420 lines
13 KiB
Java
420 lines
13 KiB
Java
/*
|
|
* 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 com.example.android.mediarouter.player;
|
|
|
|
import android.app.PendingIntent;
|
|
import android.net.Uri;
|
|
import android.support.v7.media.MediaItemStatus;
|
|
import android.support.v7.media.MediaSessionStatus;
|
|
import android.util.Log;
|
|
|
|
import com.example.android.mediarouter.player.Player.Callback;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* SessionManager manages a media session as a queue. It supports common
|
|
* queuing behaviors such as enqueue/remove of media items, pause/resume/stop,
|
|
* etc.
|
|
*
|
|
* Actual playback of a single media item is abstracted into a Player interface,
|
|
* and is handled outside this class.
|
|
*/
|
|
public class SessionManager implements Callback {
|
|
private static final String TAG = "SessionManager";
|
|
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
|
|
|
private String mName;
|
|
private int mSessionId;
|
|
private int mItemId;
|
|
private boolean mPaused;
|
|
private boolean mSessionValid;
|
|
private Player mPlayer;
|
|
private Callback mCallback;
|
|
private List<PlaylistItem> mPlaylist = new ArrayList<PlaylistItem>();
|
|
|
|
public SessionManager(String name) {
|
|
mName = name;
|
|
}
|
|
|
|
public boolean hasSession() {
|
|
return mSessionValid;
|
|
}
|
|
|
|
public String getSessionId() {
|
|
return mSessionValid ? Integer.toString(mSessionId) : null;
|
|
}
|
|
|
|
public PlaylistItem getCurrentItem() {
|
|
return mPlaylist.isEmpty() ? null : mPlaylist.get(0);
|
|
}
|
|
|
|
// Get the cached statistic info from the player (will not update it)
|
|
public String getStatistics() {
|
|
checkPlayer();
|
|
return mPlayer.getStatistics();
|
|
}
|
|
|
|
// Returns the cached playlist (note this is not responsible for updating it)
|
|
public List<PlaylistItem> getPlaylist() {
|
|
return mPlaylist;
|
|
}
|
|
|
|
// Updates the playlist asynchronously, calls onPlaylistReady() when finished.
|
|
public void updateStatus() {
|
|
if (DEBUG) {
|
|
log("updateStatus");
|
|
}
|
|
checkPlayer();
|
|
// update the statistics first, so that the stats string is valid when
|
|
// onPlaylistReady() gets called in the end
|
|
mPlayer.updateStatistics();
|
|
|
|
if (mPlaylist.isEmpty()) {
|
|
// If queue is empty, don't forget to call onPlaylistReady()!
|
|
onPlaylistReady();
|
|
} else if (mPlayer.isQueuingSupported()) {
|
|
// If player supports queuing, get status of each item. Player is
|
|
// responsible to call onPlaylistReady() after last getStatus().
|
|
// (update=1 requires player to callback onPlaylistReady())
|
|
for (int i = 0; i < mPlaylist.size(); i++) {
|
|
PlaylistItem item = mPlaylist.get(i);
|
|
mPlayer.getStatus(item, (i == mPlaylist.size() - 1) /* update */);
|
|
}
|
|
} else {
|
|
// Otherwise, only need to get status for current item. Player is
|
|
// responsible to call onPlaylistReady() when finished.
|
|
mPlayer.getStatus(getCurrentItem(), true /* update */);
|
|
}
|
|
}
|
|
|
|
public PlaylistItem add(Uri uri, String mime) {
|
|
return add(uri, mime, null);
|
|
}
|
|
|
|
public PlaylistItem add(Uri uri, String mime, PendingIntent receiver) {
|
|
if (DEBUG) {
|
|
log("add: uri=" + uri + ", receiver=" + receiver);
|
|
}
|
|
// create new session if needed
|
|
startSession();
|
|
checkPlayerAndSession();
|
|
|
|
// append new item with initial status PLAYBACK_STATE_PENDING
|
|
PlaylistItem item = new PlaylistItem(
|
|
Integer.toString(mSessionId), Integer.toString(mItemId), uri, mime, receiver);
|
|
mPlaylist.add(item);
|
|
mItemId++;
|
|
|
|
// if player supports queuing, enqueue the item now
|
|
if (mPlayer.isQueuingSupported()) {
|
|
mPlayer.enqueue(item);
|
|
}
|
|
updatePlaybackState();
|
|
return item;
|
|
}
|
|
|
|
public PlaylistItem remove(String iid) {
|
|
if (DEBUG) {
|
|
log("remove: iid=" + iid);
|
|
}
|
|
checkPlayerAndSession();
|
|
return removeItem(iid, MediaItemStatus.PLAYBACK_STATE_CANCELED);
|
|
}
|
|
|
|
public PlaylistItem seek(String iid, long pos) {
|
|
if (DEBUG) {
|
|
log("seek: iid=" + iid +", pos=" + pos);
|
|
}
|
|
checkPlayerAndSession();
|
|
// seeking on pending items are not yet supported
|
|
checkItemCurrent(iid);
|
|
|
|
PlaylistItem item = getCurrentItem();
|
|
if (pos != item.getPosition()) {
|
|
item.setPosition(pos);
|
|
if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
|
|
|| item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
|
|
mPlayer.seek(item);
|
|
}
|
|
}
|
|
return item;
|
|
}
|
|
|
|
public PlaylistItem getStatus(String iid) {
|
|
checkPlayerAndSession();
|
|
|
|
// This should only be called for local player. Remote player is
|
|
// asynchronous, need to use updateStatus() instead.
|
|
if (mPlayer.isRemotePlayback()) {
|
|
throw new IllegalStateException(
|
|
"getStatus should not be called on remote player!");
|
|
}
|
|
|
|
for (PlaylistItem item : mPlaylist) {
|
|
if (item.getItemId().equals(iid)) {
|
|
if (item == getCurrentItem()) {
|
|
mPlayer.getStatus(item, false);
|
|
}
|
|
return item;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void pause() {
|
|
if (DEBUG) {
|
|
log("pause");
|
|
}
|
|
mPaused = true;
|
|
updatePlaybackState();
|
|
}
|
|
|
|
public void resume() {
|
|
if (DEBUG) {
|
|
log("resume");
|
|
}
|
|
mPaused = false;
|
|
updatePlaybackState();
|
|
}
|
|
|
|
public void stop() {
|
|
if (DEBUG) {
|
|
log("stop");
|
|
}
|
|
mPlayer.stop();
|
|
mPlaylist.clear();
|
|
mPaused = false;
|
|
updateStatus();
|
|
}
|
|
|
|
public String startSession() {
|
|
if (!mSessionValid) {
|
|
mSessionId++;
|
|
mItemId = 0;
|
|
mPaused = false;
|
|
mSessionValid = true;
|
|
return Integer.toString(mSessionId);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public boolean endSession() {
|
|
if (mSessionValid) {
|
|
mSessionValid = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public MediaSessionStatus getSessionStatus(String sid) {
|
|
int sessionState = (sid != null && sid.equals(mSessionId)) ?
|
|
MediaSessionStatus.SESSION_STATE_ACTIVE :
|
|
MediaSessionStatus.SESSION_STATE_INVALIDATED;
|
|
|
|
return new MediaSessionStatus.Builder(sessionState)
|
|
.setQueuePaused(mPaused)
|
|
.build();
|
|
}
|
|
|
|
// Suspend the playback manager. Put the current item back into PENDING
|
|
// state, and remember the current playback position. Called when switching
|
|
// to a different player (route).
|
|
public void suspend(long pos) {
|
|
for (PlaylistItem item : mPlaylist) {
|
|
item.setRemoteItemId(null);
|
|
item.setDuration(0);
|
|
}
|
|
PlaylistItem item = getCurrentItem();
|
|
if (DEBUG) {
|
|
log("suspend: item=" + item + ", pos=" + pos);
|
|
}
|
|
if (item != null) {
|
|
if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
|
|
|| item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
|
|
item.setState(MediaItemStatus.PLAYBACK_STATE_PENDING);
|
|
item.setPosition(pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unsuspend the playback manager. Restart playback on new player (route).
|
|
// This will resume playback of current item. Furthermore, if the new player
|
|
// supports queuing, playlist will be re-established on the remote player.
|
|
public void unsuspend() {
|
|
if (DEBUG) {
|
|
log("unsuspend");
|
|
}
|
|
if (mPlayer.isQueuingSupported()) {
|
|
for (PlaylistItem item : mPlaylist) {
|
|
mPlayer.enqueue(item);
|
|
}
|
|
}
|
|
updatePlaybackState();
|
|
}
|
|
|
|
// Player.Callback
|
|
@Override
|
|
public void onError() {
|
|
finishItem(true);
|
|
}
|
|
|
|
@Override
|
|
public void onCompletion() {
|
|
finishItem(false);
|
|
}
|
|
|
|
@Override
|
|
public void onPlaylistChanged() {
|
|
// Playlist has changed, update the cached playlist
|
|
updateStatus();
|
|
}
|
|
|
|
@Override
|
|
public void onPlaylistReady() {
|
|
// Notify activity to update Ui
|
|
if (mCallback != null) {
|
|
mCallback.onStatusChanged();
|
|
}
|
|
}
|
|
|
|
private void log(String message) {
|
|
Log.d(TAG, mName + ": " + message);
|
|
}
|
|
|
|
private void checkPlayer() {
|
|
if (mPlayer == null) {
|
|
throw new IllegalStateException("Player not set!");
|
|
}
|
|
}
|
|
|
|
private void checkSession() {
|
|
if (!mSessionValid) {
|
|
throw new IllegalStateException("Session not set!");
|
|
}
|
|
}
|
|
|
|
private void checkPlayerAndSession() {
|
|
checkPlayer();
|
|
checkSession();
|
|
}
|
|
|
|
private void checkItemCurrent(String iid) {
|
|
PlaylistItem item = getCurrentItem();
|
|
if (item == null || !item.getItemId().equals(iid)) {
|
|
throw new IllegalArgumentException("Item is not current!");
|
|
}
|
|
}
|
|
|
|
private void updatePlaybackState() {
|
|
PlaylistItem item = getCurrentItem();
|
|
if (item != null) {
|
|
if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING) {
|
|
item.setState(mPaused ? MediaItemStatus.PLAYBACK_STATE_PAUSED
|
|
: MediaItemStatus.PLAYBACK_STATE_PLAYING);
|
|
if (!mPlayer.isQueuingSupported()) {
|
|
mPlayer.play(item);
|
|
}
|
|
} else if (mPaused && item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) {
|
|
mPlayer.pause();
|
|
item.setState(MediaItemStatus.PLAYBACK_STATE_PAUSED);
|
|
} else if (!mPaused && item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
|
|
mPlayer.resume();
|
|
item.setState(MediaItemStatus.PLAYBACK_STATE_PLAYING);
|
|
}
|
|
// notify client that item playback status has changed
|
|
if (mCallback != null) {
|
|
mCallback.onItemChanged(item);
|
|
}
|
|
}
|
|
updateStatus();
|
|
}
|
|
|
|
private PlaylistItem removeItem(String iid, int state) {
|
|
checkPlayerAndSession();
|
|
List<PlaylistItem> queue =
|
|
new ArrayList<PlaylistItem>(mPlaylist.size());
|
|
PlaylistItem found = null;
|
|
for (PlaylistItem item : mPlaylist) {
|
|
if (iid.equals(item.getItemId())) {
|
|
if (mPlayer.isQueuingSupported()) {
|
|
mPlayer.remove(item.getRemoteItemId());
|
|
} else if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
|
|
|| item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED){
|
|
mPlayer.stop();
|
|
}
|
|
item.setState(state);
|
|
found = item;
|
|
// notify client that item is now removed
|
|
if (mCallback != null) {
|
|
mCallback.onItemChanged(found);
|
|
}
|
|
} else {
|
|
queue.add(item);
|
|
}
|
|
}
|
|
if (found != null) {
|
|
mPlaylist = queue;
|
|
updatePlaybackState();
|
|
} else {
|
|
log("item not found");
|
|
}
|
|
return found;
|
|
}
|
|
|
|
private void finishItem(boolean error) {
|
|
PlaylistItem item = getCurrentItem();
|
|
if (item != null) {
|
|
removeItem(item.getItemId(), error ?
|
|
MediaItemStatus.PLAYBACK_STATE_ERROR :
|
|
MediaItemStatus.PLAYBACK_STATE_FINISHED);
|
|
updateStatus();
|
|
}
|
|
}
|
|
|
|
// set the Player that this playback manager will interact with
|
|
public void setPlayer(Player player) {
|
|
mPlayer = player;
|
|
checkPlayer();
|
|
mPlayer.setCallback(this);
|
|
}
|
|
|
|
// provide a callback interface to tell the UI when significant state changes occur
|
|
public void setCallback(Callback callback) {
|
|
mCallback = callback;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
String result = "Media Queue: ";
|
|
if (!mPlaylist.isEmpty()) {
|
|
for (PlaylistItem item : mPlaylist) {
|
|
result += "\n" + item.toString();
|
|
}
|
|
} else {
|
|
result += "<empty>";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public interface Callback {
|
|
void onStatusChanged();
|
|
void onItemChanged(PlaylistItem item);
|
|
}
|
|
}
|