From c4b6f366ca2b8e731f87f0d4423c7e02185289f8 Mon Sep 17 00:00:00 2001 From: Domingos Lopes Date: Sat, 26 Mar 2016 17:12:50 -0400 Subject: implement several remote playback commands --- .../core/service/playback/LocalPSMP.java | 9 - .../playback/PlaybackServiceMediaPlayer.java | 4 +- .../core/service/playback/RemotePSMP.java | 232 +++++++++++---------- 3 files changed, 130 insertions(+), 115 deletions(-) (limited to 'core/src/main/java/de') diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java index 379875478..684c54d7f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java @@ -19,7 +19,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; -import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.RewindAfterPauseUtils; @@ -424,14 +423,6 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { }); } - /** - * Seek to the start of the specified chapter. - */ - @Override - public void seekToChapter(@NonNull Chapter c) { - seekTo((int) c.getStart()); - } - /** * Returns the duration of the current media object or INVALID_TIME if the duration could not be retrieved. */ diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java index 7570e7db0..13eedfba2 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java @@ -131,7 +131,9 @@ public abstract class PlaybackServiceMediaPlayer { /** * Seek to the start of the specified chapter. */ - public abstract void seekToChapter(@NonNull Chapter c); + public void seekToChapter(@NonNull Chapter c) { + seekTo((int) c.getStart()); + } /** * Returns the duration of the current media object or INVALID_TIME if the duration could not be retrieved. diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java index 4ae777e65..81da8cf3d 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java @@ -17,15 +17,15 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.ReentrantLock; import de.danoeh.antennapod.core.cast.CastConsumer; import de.danoeh.antennapod.core.cast.CastConsumerImpl; import de.danoeh.antennapod.core.cast.CastManager; -import de.danoeh.antennapod.core.feed.Chapter; 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.CastUtils; +import de.danoeh.antennapod.core.util.RewindAfterPauseUtils; import de.danoeh.antennapod.core.util.playback.Playable; /** @@ -49,7 +49,7 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer { * Some asynchronous calls might change the state of the MediaPlayer object. Therefore calls in other threads * have to wait until these operations have finished. */ - private final ReentrantLock playerLock; + //private final ReentrantLock playerLock; private final ThreadPoolExecutor executor; public RemotePSMP(@NonNull Context context, @NonNull PSMPCallback callback) { @@ -61,7 +61,7 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer { mediaType = null; this.startWhenPrepared = new AtomicBoolean(false); - playerLock = new ReentrantLock(); + //playerLock = new ReentrantLock(); executor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.MINUTES, new LinkedBlockingDeque<>(), (r, executor) -> Log.d(TAG, "Rejected execution of runnable")); @@ -124,8 +124,16 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer { public void onMediaLoadResult(int statusCode) { if (playerStatus == PlayerStatus.PREPARING) { if (statusCode == CastStatusCodes.SUCCESS) { - executor.execute(RemotePSMP.this::onPrepared); - } else { + setPlayerStatus(PlayerStatus.PREPARED, media); + if (media.getDuration() == 0) { + Log.d(TAG, "Setting duration of media"); + try { + media.setDuration((int) castMgr.getMediaDuration()); + } catch (TransientNetworkDisconnectionException | NoConnectionException e) { + Log.e(TAG, "Unable to get remote media's duration"); + } + } + } else if (statusCode != CastStatusCodes.REPLACED){ Log.d(TAG, "Remote media failed to load"); setPlayerStatus(PlayerStatus.INITIALIZED, media); } @@ -144,27 +152,19 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer { @Override public void playMediaObject(@NonNull final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) { Log.d(TAG, "playMediaObject() called"); - executor.execute(() -> { - playerLock.lock(); - try { - playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately); - } finally { - playerLock.unlock(); - } - }); + playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately); } /** * Internal implementation of playMediaObject. This method has an additional parameter that allows the caller to force a media player reset even if * the given playable parameter is the same object as the currently playing media. - *

- * This method requires the playerLock and is executed on the caller's thread. * * @see #playMediaObject(de.danoeh.antennapod.core.util.playback.Playable, boolean, boolean, boolean) */ private void playMediaObject(@NonNull final Playable playable, final boolean forceReset, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) { - if (!playerLock.isHeldByCurrentThread()) { - throw new IllegalStateException("method requires playerLock"); + if (!CastUtils.isCastable(playable)) { + Log.d(TAG, "media provided is not compatible with cast device"); + return; } if (media != null) { @@ -193,53 +193,71 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer { } } - if (CastUtils.isCastable(playable)) { - this.media = playable; - remoteMedia = CastUtils.convertFromFeedMedia((FeedMedia) media); - //this.stream = stream; - this.mediaType = media.getMediaType(); - this.startWhenPrepared.set(startWhenPrepared); - setPlayerStatus(PlayerStatus.INITIALIZING, media); - try { - media.loadMetadata(); - executor.execute(() -> updateMediaSessionMetadata(media)); - setPlayerStatus(PlayerStatus.INITIALIZED, media); - if (prepareImmediately) { - prepareSync(); - } - } catch (Playable.PlayableException e) { - Log.e(TAG, "Error while loading media metadata", e); - setPlayerStatus(PlayerStatus.STOPPED, null); + this.media = playable; + remoteMedia = CastUtils.convertFromFeedMedia((FeedMedia) media); + //this.stream = stream; + this.mediaType = media.getMediaType(); + this.startWhenPrepared.set(startWhenPrepared); + setPlayerStatus(PlayerStatus.INITIALIZING, media); + try { + media.loadMetadata(); + executor.execute(() -> callback.updateMediaSessionMetadata(media)); + setPlayerStatus(PlayerStatus.INITIALIZED, media); + if (prepareImmediately) { + prepare(); } + } catch (Playable.PlayableException e) { + Log.e(TAG, "Error while loading media metadata", e); + setPlayerStatus(PlayerStatus.STOPPED, null); } } @Override public void resume() { - //TODO + try { + setVolume(UserPreferences.getLeftVolume(), UserPreferences.getRightVolume()); + if (playerStatus == PlayerStatus.PREPARED && media.getPosition() > 0) { + int newPosition = RewindAfterPauseUtils.calculatePositionWithRewind( + media.getPosition(), + media.getLastPlayedTime()); + castMgr.play(newPosition); + } + castMgr.play(); + } catch (CastException | TransientNetworkDisconnectionException | NoConnectionException e) { + Log.e(TAG, "Unable to resume remote playback", e); + } } @Override public void pause(boolean abandonFocus, boolean reinit) { - //TODO + boolean playing = true; + try { + playing = castMgr.isRemoteMediaPlaying(); + if (playing) { + castMgr.pause(); + } + } catch (CastException | TransientNetworkDisconnectionException | NoConnectionException e) { + Log.e(TAG, "Unable to pause", e); + } + if (playing && reinit) { + reinit(); + } } @Override public void prepare() { - executor.submit( () -> { - playerLock.lock(); - prepareSync(); - playerLock.unlock(); - - }); - } - - private void prepareSync() { if (playerStatus == PlayerStatus.INITIALIZED) { Log.d(TAG, "Preparing media player"); setPlayerStatus(PlayerStatus.PREPARING, media); try { - castMgr.loadMedia(remoteMedia, startWhenPrepared.get(), media.getPosition()); + int position = media.getPosition(); + if (position > 0) { + position = RewindAfterPauseUtils.calculatePositionWithRewind( + position, + media.getLastPlayedTime()); + } + setVolume(UserPreferences.getLeftVolume(), UserPreferences.getRightVolume()); + castMgr.loadMedia(remoteMedia, startWhenPrepared.get(), position); } catch (TransientNetworkDisconnectionException | NoConnectionException e) { Log.e(TAG, "Error loading media", e); setPlayerStatus(PlayerStatus.INITIALIZED, media); @@ -247,66 +265,68 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer { } } - /** - * Called after media player has been prepared. This method is executed on the caller's thread. - */ - void onPrepared() { - playerLock.lock(); - - if (playerStatus != PlayerStatus.PREPARING) { - playerLock.unlock(); - Log.w(TAG, "onPrepared() called, but player is not in PREPARING state anymore"); - return; - } - Log.d(TAG, "Resource loaded"); - if (media.getDuration() == 0) { - Log.d(TAG, "Setting duration of media"); - try { - media.setDuration((int) castMgr.getMediaDuration()); - } catch (TransientNetworkDisconnectionException | NoConnectionException e) { - Log.e(TAG, "Unable to get remote media's duration"); - } - } - setPlayerStatus(PlayerStatus.PREPARED, media); - playerLock.unlock(); - } - @Override public void reinit() { - //TODO + if (media != null) { + playMediaObject(media, true, false, startWhenPrepared.get(), false); + } else { + Log.d(TAG, "Call to reinit was ignored: media was null"); + } } @Override public void seekTo(int t) { - //TODO + //TODO check other seek implementations and see if there's no issue with sending too many seek commands to the remote media player + try { + if (castMgr.isRemoteMediaLoaded()) { + setPlayerStatus(PlayerStatus.SEEKING, media); + castMgr.seek(t); + } else if (media != null && playerStatus == PlayerStatus.INITIALIZED){ + media.setPosition(t); + startWhenPrepared.set(false); + prepare(); + } + } catch (TransientNetworkDisconnectionException | NoConnectionException e) { + Log.e(TAG, "Unable to seek", e); + } } @Override public void seekDelta(int d) { - //TODO - } - - @Override - public void seekToChapter(@NonNull Chapter c) { - //TODO + int position = getPosition(); + if (position != INVALID_TIME) { + seekTo(position + d); + } else { + Log.e(TAG, "getPosition() returned INVALID_TIME in seekDelta"); + } } @Override public int getDuration() { - //TODO - return 0; - } - - @Override - public int getPosition() { + int retVal = INVALID_TIME; + boolean prepared; try { - if (!playerLock.tryLock(50, TimeUnit.MILLISECONDS)) { - return INVALID_TIME; + prepared = castMgr.isRemoteMediaLoaded(); + } catch (TransientNetworkDisconnectionException | NoConnectionException e) { + Log.e(TAG, "Unable to check if remote media is loaded", e); + prepared = playerStatus.isAtLeast(PlayerStatus.PREPARED); + } + if (prepared) { + try { + retVal = (int) castMgr.getMediaDuration(); + } catch (TransientNetworkDisconnectionException | NoConnectionException e) { + Log.e(TAG, "Unable to determine remote media's duration", e); } - } catch (InterruptedException e) { - return INVALID_TIME; } + if(retVal == INVALID_TIME && media != null && media.getDuration() > 0) { + retVal = media.getDuration(); + } + Log.d(TAG, "getDuration() -> " + retVal); + return retVal; + } + @Override + public int getPosition() { int retVal = INVALID_TIME; boolean prepared; try { @@ -322,24 +342,21 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer { Log.e(TAG, "Unable to determine remote media's position", e); } } - if(retVal <= 0 && media != null && media.getPosition() > 0) { + if(retVal <= 0 && media != null && media.getPosition() >= 0) { retVal = media.getPosition(); } - - playerLock.unlock(); Log.d(TAG, "getPosition() -> " + retVal); return retVal; } @Override public boolean isStartWhenPrepared() { - //TODO - return false; + return startWhenPrepared.get(); } @Override public void setStartWhenPrepared(boolean startWhenPrepared) { - //TODO + this.startWhenPrepared.set(startWhenPrepared); } //TODO I believe some parts of the code make the same decision skipping this check, so that @@ -389,8 +406,7 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer { @Override public MediaType getCurrentMediaType() { - //TODO - return null; + return mediaType; } @Override @@ -402,28 +418,26 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer { public void shutdown() { castMgr.removeCastConsumer(castConsumer); executor.shutdown(); - //TODO } @Override public void shutdownAsync() { - //TODO - this.shutdown(); + executor.execute(this::shutdown); + executor.shutdown(); } @Override public void setVideoSurface(SurfaceHolder surface) { - //TODO + throw new UnsupportedOperationException("Setting Video Surface unsupported in Remote Media Player"); } @Override public void resetVideoSurface() { - //TODO + throw new UnsupportedOperationException("Resetting Video Surface unsupported in Remote Media Player"); } @Override public Pair getVideoSize() { - //TODO return null; } @@ -443,12 +457,20 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer { @Override public void endPlayback(boolean wasSkipped, boolean switchingPlayers) { - //TODO + boolean isPlaying = playerStatus == PlayerStatus.PLAYING; + if (playerStatus != PlayerStatus.INDETERMINATE) { + setPlayerStatus(PlayerStatus.INDETERMINATE, media); + } + callback.endPlayback(isPlaying, wasSkipped, switchingPlayers); } @Override public void stop() { - //TODO + if (playerStatus == PlayerStatus.INDETERMINATE) { + setPlayerStatus(PlayerStatus.STOPPED, null); + } else { + Log.d(TAG, "Ignored call to stop: Current player state is: " + playerStatus); + } } @Override -- cgit v1.2.3