implement several remote playback commands
This commit is contained in:
parent
1ca0c1214f
commit
c4b6f366ca
@ -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.
|
||||
*/
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user