change protocol for episode transition on PlaybackService

This commit is contained in:
Domingos Lopes 2016-04-30 01:50:35 -04:00
parent 8e281890dd
commit e94219ce26
4 changed files with 313 additions and 148 deletions

View File

@ -20,6 +20,7 @@ import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences; import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer; import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer;
import de.danoeh.antennapod.core.service.playback.LocalPSMP; import de.danoeh.antennapod.core.service.playback.LocalPSMP;
import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.service.playback.PlayerStatus;
@ -196,10 +197,19 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
} }
@Override @Override
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { public void onPostPlayback(Playable media, boolean ended, boolean playingNext) {
return false;
} }
@Override
public Playable getNextInQueue(Playable currentMedia) {
return null;
}
@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
}
}; };
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null); Playable p = writeTestPlayable(PLAYABLE_FILE_URL, null);
@ -275,8 +285,18 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
} }
@Override @Override
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { public void onPostPlayback(Playable media, boolean ended, boolean playingNext) {
return false;
}
@Override
public Playable getNextInQueue(Playable currentMedia) {
return null;
}
@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
} }
}; };
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
@ -357,8 +377,18 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
} }
@Override @Override
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { public void onPostPlayback(Playable media, boolean ended, boolean playingNext) {
return false;
}
@Override
public Playable getNextInQueue(Playable currentMedia) {
return null;
}
@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
} }
}; };
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
@ -440,8 +470,18 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
} }
@Override @Override
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { public void onPostPlayback(Playable media, boolean ended, boolean playingNext) {
return false;
}
@Override
public Playable getNextInQueue(Playable currentMedia) {
return null;
}
@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
} }
}; };
@ -517,8 +557,18 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
} }
@Override @Override
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { public void onPostPlayback(Playable media, boolean ended, boolean playingNext) {
return false;
}
@Override
public Playable getNextInQueue(Playable currentMedia) {
return null;
}
@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
} }
}; };
@ -595,8 +645,18 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
} }
@Override @Override
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { public void onPostPlayback(Playable media, boolean ended, boolean playingNext) {
return false;
}
@Override
public Playable getNextInQueue(Playable currentMedia) {
return null;
}
@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
} }
}; };
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
@ -675,8 +735,18 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
} }
@Override @Override
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { public void onPostPlayback(Playable media, boolean ended, boolean playingNext) {
return false;
}
@Override
public Playable getNextInQueue(Playable currentMedia) {
return null;
}
@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
} }
}; };
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
@ -758,8 +828,18 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
} }
@Override @Override
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { public void onPostPlayback(Playable media, boolean ended, boolean playingNext) {
return false;
}
@Override
public Playable getNextInQueue(Playable currentMedia) {
return null;
}
@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
} }
}; };
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
@ -814,8 +894,18 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
} }
@Override @Override
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { public void onPostPlayback(Playable media, boolean ended, boolean playingNext) {
return false;
}
@Override
public Playable getNextInQueue(Playable currentMedia) {
return null;
}
@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
} }
}; };
@ -896,8 +986,18 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
} }
@Override @Override
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { public void onPostPlayback(Playable media, boolean ended, boolean playingNext) {
return false;
}
@Override
public Playable getNextInQueue(Playable currentMedia) {
return null;
}
@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
} }
}; };
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
@ -1012,8 +1112,18 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
} }
@Override @Override
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { public void onPostPlayback(Playable media, boolean ended, boolean playingNext) {
return false;
}
@Override
public Playable getNextInQueue(Playable currentMedia) {
return null;
}
@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
} }
}; };
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
@ -1103,8 +1213,18 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
} }
@Override @Override
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { public void onPostPlayback(Playable media, boolean ended, boolean playingNext) {
return false;
}
@Override
public Playable getNextInQueue(Playable currentMedia) {
return null;
}
@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
} }
}; };
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
@ -1207,8 +1327,18 @@ public class PlaybackServiceMediaPlayerTest extends InstrumentationTestCase {
} }
@Override @Override
public boolean endPlayback(Playable p, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { public void onPostPlayback(Playable media, boolean ended, boolean playingNext) {
return false;
}
@Override
public Playable getNextInQueue(Playable currentMedia) {
return null;
}
@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
} }
}; };
PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);

View File

@ -143,7 +143,10 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
setPlayerStatus(PlayerStatus.PAUSED, media); setPlayerStatus(PlayerStatus.PAUSED, media);
} }
smartMarkAsPlayed(media); if (!media.getIdentifier().equals(playable.getIdentifier())) {
final Playable oldMedia = media;
executor.submit(() -> callback.onPostPlayback(oldMedia, false, true));
}
setPlayerStatus(PlayerStatus.INDETERMINATE, null); setPlayerStatus(PlayerStatus.INDETERMINATE, null);
} }
@ -762,13 +765,47 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
if (playerStatus != PlayerStatus.INDETERMINATE) { if (playerStatus != PlayerStatus.INDETERMINATE) {
setPlayerStatus(PlayerStatus.INDETERMINATE, media); setPlayerStatus(PlayerStatus.INDETERMINATE, media);
} }
// we're relying on the position stored in the Playable object for post-playback processing
if (media != null) {
int position = getPosition();
if (position >= 0) {
media.setPosition(position);
}
}
if (mediaPlayer != null) { if (mediaPlayer != null) {
mediaPlayer.reset(); mediaPlayer.reset();
} }
audioManager.abandonAudioFocus(audioFocusChangeListener); audioManager.abandonAudioFocus(audioFocusChangeListener);
callback.endPlayback(media, isPlaying, wasSkipped, switchingPlayers); // 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;
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, !nextMedia.localFileAvailable(), playNextEpisode, playNextEpisode);
} else {
callback.onPlaybackEnded(null, true);
stop();
}
executor.submit(() -> callback.onPostPlayback(currentMedia, !wasSkipped, nextMedia != null));
playerLock.unlock(); playerLock.unlock();
}); });
} }

View File

@ -700,121 +700,138 @@ public class PlaybackService extends MediaBrowserServiceCompat {
} }
@Override @Override
public boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { public void onPostPlayback(Playable media, boolean ended, boolean playingNext) {
PlaybackService.this.endPlayback(media, playNextEpisode, wasSkipped, switchingPlayers); PlaybackService.this.onPostPlayback(media, ended, playingNext);
return true; }
@Override
public Playable getNextInQueue(Playable currentMedia) {
return PlaybackService.this.getNextInQueue(currentMedia);
}
@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
PlaybackService.this.onPlaybackEnded(mediaType, stopPlaying);
} }
}; };
private void endPlayback(final Playable playable, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { private Playable getNextInQueue(final Playable currentMedia) {
Log.d(TAG, "Playback ended" + (switchingPlayers ? " from switching players": "")); if (!(currentMedia instanceof FeedMedia)) {
Log.d(TAG, "getNextInQueue(), but playable not an instance of FeedMedia, so not proceeding");
return null;
}
if (!ClientConfig.playbackServiceCallbacks.useQueue()) {
Log.d(TAG, "getNextInQueue(), but queue not in use by this app");
return null;
}
Log.d(TAG, "getNextInQueue()");
FeedMedia media = (FeedMedia) currentMedia;
try {
media.loadMetadata();
} catch (Playable.PlayableException e) {
Log.e(TAG, "Unable to load metadata to get next in queue", e);
return null;
}
FeedItem item = media.getItem();
if (item == null) {
Log.w(TAG, "getNextInQueue() with FeedMedia object whose FeedItem is null");
return null;
}
FeedItem nextItem;
try {
final List<FeedItem> queue = taskManager.getQueue();
nextItem = DBTasks.getQueueSuccessorOfItem(item.getId(), queue);
} catch (InterruptedException e) {
Log.e(TAG, "Error handling the queue in order to retrieve the next item", e);
return null;
}
return (nextItem != null)? nextItem.getMedia() : null;
}
/**
* Set of instructions to be performed when playback ends.
*/
private void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
Log.d(TAG, "Playback ended");
if (stopPlaying) {
taskManager.cancelPositionSaver();
writePlaybackPreferencesNoMediaPlaying();
if (!isCasting) {
stopForeground(true);
}
stopWidgetUpdater();
}
if (mediaType == null) {
sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
} else {
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
isCasting ? EXTRA_CODE_CAST :
(mediaType == MediaType.VIDEO) ? EXTRA_CODE_VIDEO : EXTRA_CODE_AUDIO);
}
}
//TODO add javadoc, that is is assumed that the playable object contains info about its position and duration
private void onPostPlayback(final Playable playable, boolean ended, boolean playingNext) {
if (playable == null) { if (playable == null) {
Log.e(TAG, "Cannot end playback: media was null"); Log.e(TAG, "Cannot do post-playback processing: media was null");
return; return;
} }
Log.d(TAG, "onPostPlayback(): media=" + playable.getEpisodeTitle());
taskManager.cancelPositionSaver(); if (!(playable instanceof FeedMedia)) {
Log.d(TAG, "Not doing post-playback processing: media not of type FeedMedia");
return;
}
FeedMedia media = (FeedMedia) playable;
FeedItem item = media.getItem();
boolean smartMarkAsPlayed = playingNext && media.hasAlmostEnded();
if (!ended && smartMarkAsPlayed) {
Log.d(TAG, "smart mark as played");
}
boolean isInQueue = false; // auto-flattr if enabled
FeedItem nextItem = null; if (isAutoFlattrable(media) && UserPreferences.getAutoFlattrPlayedDurationThreshold() == 1.0f) {
DBTasks.flattrItemIfLoggedIn(PlaybackService.this, item);
}
if (playable instanceof FeedMedia && ((FeedMedia) playable).getItem() != null) { // gpodder play action
FeedMedia media = (FeedMedia) playable; if (GpodnetPreferences.loggedIn()) {
FeedItem item = media.getItem(); GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY)
.currentDeviceId()
.currentTimestamp()
.started(startPosition / 1000)
.position(((ended || smartMarkAsPlayed) ? media.getDuration() : media.getPosition()) / 1000)
.total(media.getDuration() / 1000)
.build();
GpodnetPreferences.enqueueEpisodeAction(action);
}
if (!switchingPlayers) { if (item != null) {
if (ended || smartMarkAsPlayed ||
!UserPreferences.shouldSkipKeepEpisode()) {
// only mark the item as played if we're not keeping it anyways
DBWriter.markItemPlayed(item, FeedItem.PLAYED, ended);
try { try {
final List<FeedItem> queue = taskManager.getQueue(); final List<FeedItem> queue = taskManager.getQueue();
isInQueue = QueueAccess.ItemListAccess(queue).contains(item.getId()); if (QueueAccess.ItemListAccess(queue).contains(item.getId())) {
nextItem = DBTasks.getQueueSuccessorOfItem(item.getId(), queue); // don't know if it actually matters to not autodownload when smart mark as played is triggered
DBWriter.removeQueueItem(PlaybackService.this, item, ended);
}
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
// isInQueue remains false // isInQueue remains false
} }
// Delete episode if enabled
boolean shouldKeep = wasSkipped && UserPreferences.shouldSkipKeepEpisode(); if (item.getFeed().getPreferences().getCurrentAutoDelete()) {
DBWriter.deleteFeedMediaOfItem(PlaybackService.this, media.getId());
if (!shouldKeep) { Log.d(TAG, "Episode Deleted");
// only mark the item as played if we're not keeping it anyways
DBWriter.markItemPlayed(item, FeedItem.PLAYED, true);
if (isInQueue) {
DBWriter.removeQueueItem(PlaybackService.this, item, true);
}
// Delete episode if enabled
if (item.getFeed().getPreferences().getCurrentAutoDelete()) {
DBWriter.deleteFeedMediaOfItem(PlaybackService.this, media.getId());
Log.d(TAG, "Episode Deleted");
}
} }
} }
DBWriter.addItemToPlaybackHistory(media);
// auto-flattr if enabled
if (isAutoFlattrable(media) && UserPreferences.getAutoFlattrPlayedDurationThreshold() == 1.0f) {
DBTasks.flattrItemIfLoggedIn(PlaybackService.this, item);
}
// gpodder play action
if(GpodnetPreferences.loggedIn()) {
GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY)
.currentDeviceId()
.currentTimestamp()
.started(startPosition / 1000)
.position(getDuration() / 1000)
.total(getDuration() / 1000)
.build();
GpodnetPreferences.enqueueEpisodeAction(action);
}
} }
if (!switchingPlayers) { if (ended || playingNext) {
// Load next episode if previous episode was in the queue and if there DBWriter.addItemToPlaybackHistory(media);
// is an episode in the queue left.
// Start playback immediately if continuous playback is enabled
Playable nextMedia = null;
boolean loadNextItem = ClientConfig.playbackServiceCallbacks.useQueue() &&
isInQueue &&
nextItem != null;
playNextEpisode = playNextEpisode &&
loadNextItem &&
UserPreferences.isFollowQueue();
if (loadNextItem) {
Log.d(TAG, "Loading next item in queue");
nextMedia = nextItem.getMedia();
}
final boolean prepareImmediately;
final boolean startWhenPrepared;
final boolean stream;
if (playNextEpisode) {
Log.d(TAG, "Playback of next episode will start immediately.");
prepareImmediately = startWhenPrepared = true;
} else {
Log.d(TAG, "No more episodes available to play");
prepareImmediately = startWhenPrepared = false;
stopForeground(true);
stopWidgetUpdater();
}
writePlaybackPreferencesNoMediaPlaying();
if (nextMedia != null) {
stream = !nextMedia.localFileAvailable();
mediaPlayer.playMediaObject(nextMedia, stream, startWhenPrepared, prepareImmediately);
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
isCasting ? EXTRA_CODE_CAST :
(nextMedia.getMediaType() == MediaType.VIDEO) ? EXTRA_CODE_VIDEO : EXTRA_CODE_AUDIO);
} else {
sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
mediaPlayer.stop();
//stopSelf();
}
} }
} }

View File

@ -9,10 +9,7 @@ import android.util.Pair;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.Playable;
@ -291,26 +288,6 @@ public abstract class PlaybackServiceMediaPlayer {
callback.statusChanged(new PSMPInfo(playerStatus, getPlayable())); callback.statusChanged(new PSMPInfo(playerStatus, getPlayable()));
} }
protected void smartMarkAsPlayed(Playable media) {
if(media != null && media instanceof FeedMedia) {
FeedMedia oldMedia = (FeedMedia) media;
if(oldMedia.hasAlmostEnded()) {
Log.d(TAG, "smart mark as read");
FeedItem item = oldMedia.getItem();
if (item == null) {
return;
}
DBWriter.markItemPlayed(item, FeedItem.PLAYED, false);
DBWriter.removeQueueItem(context, item, false);
DBWriter.addItemToPlaybackHistory(oldMedia);
if (item.getFeed().getPreferences().getCurrentAutoDelete()) {
Log.d(TAG, "Delete " + oldMedia.toString());
DBWriter.deleteFeedMediaOfItem(context, oldMedia.getId());
}
}
}
}
public interface PSMPCallback { public interface PSMPCallback {
void statusChanged(PSMPInfo newInfo); void statusChanged(PSMPInfo newInfo);
@ -328,7 +305,11 @@ public abstract class PlaybackServiceMediaPlayer {
boolean onMediaPlayerError(Object inObj, int what, int extra); boolean onMediaPlayerError(Object inObj, int what, int extra);
boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers); void onPostPlayback(Playable media, boolean ended, boolean playingNext);
Playable getNextInQueue(Playable currentMedia);
void onPlaybackEnded(MediaType mediaType, boolean stopPlaying);
} }
/** /**