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