MediaRouter / src / com.example.android.mediarouter / player /

SessionManager.java

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