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 * 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 */
60public 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
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     */
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     */
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     */
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     */
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     */
250    public Bundle getExtras() {
251        return mBundle.getBundle(KEY_EXTRAS);
252    }
253
254    @Override
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
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     */
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     */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
388        public MediaItemStatus build() {
389            return new MediaItemStatus(mBundle);
390        }
391    }
392}
393