extend the new PlaybackService media player callback protocol to RemotePSMP

This commit is contained in:
Domingos Lopes 2016-05-08 01:05:09 -04:00
parent 19a647226d
commit 9b123d0473
1 changed files with 138 additions and 76 deletions

View File

@ -25,6 +25,7 @@ import de.danoeh.antennapod.core.cast.DefaultCastConsumer;
import de.danoeh.antennapod.core.cast.RemoteMedia;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.RewindAfterPauseUtils;
import de.danoeh.antennapod.core.util.playback.Playable;
@ -42,8 +43,9 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
private final CastManager castMgr;
private volatile Playable media;
private volatile MediaInfo remoteMedia;
private volatile MediaType mediaType;
private volatile MediaInfo remoteMedia;
private volatile int remoteState;
private final AtomicBoolean isBuffering;
@ -57,31 +59,28 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
mediaType = null;
startWhenPrepared = new AtomicBoolean(false);
isBuffering = new AtomicBoolean(false);
remoteState = MediaStatus.PLAYER_STATE_UNKNOWN;
try {
if (castMgr.isConnected() && castMgr.isRemoteMediaLoaded()) {
// updates the state, but does not start playing new media if it was going to
onRemoteMediaPlayerStatusUpdated(
((p, playNextEpisode, wasSkipped, switchingPlayers) ->
this.callback.endPlayback(p, false, wasSkipped, switchingPlayers)));
onRemoteMediaPlayerStatusUpdated();
}
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
Log.e(TAG, "Unable to do initial check for loaded media", e);
}
castMgr.addCastConsumer(castConsumer);
//TODO
}
private CastConsumer castConsumer = new DefaultCastConsumer() {
@Override
public void onRemoteMediaPlayerMetadataUpdated() {
RemotePSMP.this.onRemoteMediaPlayerStatusUpdated(callback::endPlayback);
RemotePSMP.this.onRemoteMediaPlayerStatusUpdated();
}
@Override
public void onRemoteMediaPlayerStatusUpdated() {
RemotePSMP.this.onRemoteMediaPlayerStatusUpdated(callback::endPlayback);
RemotePSMP.this.onRemoteMediaPlayerStatusUpdated();
}
@Override
@ -121,8 +120,8 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
Log.d(TAG, "unable to get standbyState on onApplicationStatusChanged()");
}
if (playbackEnded) {
setPlayerStatus(PlayerStatus.INDETERMINATE, media);
callback.endPlayback(media, true, false, false);
// This is an unconventional thing to occur...
endPlayback(true);
}
}
@ -166,85 +165,126 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
return null;
}
private void onRemoteMediaPlayerStatusUpdated(@NonNull EndPlaybackCall endPlaybackCall) {
private void onRemoteMediaPlayerStatusUpdated() {
MediaStatus status = castMgr.getMediaStatus();
if (status == null) {
Log.d(TAG, "Received null MediaStatus");
//setBuffering(false);
//setPlayerStatus(PlayerStatus.INDETERMINATE, null);
return;
} else {
Log.d(TAG, "Received remote status/media update. New state=" + status.getPlayerState());
}
Playable currentMedia = localVersion(status.getMediaInfo());
boolean updateUI = currentMedia != media;
if (currentMedia != null) {
long position = status.getStreamPosition();
if (position > 0 && currentMedia.getPosition() == 0) {
currentMedia.setPosition((int) position);
}
}
int state = status.getPlayerState();
int oldState = remoteState;
remoteMedia = status.getMediaInfo();
boolean mediaChanged = !CastUtils.matches(remoteMedia, media);
boolean stateChanged = state != oldState;
if (!mediaChanged && !stateChanged) {
Log.d(TAG, "Both media and state haven't changed, so nothing to do");
return;
}
Playable currentMedia = mediaChanged ? localVersion(remoteMedia) : media;
Playable oldMedia = media;
int position = (int) status.getStreamPosition();
// check for incompatible states
if ((state == MediaStatus.PLAYER_STATE_PLAYING || state == MediaStatus.PLAYER_STATE_PAUSED)
&& currentMedia == null) {
Log.w(TAG, "RemoteMediaPlayer returned playing or pausing state, but with no media");
state = MediaStatus.PLAYER_STATE_UNKNOWN;
stateChanged = oldState != MediaStatus.PLAYER_STATE_UNKNOWN;
}
if (stateChanged) {
remoteState = state;
}
setBuffering(state == MediaStatus.PLAYER_STATE_BUFFERING);
switch (state) {
case MediaStatus.PLAYER_STATE_PLAYING:
if (!stateChanged) {
if (position >= 0) {
currentMedia.setPosition(position);
}
currentMedia.onPlaybackStart();
} else {
callback.onPlaybackStart(currentMedia, position);
}
setPlayerStatus(PlayerStatus.PLAYING, currentMedia);
break;
case MediaStatus.PLAYER_STATE_PAUSED:
if (!mediaChanged &&
oldState == MediaStatus.PLAYER_STATE_PLAYING) {
callback.onPlaybackPause(currentMedia, position);
}
setPlayerStatus(PlayerStatus.PAUSED, currentMedia);
break;
case MediaStatus.PLAYER_STATE_BUFFERING:
setPlayerStatus(playerStatus, currentMedia);
if (!mediaChanged && currentMedia != null &&
oldState == MediaStatus.PLAYER_STATE_PLAYING) {
// position could already refer to the new position after seeking
// so our best guess is the last one reported
callback.onPlaybackPause(currentMedia, position);
}
setPlayerStatus((mediaChanged || playerStatus == PlayerStatus.PREPARING) ?
PlayerStatus.PREPARING : PlayerStatus.SEEKING,
currentMedia);
break;
case MediaStatus.PLAYER_STATE_IDLE:
int reason = status.getIdleReason();
switch (reason) {
case MediaStatus.IDLE_REASON_CANCELED:
// check if we're already loading something else
if (!updateUI || media == null) {
setPlayerStatus(PlayerStatus.STOPPED, currentMedia);
} else {
updateUI = false;
// Essentially means stopped at the request of a user
callback.onPlaybackEnded(null, true);
setPlayerStatus(PlayerStatus.STOPPED, currentMedia);
if (oldMedia != null) {
if (position >= 0) {
oldMedia.setPosition(position);
}
callback.onPostPlayback(oldMedia, false, false);
}
break;
// onPlaybackEnded pretty much takes care of updating the UI
return;
case MediaStatus.IDLE_REASON_INTERRUPTED:
// check if we're already loading something else
if (!updateUI || media == null) {
setPlayerStatus(PlayerStatus.PREPARING, currentMedia);
} else {
updateUI = false;
// Means that a request to load a different media was sent
// Not sure if currentMedia already reflects the to be loaded one
if (oldState == MediaStatus.PLAYER_STATE_PLAYING && oldMedia != null) {
callback.onPlaybackPause(oldMedia, oldMedia.getPosition());
}
setPlayerStatus(PlayerStatus.PREPARING, currentMedia);
break;
case MediaStatus.IDLE_REASON_NONE:
// This probably only happens when we connected but no command has been sent yet.
setPlayerStatus(PlayerStatus.INITIALIZED, currentMedia);
break;
case MediaStatus.IDLE_REASON_FINISHED:
boolean playing = playerStatus == PlayerStatus.PLAYING;
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
endPlaybackCall.endPlayback(currentMedia,playing, false, false);
// endPlayback already updates the UI, so no need to trigger it ourselves
updateUI = false;
break;
// This is our onCompletionListener...
if (mediaChanged && currentMedia != null) {
media = currentMedia;
}
endPlayback(false);
return;
case MediaStatus.IDLE_REASON_ERROR:
Log.w(TAG, "Got an error status from the Chromecast. Skipping, if possible, to the next episode...");
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH,
R.string.cast_failed_media_error_skipping);
endPlaybackCall.endPlayback(currentMedia, startWhenPrepared.get(), true, false);
// endPlayback already updates the UI, so no need to trigger it ourselves
updateUI = false;
endPlayback(true);
return;
}
break;
case MediaStatus.PLAYER_STATE_UNKNOWN:
//is this right?
if (oldState == MediaStatus.PLAYER_STATE_PLAYING && oldMedia != null) {
callback.onPlaybackPause(oldMedia, position);
}
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
break;
default:
Log.e(TAG, "Remote media state undetermined!");
setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia);
Log.wtf(TAG, "Remote media state undetermined!");
}
if (updateUI) {
if (mediaChanged) {
callback.onMediaChanged(true);
if (oldMedia != null) {
callback.onPostPlayback(oldMedia, false, currentMedia != null);
}
}
}
@ -264,12 +304,13 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
if (!CastUtils.isCastable(playable)) {
Log.d(TAG, "media provided is not compatible with cast device");
callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH, R.string.cast_not_castable);
try {
playable.loadMetadata();
} catch (Playable.PlayableException e) {
Log.e(TAG, "Unable to load metadata of playable", e);
Playable nextPlayable = playable;
do {
nextPlayable = callback.getNextInQueue(nextPlayable);
} while (nextPlayable != null && !CastUtils.isCastable(nextPlayable));
if (nextPlayable != null) {
playMediaObject(nextPlayable, forceReset, stream, startWhenPrepared, prepareImmediately);
}
callback.endPlayback(playable, startWhenPrepared, true, false);
return;
}
@ -281,19 +322,21 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
return;
} else {
// set temporarily to pause in order to update list with current position
boolean isPlaying = playerStatus == PlayerStatus.PLAYING;
int position = media.getPosition();
try {
if (castMgr.isRemoteMediaPlaying()) {
setPlayerStatus(PlayerStatus.PAUSED, media);
}
isPlaying = castMgr.isRemoteMediaPlaying();
position = (int) castMgr.getCurrentMediaPosition();
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
Log.e(TAG, "Unable to determine whether media was playing, falling back to stored player status", e);
// this might end up just being pointless if we need to query the remote device for the position
if (playerStatus == PlayerStatus.PLAYING) {
setPlayerStatus(PlayerStatus.PAUSED, media);
}
}
smartMarkAsPlayed(media);
if (isPlaying) {
callback.onPlaybackPause(media, position);
}
if (!media.getIdentifier().equals(playable.getIdentifier())) {
final Playable oldMedia = media;
callback.onPostPlayback(oldMedia, false, true);
}
setPlayerStatus(PlayerStatus.INDETERMINATE, null);
}
@ -301,7 +344,6 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
this.media = playable;
remoteMedia = remoteVersion(playable);
//this.stream = stream;
this.mediaType = media.getMediaType();
this.startWhenPrepared.set(startWhenPrepared);
setPlayerStatus(PlayerStatus.INITIALIZING, media);
@ -328,8 +370,9 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
media.getPosition(),
media.getLastPlayedTime());
castMgr.play(newPosition);
} else {
castMgr.play();
}
castMgr.play();
} catch (CastException | TransientNetworkDisconnectionException | NoConnectionException e) {
Log.e(TAG, "Unable to resume remote playback", e);
}
@ -464,8 +507,8 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
this.startWhenPrepared.set(startWhenPrepared);
}
//TODO I believe some parts of the code make the same decision skipping this check, so that
//should be changed as well
// As things are right now, changing the return value of this function is not enough to ensure
// all other components recognize it.
@Override
public boolean canSetSpeed() {
return false;
@ -560,16 +603,39 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
protected void endPlayback(boolean wasSkipped) {
Log.d(TAG, "endPlayback() called");
boolean isPlaying = playerStatus == PlayerStatus.PLAYING;
try {
isPlaying = castMgr.isRemoteMediaPlaying();
} catch (TransientNetworkDisconnectionException | NoConnectionException e) {
Log.e(TAG, "Could not determine if media is playing", e);
}
// TODO make sure we stop playback whenever there's no next episode.
if (playerStatus != PlayerStatus.INDETERMINATE) {
setPlayerStatus(PlayerStatus.INDETERMINATE, media);
}
callback.endPlayback(media, isPlaying, wasSkipped, switchingPlayers);
if (media != null && wasSkipped) {
// current position only really matters when we skip
int position = getPosition();
if (position >= 0) {
media.setPosition(position);
}
}
final Playable currentMedia = media;
Playable nextMedia = callback.getNextInQueue(currentMedia);
boolean playNextEpisode = isPlaying && nextMedia != null && UserPreferences.isFollowQueue();
if (playNextEpisode) {
Log.d(TAG, "Playback of next episode will start immediately.");
} else if (nextMedia == null){
Log.d(TAG, "No more episodes available to play");
} else {
Log.d(TAG, "Loading next episode, but not playing automatically.");
}
if (nextMedia != null) {
callback.onPlaybackEnded(nextMedia.getMediaType(), !playNextEpisode);
// setting media to null signals to playMediaObject() that we're taking care of post-playback processing
media = null;
playMediaObject(nextMedia, false, true /*TODO for now we always stream*/, playNextEpisode, playNextEpisode);
} else {
callback.onPlaybackEnded(null, true);
stop();
}
callback.onPostPlayback(currentMedia, !wasSkipped, nextMedia != null);
}
@Override
@ -585,8 +651,4 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
protected boolean shouldLockWifi() {
return false;
}
private interface EndPlaybackCall {
boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers);
}
}