1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.mediarouter.media; 18 19 import android.app.PendingIntent; 20 import android.os.Bundle; 21 import android.os.SystemClock; 22 23 import androidx.core.util.TimeUtils; 24 25 /** 26 * Describes the playback status of a media item. 27 * <p> 28 * This class is part of the remote playback protocol described by the 29 * {@link MediaControlIntent MediaControlIntent} class. 30 * </p><p> 31 * As a media item is played, it transitions through a sequence of states including: 32 * {@link #PLAYBACK_STATE_PENDING pending}, {@link #PLAYBACK_STATE_BUFFERING buffering}, 33 * {@link #PLAYBACK_STATE_PLAYING playing}, {@link #PLAYBACK_STATE_PAUSED paused}, 34 * {@link #PLAYBACK_STATE_FINISHED finished}, {@link #PLAYBACK_STATE_CANCELED canceled}, 35 * {@link #PLAYBACK_STATE_INVALIDATED invalidated}, and 36 * {@link #PLAYBACK_STATE_ERROR error}. Refer to the documentation of each state 37 * for an explanation of its meaning. 38 * </p><p> 39 * While the item is playing, the playback status may also include progress information 40 * about the {@link #getContentPosition content position} and 41 * {@link #getContentDuration content duration} although not all route destinations 42 * will report it. 43 * </p><p> 44 * To monitor playback status, the application should supply a {@link PendingIntent} to use as the 45 * {@link MediaControlIntent#EXTRA_ITEM_STATUS_UPDATE_RECEIVER item status update receiver} 46 * for a given {@link MediaControlIntent#ACTION_PLAY playback request}. Note that 47 * the status update receiver will only be invoked for major status changes such as a 48 * transition from playing to finished. 49 * </p><p class="note"> 50 * The status update receiver will not be invoked for minor progress updates such as 51 * changes to playback position or duration. If the application wants to monitor 52 * playback progress, then it must use the 53 * {@link MediaControlIntent#ACTION_GET_STATUS get status request} to poll for changes 54 * periodically and estimate the playback position while playing. Note that there may 55 * be a significant power impact to polling so the application is advised only 56 * to poll when the screen is on and never more than about once every 5 seconds or so. 57 * </p><p> 58 * This object is immutable once created using a {@link Builder} instance. 59 * </p> 60 */ 61 public final class MediaItemStatus { 62 static final String KEY_TIMESTAMP = "timestamp"; 63 static final String KEY_PLAYBACK_STATE = "playbackState"; 64 static final String KEY_CONTENT_POSITION = "contentPosition"; 65 static final String KEY_CONTENT_DURATION = "contentDuration"; 66 static final String KEY_EXTRAS = "extras"; 67 68 final Bundle mBundle; 69 70 /** 71 * Playback state: Pending. 72 * <p> 73 * Indicates that the media item has not yet started playback but will be played eventually. 74 * </p> 75 */ 76 public static final int PLAYBACK_STATE_PENDING = 0; 77 78 /** 79 * Playback state: Playing. 80 * <p> 81 * Indicates that the media item is currently playing. 82 * </p> 83 */ 84 public static final int PLAYBACK_STATE_PLAYING = 1; 85 86 /** 87 * Playback state: Paused. 88 * <p> 89 * Indicates that playback of the media item has been paused. Playback can be 90 * resumed using the {@link MediaControlIntent#ACTION_RESUME resume} action. 91 * </p> 92 */ 93 public static final int PLAYBACK_STATE_PAUSED = 2; 94 95 /** 96 * Playback state: Buffering or seeking to a new position. 97 * <p> 98 * Indicates that the media item has been temporarily interrupted 99 * to fetch more content. Playback will continue automatically 100 * when enough content has been buffered. 101 * </p> 102 */ 103 public static final int PLAYBACK_STATE_BUFFERING = 3; 104 105 /** 106 * Playback state: Finished. 107 * <p> 108 * Indicates that the media item played to the end of the content and finished normally. 109 * </p><p> 110 * A finished media item cannot be resumed. To play the content again, the application 111 * must send a new {@link MediaControlIntent#ACTION_PLAY play} or 112 * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action. 113 * </p> 114 */ 115 public static final int PLAYBACK_STATE_FINISHED = 4; 116 117 /** 118 * Playback state: Canceled. 119 * <p> 120 * Indicates that the media item was explicitly removed from the queue by the 121 * application. Items may be canceled and removed from the queue using 122 * the {@link MediaControlIntent#ACTION_REMOVE remove} or 123 * {@link MediaControlIntent#ACTION_STOP stop} action or by issuing 124 * another {@link MediaControlIntent#ACTION_PLAY play} action that has the 125 * side-effect of clearing the queue. 126 * </p><p> 127 * A canceled media item cannot be resumed. To play the content again, the 128 * application must send a new {@link MediaControlIntent#ACTION_PLAY play} or 129 * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action. 130 * </p> 131 */ 132 public static final int PLAYBACK_STATE_CANCELED = 5; 133 134 /** 135 * Playback state: Invalidated. 136 * <p> 137 * Indicates that the media item was invalidated permanently and involuntarily. 138 * This state is used to indicate that the media item was invalidated and removed 139 * from the queue because the session to which it belongs was invalidated 140 * (typically by another application taking control of the route). 141 * </p><p> 142 * When invalidation occurs, the application should generally wait for the user 143 * to perform an explicit action, such as clicking on a play button in the UI, 144 * before creating a new media session to avoid unnecessarily interrupting 145 * another application that may have just started using the route. 146 * </p><p> 147 * An invalidated media item cannot be resumed. To play the content again, the application 148 * must send a new {@link MediaControlIntent#ACTION_PLAY play} or 149 * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action. 150 * </p> 151 */ 152 public static final int PLAYBACK_STATE_INVALIDATED = 6; 153 154 /** 155 * Playback state: Playback halted or aborted due to an error. 156 * <p> 157 * Examples of errors are no network connectivity when attempting to retrieve content 158 * from a server, or expired user credentials when trying to play subscription-based 159 * content. 160 * </p><p> 161 * A media item in the error state cannot be resumed. To play the content again, 162 * the application must send a new {@link MediaControlIntent#ACTION_PLAY play} or 163 * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action. 164 * </p> 165 */ 166 public static final int PLAYBACK_STATE_ERROR = 7; 167 168 /** 169 * Integer extra: HTTP status code. 170 * <p> 171 * Specifies the HTTP status code that was encountered when the content 172 * was requested after all redirects were followed. This key only needs to 173 * specified when the content uri uses the HTTP or HTTPS scheme and an error 174 * occurred. This key may be omitted if the content was able to be played 175 * successfully; there is no need to report a 200 (OK) status code. 176 * </p><p> 177 * The value is an integer HTTP status code, such as 401 (Unauthorized), 178 * 404 (Not Found), or 500 (Server Error), or 0 if none. 179 * </p> 180 */ 181 public static final String EXTRA_HTTP_STATUS_CODE = 182 "android.media.status.extra.HTTP_STATUS_CODE"; 183 184 /** 185 * Bundle extra: HTTP response headers. 186 * <p> 187 * Specifies the HTTP response headers that were returned when the content was 188 * requested from the network. The headers may include additional information 189 * about the content or any errors conditions that were encountered while 190 * trying to fetch the content. 191 * </p><p> 192 * The value is a {@link android.os.Bundle} of string based key-value pairs 193 * that describe the HTTP response headers. 194 * </p> 195 */ 196 public static final String EXTRA_HTTP_RESPONSE_HEADERS = 197 "android.media.status.extra.HTTP_RESPONSE_HEADERS"; 198 MediaItemStatus(Bundle bundle)199 MediaItemStatus(Bundle bundle) { 200 mBundle = bundle; 201 } 202 203 /** 204 * Gets the timestamp associated with the status information in 205 * milliseconds since boot in the {@link SystemClock#elapsedRealtime} time base. 206 * 207 * @return The status timestamp in the {@link SystemClock#elapsedRealtime()} time base. 208 */ getTimestamp()209 public long getTimestamp() { 210 return mBundle.getLong(KEY_TIMESTAMP); 211 } 212 213 /** 214 * Gets the playback state of the media item. 215 * 216 * @return The playback state. One of {@link #PLAYBACK_STATE_PENDING}, 217 * {@link #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED}, 218 * {@link #PLAYBACK_STATE_BUFFERING}, {@link #PLAYBACK_STATE_FINISHED}, 219 * {@link #PLAYBACK_STATE_CANCELED}, {@link #PLAYBACK_STATE_INVALIDATED}, 220 * or {@link #PLAYBACK_STATE_ERROR}. 221 */ getPlaybackState()222 public int getPlaybackState() { 223 return mBundle.getInt(KEY_PLAYBACK_STATE, PLAYBACK_STATE_ERROR); 224 } 225 226 /** 227 * Gets the content playback position as a long integer number of milliseconds 228 * from the beginning of the content. 229 * 230 * @return The content playback position in milliseconds, or -1 if unknown. 231 */ getContentPosition()232 public long getContentPosition() { 233 return mBundle.getLong(KEY_CONTENT_POSITION, -1); 234 } 235 236 /** 237 * Gets the total duration of the content to be played as a long integer number of 238 * milliseconds. 239 * 240 * @return The content duration in milliseconds, or -1 if unknown. 241 */ getContentDuration()242 public long getContentDuration() { 243 return mBundle.getLong(KEY_CONTENT_DURATION, -1); 244 } 245 246 /** 247 * Gets a bundle of extras for this status object. 248 * The extras will be ignored by the media router but they may be used 249 * by applications. 250 */ getExtras()251 public Bundle getExtras() { 252 return mBundle.getBundle(KEY_EXTRAS); 253 } 254 255 @Override toString()256 public String toString() { 257 StringBuilder result = new StringBuilder(); 258 result.append("MediaItemStatus{ "); 259 result.append("timestamp="); 260 TimeUtils.formatDuration(SystemClock.elapsedRealtime() - getTimestamp(), result); 261 result.append(" ms ago"); 262 result.append(", playbackState=").append(playbackStateToString(getPlaybackState())); 263 result.append(", contentPosition=").append(getContentPosition()); 264 result.append(", contentDuration=").append(getContentDuration()); 265 result.append(", extras=").append(getExtras()); 266 result.append(" }"); 267 return result.toString(); 268 } 269 playbackStateToString(int playbackState)270 private static String playbackStateToString(int playbackState) { 271 switch (playbackState) { 272 case PLAYBACK_STATE_PENDING: 273 return "pending"; 274 case PLAYBACK_STATE_BUFFERING: 275 return "buffering"; 276 case PLAYBACK_STATE_PLAYING: 277 return "playing"; 278 case PLAYBACK_STATE_PAUSED: 279 return "paused"; 280 case PLAYBACK_STATE_FINISHED: 281 return "finished"; 282 case PLAYBACK_STATE_CANCELED: 283 return "canceled"; 284 case PLAYBACK_STATE_INVALIDATED: 285 return "invalidated"; 286 case PLAYBACK_STATE_ERROR: 287 return "error"; 288 } 289 return Integer.toString(playbackState); 290 } 291 292 /** 293 * Converts this object to a bundle for serialization. 294 * 295 * @return The contents of the object represented as a bundle. 296 */ asBundle()297 public Bundle asBundle() { 298 return mBundle; 299 } 300 301 /** 302 * Creates an instance from a bundle. 303 * 304 * @param bundle The bundle, or null if none. 305 * @return The new instance, or null if the bundle was null. 306 */ fromBundle(Bundle bundle)307 public static MediaItemStatus fromBundle(Bundle bundle) { 308 return bundle != null ? new MediaItemStatus(bundle) : null; 309 } 310 311 /** 312 * Builder for {@link MediaItemStatus media item status objects}. 313 */ 314 public static final class Builder { 315 private final Bundle mBundle; 316 317 /** 318 * Creates a media item status builder using the current time as the 319 * reference timestamp. 320 * 321 * @param playbackState The item playback state. 322 */ Builder(int playbackState)323 public Builder(int playbackState) { 324 mBundle = new Bundle(); 325 setTimestamp(SystemClock.elapsedRealtime()); 326 setPlaybackState(playbackState); 327 } 328 329 /** 330 * Creates a media item status builder whose initial contents are 331 * copied from an existing status. 332 */ Builder(MediaItemStatus status)333 public Builder(MediaItemStatus status) { 334 if (status == null) { 335 throw new IllegalArgumentException("status must not be null"); 336 } 337 338 mBundle = new Bundle(status.mBundle); 339 } 340 341 /** 342 * Sets the timestamp associated with the status information in 343 * milliseconds since boot in the {@link SystemClock#elapsedRealtime} time base. 344 */ setTimestamp(long elapsedRealtimeTimestamp)345 public Builder setTimestamp(long elapsedRealtimeTimestamp) { 346 mBundle.putLong(KEY_TIMESTAMP, elapsedRealtimeTimestamp); 347 return this; 348 } 349 350 /** 351 * Sets the playback state of the media item. 352 */ setPlaybackState(int playbackState)353 public Builder setPlaybackState(int playbackState) { 354 mBundle.putInt(KEY_PLAYBACK_STATE, playbackState); 355 return this; 356 } 357 358 /** 359 * Sets the content playback position as a long integer number of milliseconds 360 * from the beginning of the content. 361 */ setContentPosition(long positionMilliseconds)362 public Builder setContentPosition(long positionMilliseconds) { 363 mBundle.putLong(KEY_CONTENT_POSITION, positionMilliseconds); 364 return this; 365 } 366 367 /** 368 * Sets the total duration of the content to be played as a long integer number 369 * of milliseconds. 370 */ setContentDuration(long durationMilliseconds)371 public Builder setContentDuration(long durationMilliseconds) { 372 mBundle.putLong(KEY_CONTENT_DURATION, durationMilliseconds); 373 return this; 374 } 375 376 /** 377 * Sets a bundle of extras for this status object. 378 * The extras will be ignored by the media router but they may be used 379 * by applications. 380 */ setExtras(Bundle extras)381 public Builder setExtras(Bundle extras) { 382 mBundle.putBundle(KEY_EXTRAS, extras); 383 return this; 384 } 385 386 /** 387 * Builds the {@link MediaItemStatus media item status object}. 388 */ build()389 public MediaItemStatus build() { 390 return new MediaItemStatus(mBundle); 391 } 392 } 393 } 394