diff options
author | Domingos Lopes <domingos86lopes+github@gmail.com> | 2016-05-08 01:05:09 -0400 |
---|---|---|
committer | Domingos Lopes <domingos86lopes+github@gmail.com> | 2016-07-25 23:16:28 -0400 |
commit | 9b123d04737fa9a1305b0e6827c768396ec31ca0 (patch) | |
tree | 237d0bc8bb588b977caf37d0e1775e43fd2c36a7 /core/src/play | |
parent | 19a647226d3682a1a0acc88c722c6512692f4754 (diff) | |
download | AntennaPod-9b123d04737fa9a1305b0e6827c768396ec31ca0.zip |
extend the new PlaybackService media player callback protocol to RemotePSMP
Diffstat (limited to 'core/src/play')
-rw-r--r-- | core/src/play/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java | 214 |
1 files changed, 138 insertions, 76 deletions
diff --git a/core/src/play/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java b/core/src/play/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java index c051cc435..8982e85f3 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java +++ b/core/src/play/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java @@ -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); - } } |