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