• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.example.android.supportv7.media;
18 
19 import android.app.PendingIntent;
20 import android.net.Uri;
21 import android.support.v7.media.MediaItemStatus;
22 import android.support.v7.media.MediaSessionStatus;
23 import android.util.Log;
24 
25 import java.util.List;
26 import java.util.ArrayList;
27 
28 /**
29  * SessionManager manages a media session as a queue. It supports common
30  * queuing behaviors such as enqueue/remove of media items, pause/resume/stop,
31  * etc.
32  *
33  * Actual playback of a single media item is abstracted into a Player interface,
34  * and is handled outside this class.
35  */
36 public class SessionManager implements Player.Callback {
37     private static final String TAG = "SessionManager";
38     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
39 
40     private String mName;
41     private int mSessionId;
42     private int mItemId;
43     private boolean mPaused;
44     private boolean mSessionValid;
45     private Player mPlayer;
46     private Callback mCallback;
47     private List<PlaylistItem> mPlaylist = new ArrayList<PlaylistItem>();
48 
SessionManager(String name)49     public SessionManager(String name) {
50         mName = name;
51     }
52 
isPaused()53     public boolean isPaused() {
54         return hasSession() && mPaused;
55     }
56 
hasSession()57     public boolean hasSession() {
58         return mSessionValid;
59     }
60 
getSessionId()61     public String getSessionId() {
62         return mSessionValid ? Integer.toString(mSessionId) : null;
63     }
64 
getCurrentItem()65     public PlaylistItem getCurrentItem() {
66         return mPlaylist.isEmpty() ? null : mPlaylist.get(0);
67     }
68 
69     // Returns the cached playlist (note this is not responsible for updating it)
getPlaylist()70     public List<PlaylistItem> getPlaylist() {
71         return mPlaylist;
72     }
73 
74     // Updates the playlist asynchronously, calls onPlaylistReady() when finished.
updateStatus()75     public void updateStatus() {
76         if (DEBUG) {
77             log("updateStatus");
78         }
79         checkPlayer();
80         // update the statistics first, so that the stats string is valid when
81         // onPlaylistReady() gets called in the end
82         mPlayer.takeSnapshot();
83 
84         if (mPlaylist.isEmpty()) {
85             // If queue is empty, don't forget to call onPlaylistReady()!
86             onPlaylistReady();
87         } else if (mPlayer.isQueuingSupported()) {
88             // If player supports queuing, get status of each item. Player is
89             // responsible to call onPlaylistReady() after last getStatus().
90             // (update=1 requires player to callback onPlaylistReady())
91             for (int i = 0; i < mPlaylist.size(); i++) {
92                 PlaylistItem item = mPlaylist.get(i);
93                 mPlayer.getStatus(item, (i == mPlaylist.size() - 1) /* update */);
94             }
95         } else {
96             // Otherwise, only need to get status for current item. Player is
97             // responsible to call onPlaylistReady() when finished.
98             mPlayer.getStatus(getCurrentItem(), true /* update */);
99         }
100     }
101 
add(String title, Uri uri, String mime)102     public PlaylistItem add(String title, Uri uri, String mime) {
103         return add(title, uri, mime, null);
104     }
105 
add(String title, Uri uri, String mime, PendingIntent receiver)106     public PlaylistItem add(String title, Uri uri, String mime, PendingIntent receiver) {
107         if (DEBUG) {
108             log("add: title=" + title + ", uri=" + uri + ", receiver=" + receiver);
109         }
110         // create new session if needed
111         startSession();
112         checkPlayerAndSession();
113 
114         // append new item with initial status PLAYBACK_STATE_PENDING
115         PlaylistItem item = new PlaylistItem(Integer.toString(mSessionId),
116                 Integer.toString(mItemId), title, uri, mime, receiver);
117         mPlaylist.add(item);
118         mItemId++;
119 
120         // if player supports queuing, enqueue the item now
121         if (mPlayer.isQueuingSupported()) {
122             mPlayer.enqueue(item);
123         }
124         updatePlaybackState();
125         return item;
126     }
127 
remove(String iid)128     public PlaylistItem remove(String iid) {
129         if (DEBUG) {
130             log("remove: iid=" + iid);
131         }
132         checkPlayerAndSession();
133         return removeItem(iid, MediaItemStatus.PLAYBACK_STATE_CANCELED);
134     }
135 
seek(String iid, long pos)136     public PlaylistItem seek(String iid, long pos) {
137         if (DEBUG) {
138             log("seek: iid=" + iid +", pos=" + pos);
139         }
140         checkPlayerAndSession();
141         // seeking on pending items are not yet supported
142         checkItemCurrent(iid);
143 
144         PlaylistItem item = getCurrentItem();
145         if (pos != item.getPosition()) {
146             item.setPosition(pos);
147             if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
148                     || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
149                 mPlayer.seek(item);
150             }
151         }
152         return item;
153     }
154 
getStatus(String iid)155     public PlaylistItem getStatus(String iid) {
156         checkPlayerAndSession();
157 
158         // This should only be called for local player. Remote player is
159         // asynchronous, need to use updateStatus() instead.
160         if (mPlayer.isRemotePlayback()) {
161             throw new IllegalStateException(
162                     "getStatus should not be called on remote player!");
163         }
164 
165         for (PlaylistItem item : mPlaylist) {
166             if (item.getItemId().equals(iid)) {
167                 if (item == getCurrentItem()) {
168                     mPlayer.getStatus(item, false);
169                 }
170                 return item;
171             }
172         }
173         return null;
174     }
175 
pause()176     public void pause() {
177         if (DEBUG) {
178             log("pause");
179         }
180         if (!mSessionValid) {
181             return;
182         }
183         checkPlayer();
184         mPaused = true;
185         updatePlaybackState();
186     }
187 
resume()188     public void resume() {
189         if (DEBUG) {
190             log("resume");
191         }
192         if (!mSessionValid) {
193             return;
194         }
195         checkPlayer();
196         mPaused = false;
197         updatePlaybackState();
198     }
199 
stop()200     public void stop() {
201         if (DEBUG) {
202             log("stop");
203         }
204         if (!mSessionValid) {
205             return;
206         }
207         checkPlayer();
208         mPlayer.stop();
209         mPlaylist.clear();
210         mPaused = false;
211         updateStatus();
212     }
213 
startSession()214     public String startSession() {
215         if (!mSessionValid) {
216             mSessionId++;
217             mItemId = 0;
218             mPaused = false;
219             mSessionValid = true;
220             return Integer.toString(mSessionId);
221         }
222         return null;
223     }
224 
endSession()225     public boolean endSession() {
226         if (mSessionValid) {
227             mSessionValid = false;
228             return true;
229         }
230         return false;
231     }
232 
getSessionStatus(String sid)233     MediaSessionStatus getSessionStatus(String sid) {
234         int sessionState = (sid != null && sid.equals(mSessionId)) ?
235                 MediaSessionStatus.SESSION_STATE_ACTIVE :
236                     MediaSessionStatus.SESSION_STATE_INVALIDATED;
237 
238         return new MediaSessionStatus.Builder(sessionState)
239                 .setQueuePaused(mPaused)
240                 .build();
241     }
242 
243     // Suspend the playback manager. Put the current item back into PENDING
244     // state, and remember the current playback position. Called when switching
245     // to a different player (route).
suspend(long pos)246     public void suspend(long pos) {
247         for (PlaylistItem item : mPlaylist) {
248             item.setRemoteItemId(null);
249             item.setDuration(0);
250         }
251         PlaylistItem item = getCurrentItem();
252         if (DEBUG) {
253             log("suspend: item=" + item + ", pos=" + pos);
254         }
255         if (item != null) {
256             if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
257                     || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
258                 item.setState(MediaItemStatus.PLAYBACK_STATE_PENDING);
259                 item.setPosition(pos);
260             }
261         }
262     }
263 
264     // Unsuspend the playback manager. Restart playback on new player (route).
265     // This will resume playback of current item. Furthermore, if the new player
266     // supports queuing, playlist will be re-established on the remote player.
unsuspend()267     public void unsuspend() {
268         if (DEBUG) {
269             log("unsuspend");
270         }
271         if (mPlayer.isQueuingSupported()) {
272             for (PlaylistItem item : mPlaylist) {
273                 mPlayer.enqueue(item);
274             }
275         }
276         updatePlaybackState();
277     }
278 
279     // Player.Callback
280     @Override
onError()281     public void onError() {
282         finishItem(true);
283     }
284 
285     @Override
onCompletion()286     public void onCompletion() {
287         finishItem(false);
288     }
289 
290     @Override
onPlaylistChanged()291     public void onPlaylistChanged() {
292         // Playlist has changed, update the cached playlist
293         updateStatus();
294     }
295 
296     @Override
onPlaylistReady()297     public void onPlaylistReady() {
298         // Notify activity to update Ui
299         if (mCallback != null) {
300             mCallback.onStatusChanged();
301         }
302     }
303 
log(String message)304     private void log(String message) {
305         Log.d(TAG, mName + ": " + message);
306     }
307 
checkPlayer()308     private void checkPlayer() {
309         if (mPlayer == null) {
310             throw new IllegalStateException("Player not set!");
311         }
312     }
313 
checkSession()314     private void checkSession() {
315         if (!mSessionValid) {
316             throw new IllegalStateException("Session not set!");
317         }
318     }
319 
checkPlayerAndSession()320     private void checkPlayerAndSession() {
321         checkPlayer();
322         checkSession();
323     }
324 
checkItemCurrent(String iid)325     private void checkItemCurrent(String iid) {
326         PlaylistItem item = getCurrentItem();
327         if (item == null || !item.getItemId().equals(iid)) {
328             throw new IllegalArgumentException("Item is not current!");
329         }
330     }
331 
updatePlaybackState()332     private void updatePlaybackState() {
333         PlaylistItem item = getCurrentItem();
334         if (item != null) {
335             if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING) {
336                 item.setState(mPaused ? MediaItemStatus.PLAYBACK_STATE_PAUSED
337                         : MediaItemStatus.PLAYBACK_STATE_PLAYING);
338                 if (!mPlayer.isQueuingSupported()) {
339                     mPlayer.play(item);
340                 }
341             } else if (mPaused && item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) {
342                 mPlayer.pause();
343                 item.setState(MediaItemStatus.PLAYBACK_STATE_PAUSED);
344             } else if (!mPaused && item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
345                 mPlayer.resume();
346                 item.setState(MediaItemStatus.PLAYBACK_STATE_PLAYING);
347             }
348             // notify client that item playback status has changed
349             if (mCallback != null) {
350                 mCallback.onItemChanged(item);
351             }
352         } else {
353             mPlayer.initMediaSession();
354         }
355         updateStatus();
356     }
357 
removeItem(String iid, int state)358     private PlaylistItem removeItem(String iid, int state) {
359         checkPlayerAndSession();
360         List<PlaylistItem> queue =
361                 new ArrayList<PlaylistItem>(mPlaylist.size());
362         PlaylistItem found = null;
363         for (PlaylistItem item : mPlaylist) {
364             if (iid.equals(item.getItemId())) {
365                 if (mPlayer.isQueuingSupported()) {
366                     mPlayer.remove(item.getRemoteItemId());
367                 } else if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
368                         || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED){
369                     mPlayer.stop();
370                 }
371                 item.setState(state);
372                 found = item;
373                 // notify client that item is now removed
374                 if (mCallback != null) {
375                     mCallback.onItemChanged(found);
376                 }
377             } else {
378                 queue.add(item);
379             }
380         }
381         if (found != null) {
382             mPlaylist = queue;
383             updatePlaybackState();
384         } else {
385             log("item not found");
386         }
387         return found;
388     }
389 
finishItem(boolean error)390     private void finishItem(boolean error) {
391         PlaylistItem item = getCurrentItem();
392         if (item != null) {
393             removeItem(item.getItemId(), error ?
394                     MediaItemStatus.PLAYBACK_STATE_ERROR :
395                         MediaItemStatus.PLAYBACK_STATE_FINISHED);
396             updateStatus();
397         }
398     }
399 
400     // set the Player that this playback manager will interact with
setPlayer(Player player)401     public void setPlayer(Player player) {
402         mPlayer = player;
403         checkPlayer();
404         mPlayer.setCallback(this);
405     }
406 
407     // provide a callback interface to tell the UI when significant state changes occur
setCallback(Callback callback)408     public void setCallback(Callback callback) {
409         mCallback = callback;
410     }
411 
412     @Override
toString()413     public String toString() {
414         String result = "Media Queue: ";
415         if (!mPlaylist.isEmpty()) {
416             for (PlaylistItem item : mPlaylist) {
417                 result += "\n" + item.toString();
418             }
419         } else {
420             result += "<empty>";
421         }
422         return result;
423     }
424 
425     public interface Callback {
onStatusChanged()426         void onStatusChanged();
onItemChanged(PlaylistItem item)427         void onItemChanged(PlaylistItem item);
428     }
429 }
430