/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v7.media; import android.app.PendingIntent; import android.os.Bundle; import android.os.SystemClock; import android.support.v4.util.TimeUtils; /** * Describes the playback status of a media item. *

* This class is part of the remote playback protocol described by the * {@link MediaControlIntent MediaControlIntent} class. *

* As a media item is played, it transitions through a sequence of states including: * {@link #PLAYBACK_STATE_PENDING pending}, {@link #PLAYBACK_STATE_BUFFERING buffering}, * {@link #PLAYBACK_STATE_PLAYING playing}, {@link #PLAYBACK_STATE_PAUSED paused}, * {@link #PLAYBACK_STATE_FINISHED finished}, {@link #PLAYBACK_STATE_CANCELED canceled}, * {@link #PLAYBACK_STATE_INVALIDATED invalidated}, and * {@link #PLAYBACK_STATE_ERROR error}. Refer to the documentation of each state * for an explanation of its meaning. *

* While the item is playing, the playback status may also include progress information * about the {@link #getContentPosition content position} and * {@link #getContentDuration content duration} although not all route destinations * will report it. *

* To monitor playback status, the application should supply a {@link PendingIntent} to use as the * {@link MediaControlIntent#EXTRA_ITEM_STATUS_UPDATE_RECEIVER item status update receiver} * for a given {@link MediaControlIntent#ACTION_PLAY playback request}. Note that * the status update receiver will only be invoked for major status changes such as a * transition from playing to finished. *

* The status update receiver will not be invoked for minor progress updates such as * changes to playback position or duration. If the application wants to monitor * playback progress, then it must use the * {@link MediaControlIntent#ACTION_GET_STATUS get status request} to poll for changes * periodically and estimate the playback position while playing. Note that there may * be a significant power impact to polling so the application is advised only * to poll when the screen is on and never more than about once every 5 seconds or so. *

* This object is immutable once created using a {@link Builder} instance. *

*/ public final class MediaItemStatus { static final String KEY_TIMESTAMP = "timestamp"; static final String KEY_PLAYBACK_STATE = "playbackState"; static final String KEY_CONTENT_POSITION = "contentPosition"; static final String KEY_CONTENT_DURATION = "contentDuration"; static final String KEY_EXTRAS = "extras"; final Bundle mBundle; /** * Playback state: Pending. *

* Indicates that the media item has not yet started playback but will be played eventually. *

*/ public static final int PLAYBACK_STATE_PENDING = 0; /** * Playback state: Playing. *

* Indicates that the media item is currently playing. *

*/ public static final int PLAYBACK_STATE_PLAYING = 1; /** * Playback state: Paused. *

* Indicates that playback of the media item has been paused. Playback can be * resumed using the {@link MediaControlIntent#ACTION_RESUME resume} action. *

*/ public static final int PLAYBACK_STATE_PAUSED = 2; /** * Playback state: Buffering or seeking to a new position. *

* Indicates that the media item has been temporarily interrupted * to fetch more content. Playback will continue automatically * when enough content has been buffered. *

*/ public static final int PLAYBACK_STATE_BUFFERING = 3; /** * Playback state: Finished. *

* Indicates that the media item played to the end of the content and finished normally. *

* A finished media item cannot be resumed. To play the content again, the application * must send a new {@link MediaControlIntent#ACTION_PLAY play} or * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action. *

*/ public static final int PLAYBACK_STATE_FINISHED = 4; /** * Playback state: Canceled. *

* Indicates that the media item was explicitly removed from the queue by the * application. Items may be canceled and removed from the queue using * the {@link MediaControlIntent#ACTION_REMOVE remove} or * {@link MediaControlIntent#ACTION_STOP stop} action or by issuing * another {@link MediaControlIntent#ACTION_PLAY play} action that has the * side-effect of clearing the queue. *

* A canceled media item cannot be resumed. To play the content again, the * application must send a new {@link MediaControlIntent#ACTION_PLAY play} or * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action. *

*/ public static final int PLAYBACK_STATE_CANCELED = 5; /** * Playback state: Invalidated. *

* Indicates that the media item was invalidated permanently and involuntarily. * This state is used to indicate that the media item was invalidated and removed * from the queue because the session to which it belongs was invalidated * (typically by another application taking control of the route). *

* When invalidation occurs, the application should generally wait for the user * to perform an explicit action, such as clicking on a play button in the UI, * before creating a new media session to avoid unnecessarily interrupting * another application that may have just started using the route. *

* An invalidated media item cannot be resumed. To play the content again, the application * must send a new {@link MediaControlIntent#ACTION_PLAY play} or * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action. *

*/ public static final int PLAYBACK_STATE_INVALIDATED = 6; /** * Playback state: Playback halted or aborted due to an error. *

* Examples of errors are no network connectivity when attempting to retrieve content * from a server, or expired user credentials when trying to play subscription-based * content. *

* A media item in the error state cannot be resumed. To play the content again, * the application must send a new {@link MediaControlIntent#ACTION_PLAY play} or * {@link MediaControlIntent#ACTION_ENQUEUE enqueue} action. *

*/ public static final int PLAYBACK_STATE_ERROR = 7; /** * Integer extra: HTTP status code. *

* Specifies the HTTP status code that was encountered when the content * was requested after all redirects were followed. This key only needs to * specified when the content uri uses the HTTP or HTTPS scheme and an error * occurred. This key may be omitted if the content was able to be played * successfully; there is no need to report a 200 (OK) status code. *

* The value is an integer HTTP status code, such as 401 (Unauthorized), * 404 (Not Found), or 500 (Server Error), or 0 if none. *

*/ public static final String EXTRA_HTTP_STATUS_CODE = "android.media.status.extra.HTTP_STATUS_CODE"; /** * Bundle extra: HTTP response headers. *

* Specifies the HTTP response headers that were returned when the content was * requested from the network. The headers may include additional information * about the content or any errors conditions that were encountered while * trying to fetch the content. *

* The value is a {@link android.os.Bundle} of string based key-value pairs * that describe the HTTP response headers. *

*/ public static final String EXTRA_HTTP_RESPONSE_HEADERS = "android.media.status.extra.HTTP_RESPONSE_HEADERS"; MediaItemStatus(Bundle bundle) { mBundle = bundle; } /** * Gets the timestamp associated with the status information in * milliseconds since boot in the {@link SystemClock#elapsedRealtime} time base. * * @return The status timestamp in the {@link SystemClock#elapsedRealtime()} time base. */ public long getTimestamp() { return mBundle.getLong(KEY_TIMESTAMP); } /** * Gets the playback state of the media item. * * @return The playback state. One of {@link #PLAYBACK_STATE_PENDING}, * {@link #PLAYBACK_STATE_PLAYING}, {@link #PLAYBACK_STATE_PAUSED}, * {@link #PLAYBACK_STATE_BUFFERING}, {@link #PLAYBACK_STATE_FINISHED}, * {@link #PLAYBACK_STATE_CANCELED}, {@link #PLAYBACK_STATE_INVALIDATED}, * or {@link #PLAYBACK_STATE_ERROR}. */ public int getPlaybackState() { return mBundle.getInt(KEY_PLAYBACK_STATE, PLAYBACK_STATE_ERROR); } /** * Gets the content playback position as a long integer number of milliseconds * from the beginning of the content. * * @return The content playback position in milliseconds, or -1 if unknown. */ public long getContentPosition() { return mBundle.getLong(KEY_CONTENT_POSITION, -1); } /** * Gets the total duration of the content to be played as a long integer number of * milliseconds. * * @return The content duration in milliseconds, or -1 if unknown. */ public long getContentDuration() { return mBundle.getLong(KEY_CONTENT_DURATION, -1); } /** * Gets a bundle of extras for this status object. * The extras will be ignored by the media router but they may be used * by applications. */ public Bundle getExtras() { return mBundle.getBundle(KEY_EXTRAS); } @Override public String toString() { StringBuilder result = new StringBuilder(); result.append("MediaItemStatus{ "); result.append("timestamp="); TimeUtils.formatDuration(SystemClock.elapsedRealtime() - getTimestamp(), result); result.append(" ms ago"); result.append(", playbackState=").append(playbackStateToString(getPlaybackState())); result.append(", contentPosition=").append(getContentPosition()); result.append(", contentDuration=").append(getContentDuration()); result.append(", extras=").append(getExtras()); result.append(" }"); return result.toString(); } private static String playbackStateToString(int playbackState) { switch (playbackState) { case PLAYBACK_STATE_PENDING: return "pending"; case PLAYBACK_STATE_BUFFERING: return "buffering"; case PLAYBACK_STATE_PLAYING: return "playing"; case PLAYBACK_STATE_PAUSED: return "paused"; case PLAYBACK_STATE_FINISHED: return "finished"; case PLAYBACK_STATE_CANCELED: return "canceled"; case PLAYBACK_STATE_INVALIDATED: return "invalidated"; case PLAYBACK_STATE_ERROR: return "error"; } return Integer.toString(playbackState); } /** * Converts this object to a bundle for serialization. * * @return The contents of the object represented as a bundle. */ public Bundle asBundle() { return mBundle; } /** * Creates an instance from a bundle. * * @param bundle The bundle, or null if none. * @return The new instance, or null if the bundle was null. */ public static MediaItemStatus fromBundle(Bundle bundle) { return bundle != null ? new MediaItemStatus(bundle) : null; } /** * Builder for {@link MediaItemStatus media item status objects}. */ public static final class Builder { private final Bundle mBundle; /** * Creates a media item status builder using the current time as the * reference timestamp. * * @param playbackState The item playback state. */ public Builder(int playbackState) { mBundle = new Bundle(); setTimestamp(SystemClock.elapsedRealtime()); setPlaybackState(playbackState); } /** * Creates a media item status builder whose initial contents are * copied from an existing status. */ public Builder(MediaItemStatus status) { if (status == null) { throw new IllegalArgumentException("status must not be null"); } mBundle = new Bundle(status.mBundle); } /** * Sets the timestamp associated with the status information in * milliseconds since boot in the {@link SystemClock#elapsedRealtime} time base. */ public Builder setTimestamp(long elapsedRealtimeTimestamp) { mBundle.putLong(KEY_TIMESTAMP, elapsedRealtimeTimestamp); return this; } /** * Sets the playback state of the media item. */ public Builder setPlaybackState(int playbackState) { mBundle.putInt(KEY_PLAYBACK_STATE, playbackState); return this; } /** * Sets the content playback position as a long integer number of milliseconds * from the beginning of the content. */ public Builder setContentPosition(long positionMilliseconds) { mBundle.putLong(KEY_CONTENT_POSITION, positionMilliseconds); return this; } /** * Sets the total duration of the content to be played as a long integer number * of milliseconds. */ public Builder setContentDuration(long durationMilliseconds) { mBundle.putLong(KEY_CONTENT_DURATION, durationMilliseconds); return this; } /** * Sets a bundle of extras for this status object. * The extras will be ignored by the media router but they may be used * by applications. */ public Builder setExtras(Bundle extras) { mBundle.putBundle(KEY_EXTRAS, extras); return this; } /** * Builds the {@link MediaItemStatus media item status object}. */ public MediaItemStatus build() { return new MediaItemStatus(mBundle); } } }