adapt media player switch into the new protocol

This commit is contained in:
Domingos Lopes 2016-05-13 01:33:05 -04:00
parent 385079d168
commit c17723816b
5 changed files with 124 additions and 63 deletions

View File

@ -13,6 +13,7 @@ import org.antennapod.audio.MediaPlayer;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -754,8 +755,8 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
@Override @Override
protected void endPlayback(final boolean wasSkipped) { protected Future<?> endPlayback(final boolean wasSkipped, final boolean shouldContinue, final boolean toStoppedState) {
executor.submit(() -> { return executor.submit(() -> {
playerLock.lock(); playerLock.lock();
releaseWifiLockIfNecessary(); releaseWifiLockIfNecessary();
@ -776,35 +777,46 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
mediaPlayer.reset(); mediaPlayer.reset();
} }
audioManager.abandonAudioFocus(audioFocusChangeListener); audioManager.abandonAudioFocus(audioFocusChangeListener);
// Load next episode if previous episode was in the queue and if there
// is an episode in the queue left.
// Start playback immediately if continuous playback is enabled
final Playable currentMedia = media; final Playable currentMedia = media;
Playable nextMedia = callback.getNextInQueue(currentMedia); Playable nextMedia = null;
boolean playNextEpisode = isPlaying && if (shouldContinue) {
nextMedia != null && // Load next episode if previous episode was in the queue and if there
UserPreferences.isFollowQueue(); // is an episode in the queue left.
// Start playback immediately if continuous playback is enabled
nextMedia = callback.getNextInQueue(currentMedia);
if (playNextEpisode) { boolean playNextEpisode = isPlaying &&
Log.d(TAG, "Playback of next episode will start immediately."); nextMedia != null &&
} else if (nextMedia == null){ UserPreferences.isFollowQueue();
Log.d(TAG, "No more episodes available to play");
} else { if (playNextEpisode) {
Log.d(TAG, "Loading next episode, but not playing automatically."); 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, !nextMedia.localFileAvailable(), playNextEpisode, playNextEpisode);
}
} }
if (shouldContinue || toStoppedState) {
if (nextMedia == null) {
callback.onPlaybackEnded(null, true);
stop();
}
final boolean hasNext = nextMedia != null;
if (nextMedia != null) { executor.submit(() -> callback.onPostPlayback(currentMedia, !wasSkipped, hasNext));
callback.onPlaybackEnded(nextMedia.getMediaType(), !playNextEpisode); } else if (isPlaying) {
// setting media to null signals to playMediaObject() that we're taking care of post-playback processing callback.onPlaybackPause(currentMedia, currentMedia.getPosition());
media = null;
playMediaObject(nextMedia, false, !nextMedia.localFileAvailable(), playNextEpisode, playNextEpisode);
} else {
callback.onPlaybackEnded(null, true);
stop();
} }
executor.submit(() -> callback.onPostPlayback(currentMedia, !wasSkipped, nextMedia != null));
playerLock.unlock(); playerLock.unlock();
}); });
} }
@ -815,8 +827,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
* This method will only take care of changing the PlayerStatus of this object! Other tasks like * This method will only take care of changing the PlayerStatus of this object! Other tasks like
* abandoning audio focus have to be done with other methods. * abandoning audio focus have to be done with other methods.
*/ */
@Override private void stop() {
public void stop() {
executor.submit(() -> { executor.submit(() -> {
playerLock.lock(); playerLock.lock();
releaseWifiLockIfNecessary(); releaseWifiLockIfNecessary();
@ -869,7 +880,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
mp -> genericOnCompletion(); mp -> genericOnCompletion();
private void genericOnCompletion() { private void genericOnCompletion() {
endPlayback(false); endPlayback(false, true, true);
} }
private final MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = private final MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener =

View File

@ -786,6 +786,11 @@ public class PlaybackService extends MediaBrowserServiceCompat {
if (!(playable instanceof FeedMedia)) { if (!(playable instanceof FeedMedia)) {
Log.d(TAG, "Not doing post-playback processing: media not of type FeedMedia"); Log.d(TAG, "Not doing post-playback processing: media not of type FeedMedia");
if (ended) {
playable.onPlaybackCompleted(getApplicationContext());
} else {
playable.onPlaybackPause(getApplicationContext());
}
return; return;
} }
FeedMedia media = (FeedMedia) playable; FeedMedia media = (FeedMedia) playable;
@ -1587,7 +1592,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override @Override
public void onStop() { public void onStop() {
Log.d(TAG, "onStop()"); Log.d(TAG, "onStop()");
mediaPlayer.stop(); mediaPlayer.stopPlayback(true);
} }
@Override @Override

View File

@ -8,6 +8,8 @@ import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import java.util.concurrent.Future;
import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.Playable;
@ -226,18 +228,43 @@ public abstract class PlaybackServiceMediaPlayer {
protected abstract void setPlayable(Playable playable); protected abstract void setPlayable(Playable playable);
public void skip() { public void skip() {
endPlayback(true); endPlayback(true, true, true);
} }
protected abstract void endPlayback(boolean wasSkipped); /**
* Ends playback of current media (if any) and moves into INDETERMINATE state, unless
* {@param toStoppedState} is set to true, in which case it moves into STOPPED state.
*
* @see #endPlayback(boolean, boolean, boolean)
*/
public Future<?> stopPlayback(boolean toStoppedState) {
return endPlayback(true, false, toStoppedState);
}
/** /**
* Moves the PSMP into STOPPED state. This call is only valid if the player is currently in * Internal method that handles end of playback.
* INDETERMINATE state, for example after a call to endPlayback. *
* This method will only take care of changing the PlayerStatus of this object! Other tasks like * Currently, it has 4 use cases:
* abandoning audio focus have to be done with other methods. * <ul>
* <li>Media playback has completed: call with (false, true, true)</li>
* <li>User asks to skip to next episode: call with (true, true, true)</li>
* <li>Stopping the media player: call with (true, false, true)</li>
* <li>We want to change the media player implementation: call with (true, false, false)</li>
* </ul>
*
* @param wasSkipped If true, we assume the current media's playback has ended, for
* purposes of post playback processing.
* @param shouldContinue If true, the media player should try to load, and possibly play,
* the next item, based on the user preferences and whether such item
* exists.
* @param toStoppedState If true, the playback state gets set to STOPPED if the media player
* is not loading/playing after this call, and the UI will reflect that.
* Only relevant if {@param shouldContinue} is set to false, otherwise
* this method's behavior defaults as if this parameter was true.
*
* @return a Future, just for the purpose of tracking its execution.
*/ */
public abstract void stop(); protected abstract Future<?> endPlayback(boolean wasSkipped, boolean shouldContinue, boolean toStoppedState);
/** /**
* @return {@code true} if the WifiLock feature should be used, {@code false} otherwise. * @return {@code true} if the WifiLock feature should be used, {@code false} otherwise.

View File

@ -15,6 +15,8 @@ import android.widget.Toast;
import com.google.android.gms.cast.ApplicationMetadata; import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager; import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager;
import java.util.concurrent.ExecutionException;
import de.danoeh.antennapod.core.cast.CastConsumer; import de.danoeh.antennapod.core.cast.CastConsumer;
import de.danoeh.antennapod.core.cast.CastManager; import de.danoeh.antennapod.core.cast.CastManager;
import de.danoeh.antennapod.core.cast.DefaultCastConsumer; import de.danoeh.antennapod.core.cast.DefaultCastConsumer;
@ -182,8 +184,11 @@ public class PlaybackServiceFlavorHelper {
boolean wasLaunched) { boolean wasLaunched) {
PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer(); PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer();
if (mediaPlayer != null) { if (mediaPlayer != null) {
//TODO change implementation to new protocol try {
// mediaPlayer.endPlayback(true, true); mediaPlayer.stopPlayback(false).get();
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "There was a problem stopping playback while switching media players", e);
}
mediaPlayer.shutdownQuietly(); mediaPlayer.shutdownQuietly();
} }
mediaPlayer = newPlayer; mediaPlayer = newPlayer;

View File

@ -15,6 +15,8 @@ import com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastEx
import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException; import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException;
import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException; import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.R;
@ -121,7 +123,7 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
} }
if (playbackEnded) { if (playbackEnded) {
// This is an unconventional thing to occur... // This is an unconventional thing to occur...
endPlayback(true); endPlayback(true, true, true);
} }
} }
@ -259,13 +261,13 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
if (mediaChanged && currentMedia != null) { if (mediaChanged && currentMedia != null) {
media = currentMedia; media = currentMedia;
} }
endPlayback(false); endPlayback(false, true, true);
return; return;
case MediaStatus.IDLE_REASON_ERROR: case MediaStatus.IDLE_REASON_ERROR:
Log.w(TAG, "Got an error status from the Chromecast. Skipping, if possible, to the next episode..."); Log.w(TAG, "Got an error status from the Chromecast. Skipping, if possible, to the next episode...");
callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH, callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH,
R.string.cast_failed_media_error_skipping); R.string.cast_failed_media_error_skipping);
endPlayback(true); endPlayback(true, true, true);
return; return;
} }
break; break;
@ -597,7 +599,7 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
} }
@Override @Override
protected void endPlayback(boolean wasSkipped) { protected Future<?> endPlayback(boolean wasSkipped, boolean shouldContinue, boolean toStoppedState) {
Log.d(TAG, "endPlayback() called"); Log.d(TAG, "endPlayback() called");
boolean isPlaying = playerStatus == PlayerStatus.PLAYING; boolean isPlaying = playerStatus == PlayerStatus.PLAYING;
if (playerStatus != PlayerStatus.INDETERMINATE) { if (playerStatus != PlayerStatus.INDETERMINATE) {
@ -611,32 +613,43 @@ public class RemotePSMP extends PlaybackServiceMediaPlayer {
} }
} }
final Playable currentMedia = media; final Playable currentMedia = media;
Playable nextMedia = callback.getNextInQueue(currentMedia); Playable nextMedia = null;
if (shouldContinue) {
nextMedia = callback.getNextInQueue(currentMedia);
boolean playNextEpisode = isPlaying && nextMedia != null && UserPreferences.isFollowQueue(); boolean playNextEpisode = isPlaying && nextMedia != null && UserPreferences.isFollowQueue();
if (playNextEpisode) { if (playNextEpisode) {
Log.d(TAG, "Playback of next episode will start immediately."); Log.d(TAG, "Playback of next episode will start immediately.");
} else if (nextMedia == null){ } else if (nextMedia == null){
Log.d(TAG, "No more episodes available to play"); Log.d(TAG, "No more episodes available to play");
} else { } else {
Log.d(TAG, "Loading next episode, but not playing automatically."); 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);
}
}
if (shouldContinue || toStoppedState) {
if (nextMedia != null) {
callback.onPlaybackEnded(null, true);
stop();
}
callback.onPostPlayback(currentMedia, !wasSkipped, nextMedia != null);
} else if (isPlaying) {
callback.onPlaybackPause(currentMedia,
currentMedia != null ? currentMedia.getPosition() : INVALID_TIME);
} }
if (nextMedia != null) { FutureTask<?> future = new FutureTask<>(() -> {}, null);
callback.onPlaybackEnded(nextMedia.getMediaType(), !playNextEpisode); future.run();
// setting media to null signals to playMediaObject() that we're taking care of post-playback processing return future;
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 private void stop() {
public void stop() {
if (playerStatus == PlayerStatus.INDETERMINATE) { if (playerStatus == PlayerStatus.INDETERMINATE) {
setPlayerStatus(PlayerStatus.STOPPED, null); setPlayerStatus(PlayerStatus.STOPPED, null);
} else { } else {