implement several remote playback commands

This commit is contained in:
Domingos Lopes 2016-03-26 17:12:50 -04:00
parent 1ca0c1214f
commit c4b6f366ca
3 changed files with 130 additions and 115 deletions

View File

@ -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.
*/

View File

@ -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.

View File

@ -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.
* <p/>
* 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;
int retVal = INVALID_TIME;
boolean prepared;
try {
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);
}
}
if(retVal == INVALID_TIME && media != null && media.getDuration() > 0) {
retVal = media.getDuration();
}
Log.d(TAG, "getDuration() -> " + retVal);
return retVal;
}
@Override
public int getPosition() {
try {
if (!playerLock.tryLock(50, TimeUnit.MILLISECONDS)) {
return INVALID_TIME;
}
} catch (InterruptedException e) {
return INVALID_TIME;
}
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<Integer, Integer> 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