summaryrefslogtreecommitdiff
path: root/core/src/main/java/de
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main/java/de')
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java54
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java86
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java356
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java104
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java16
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java68
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java19
8 files changed, 439 insertions, 272 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
index 64d4d14fd..d4414227c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.core.feed;
+import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
@@ -14,12 +15,16 @@ import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
+import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.ChapterUtils;
+import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
import de.danoeh.antennapod.core.util.playback.Playable;
public class FeedMedia extends FeedFile implements Playable {
@@ -48,6 +53,8 @@ public class FeedMedia extends FeedFile implements Playable {
private String mime_type;
@Nullable private volatile FeedItem item;
private Date playbackCompletionDate;
+ private int startPosition = -1;
+ private int playedDurationWhenStarted;
// if null: unknown, will be checked
private Boolean hasEmbeddedPicture;
@@ -73,6 +80,7 @@ public class FeedMedia extends FeedFile implements Playable {
this.duration = duration;
this.position = position;
this.played_duration = played_duration;
+ this.playedDurationWhenStarted = played_duration;
this.size = size;
this.mime_type = mime_type;
this.playbackCompletionDate = playbackCompletionDate == null
@@ -472,15 +480,59 @@ public class FeedMedia extends FeedFile implements Playable {
}
setPosition(newPosition);
setLastPlayedTime(timeStamp);
+ if(startPosition>=0 && position > startPosition) {
+ setPlayedDuration(playedDurationWhenStarted + position - startPosition);
+ }
DBWriter.setFeedMediaPlaybackInformation(this);
}
@Override
public void onPlaybackStart() {
+ startPosition = (position > 0) ? position : 0;
+ playedDurationWhenStarted = played_duration;
}
+
@Override
- public void onPlaybackCompleted() {
+ public void onPlaybackPause(Context context) {
+ if (position > startPosition) {
+ played_duration = playedDurationWhenStarted + position - startPosition;
+ playedDurationWhenStarted = played_duration;
+ }
+ postPlaybackTasks(context, false);
+ startPosition = position;
+ }
+ @Override
+ public void onPlaybackCompleted(Context context) {
+ postPlaybackTasks(context, true);
+ startPosition = -1;
+ }
+
+ private void postPlaybackTasks(Context context, boolean completed) {
+ if (item != null) {
+ // gpodder play action
+ if (startPosition >= 0 && (completed || startPosition < position) &&
+ GpodnetPreferences.loggedIn()) {
+ GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, GpodnetEpisodeAction.Action.PLAY)
+ .currentDeviceId()
+ .currentTimestamp()
+ .started(startPosition / 1000)
+ .position((completed ? duration : position) / 1000)
+ .total(duration / 1000)
+ .build();
+ GpodnetPreferences.enqueueEpisodeAction(action);
+ }
+ // Auto flattr
+ float autoFlattrThreshold = UserPreferences.getAutoFlattrPlayedDurationThreshold();
+ if (FlattrUtils.hasToken() &&
+ UserPreferences.isAutoFlattr() &&
+ item.getPaymentLink() != null &&
+ item.getFlattrStatus().getUnflattred() &&
+ (completed && autoFlattrThreshold <= 1.0f ||
+ played_duration >= autoFlattrThreshold * duration)) {
+ DBTasks.flattrItemIfLoggedIn(context, item);
+ }
+ }
}
@Override
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 8bacac1ef..0871758d0 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
@@ -13,6 +13,7 @@ import org.antennapod.audio.MediaPlayer;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -140,10 +141,13 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
}
// set temporarily to pause in order to update list with current position
if (playerStatus == PlayerStatus.PLAYING) {
- setPlayerStatus(PlayerStatus.PAUSED, media);
+ callback.onPlaybackPause(media, getPosition());
}
- smartMarkAsPlayed(media);
+ if (!media.getIdentifier().equals(playable.getIdentifier())) {
+ final Playable oldMedia = media;
+ executor.submit(() -> callback.onPostPlayback(oldMedia, false, true));
+ }
setPlayerStatus(PlayerStatus.INDETERMINATE, null);
}
@@ -199,6 +203,8 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
audioFocusChangeListener, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ Log.d(TAG, "Audiofocus successfully requested");
+ Log.d(TAG, "Resuming/Starting playback");
acquireWifiLockIfNecessary();
float speed = 1.0f;
try {
@@ -220,8 +226,6 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
setPlayerStatus(PlayerStatus.PLAYING, media);
pausedBecauseOfTransientAudiofocusLoss = false;
- media.onPlaybackStart();
-
} else {
Log.e(TAG, "Failed to request audio focus");
}
@@ -249,7 +253,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
if (playerStatus == PlayerStatus.PLAYING) {
Log.d(TAG, "Pausing playback.");
mediaPlayer.pause();
- setPlayerStatus(PlayerStatus.PAUSED, media);
+ setPlayerStatus(PlayerStatus.PAUSED, media, getPosition());
if (abandonFocus) {
audioManager.abandonAudioFocus(audioFocusChangeListener);
@@ -311,11 +315,12 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
videoSize = new Pair<>(vp.getVideoWidth(), vp.getVideoHeight());
}
+ // TODO this call has no effect!
if (media.getPosition() > 0) {
seekToSync(media.getPosition());
}
- if (media.getDuration() == 0) {
+ if (media.getDuration() <= 0) {
Log.d(TAG, "Setting duration of media");
media.setDuration(mediaPlayer.getDuration());
}
@@ -367,10 +372,6 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
if (playerStatus == PlayerStatus.PLAYING
|| playerStatus == PlayerStatus.PAUSED
|| playerStatus == PlayerStatus.PREPARED) {
- if (!stream) {
- statusBeforeSeeking = playerStatus;
- setPlayerStatus(PlayerStatus.SEEKING, media);
- }
if(seekLatch != null && seekLatch.getCount() > 0) {
try {
seekLatch.await(3, TimeUnit.SECONDS);
@@ -379,6 +380,8 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
}
}
seekLatch = new CountDownLatch(1);
+ statusBeforeSeeking = playerStatus;
+ setPlayerStatus(PlayerStatus.SEEKING, media, getPosition());
mediaPlayer.seekTo(t);
try {
seekLatch.await(3, TimeUnit.SECONDS);
@@ -752,8 +755,8 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
@Override
- public void endPlayback(final boolean wasSkipped, boolean switchingPlayers) {
- executor.submit(() -> {
+ protected Future<?> endPlayback(final boolean wasSkipped, final boolean shouldContinue, final boolean toStoppedState) {
+ return executor.submit(() -> {
playerLock.lock();
releaseWifiLockIfNecessary();
@@ -762,13 +765,58 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
if (playerStatus != PlayerStatus.INDETERMINATE) {
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) {
mediaPlayer.reset();
-
}
audioManager.abandonAudioFocus(audioFocusChangeListener);
- callback.endPlayback(media, isPlaying, wasSkipped, switchingPlayers);
+ final Playable currentMedia = media;
+ Playable nextMedia = null;
+
+ if (shouldContinue) {
+ // 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
+ 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);
+ }
+ }
+ if (shouldContinue || toStoppedState) {
+ if (nextMedia == null) {
+ callback.onPlaybackEnded(null, true);
+ stop();
+ }
+ final boolean hasNext = nextMedia != null;
+
+ executor.submit(() -> callback.onPostPlayback(currentMedia, !wasSkipped, hasNext));
+ } else if (isPlaying) {
+ callback.onPlaybackPause(currentMedia, currentMedia.getPosition());
+ }
playerLock.unlock();
});
}
@@ -779,8 +827,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
* 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.
*/
- @Override
- public void stop() {
+ private void stop() {
executor.submit(() -> {
playerLock.lock();
releaseWifiLockIfNecessary();
@@ -833,7 +880,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
mp -> genericOnCompletion();
private void genericOnCompletion() {
- endPlayback(false, false);
+ endPlayback(false, true, true);
}
private final MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener =
@@ -889,8 +936,11 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
seekLatch.countDown();
}
playerLock.lock();
+ if (playerStatus == PlayerStatus.PLAYING) {
+ callback.onPlaybackStart(media, getPosition());
+ }
if (playerStatus == PlayerStatus.SEEKING) {
- setPlayerStatus(statusBeforeSeeking, media);
+ setPlayerStatus(statusBeforeSeeking, media, getPosition());
}
playerLock.unlock();
});
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
index e3557f5f8..04b5b676d 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
@@ -52,9 +52,6 @@ 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.glide.ApGlideSettings;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
-import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction.Action;
-import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
@@ -63,7 +60,6 @@ import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.IntList;
import de.danoeh.antennapod.core.util.QueueAccess;
-import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
import de.danoeh.antennapod.core.util.playback.ExternalMedia;
import de.danoeh.antennapod.core.util.playback.Playable;
@@ -206,8 +202,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
*/
private MediaSessionCompat mediaSession;
- private int startPosition;
-
private static volatile MediaType currentMediaType = MediaType.UNKNOWN;
private final IBinder mBinder = new LocalBinder();
@@ -473,7 +467,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
UserPreferences.shouldHardwareButtonSkip()) {
// assume the skip command comes from a notification or the lockscreen
// a >| skip button should actually skip
- mediaPlayer.endPlayback(true, false);
+ mediaPlayer.skip();
} else {
// assume skip command comes from a (bluetooth) media button
// user actually wants to fast-forward
@@ -530,7 +524,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() {
@Override
public void positionSaverTick() {
- saveCurrentPosition(true, PlaybackServiceTaskManager.POSITION_SAVER_WAITING_INTERVAL);
+ saveCurrentPosition(true, null, PlaybackServiceMediaPlayer.INVALID_TIME);
}
@Override
@@ -582,9 +576,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
break;
case PAUSED:
- taskManager.cancelPositionSaver();
- saveCurrentPosition(false, 0);
- taskManager.cancelWidgetUpdater();
if ((UserPreferences.isPersistNotify() || isCasting) &&
android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// do not remove notification on pause based on user pref and whether android version supports expanded notifications
@@ -595,22 +586,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
stopForeground(true);
}
writePlayerStatusPlaybackPreferences();
-
- final Playable playable = newInfo.playable;
-
- // Gpodder: send play action
- if(GpodnetPreferences.loggedIn() && playable instanceof FeedMedia) {
- FeedMedia media = (FeedMedia) playable;
- FeedItem item = media.getItem();
- GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY)
- .currentDeviceId()
- .currentTimestamp()
- .started(startPosition / 1000)
- .position(getCurrentPosition() / 1000)
- .total(getDuration() / 1000)
- .build();
- GpodnetPreferences.enqueueEpisodeAction(action);
- }
break;
case STOPPED:
@@ -619,15 +594,9 @@ public class PlaybackService extends MediaBrowserServiceCompat {
break;
case PLAYING:
- Log.d(TAG, "Audiofocus successfully requested");
- Log.d(TAG, "Resuming/Starting playback");
-
- taskManager.startPositionSaver();
- taskManager.startWidgetUpdater();
writePlayerStatusPlaybackPreferences();
setupNotification(newInfo);
started = true;
- startPosition = mediaPlayer.getPosition();
break;
case ERROR:
@@ -700,121 +669,168 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
@Override
- public boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) {
- PlaybackService.this.endPlayback(media, playNextEpisode, wasSkipped, switchingPlayers);
- return true;
+ public void onPostPlayback(@NonNull Playable media, boolean ended, boolean playingNext) {
+ PlaybackService.this.onPostPlayback(media, ended, playingNext);
+ }
+
+ @Override
+ public void onPlaybackStart(@NonNull Playable playable, int position) {
+ taskManager.startWidgetUpdater();
+ if (position != PlaybackServiceMediaPlayer.INVALID_TIME) {
+ playable.setPosition(position);
+ }
+ playable.onPlaybackStart();
+ taskManager.startPositionSaver();
+ }
+
+ @Override
+ public void onPlaybackPause(Playable playable, int position) {
+ taskManager.cancelPositionSaver();
+ saveCurrentPosition(position == PlaybackServiceMediaPlayer.INVALID_TIME || playable == null,
+ playable, position);
+ taskManager.cancelWidgetUpdater();
+ if (playable != null) {
+ playable.onPlaybackPause(getApplicationContext());
+ }
+ }
+
+ @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) {
- Log.d(TAG, "Playback ended" + (switchingPlayers ? " from switching players": ""));
+ private Playable getNextInQueue(final Playable currentMedia) {
+ 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);
+ }
+ }
+
+ /**
+ * This method processes the media object after its playback ended, either because it completed
+ * or because a different media object was selected for playback.
+ *
+ * Even though these tasks aren't supposed to be resource intensive, a good practice is to
+ * usually call this method on a background thread.
+ *
+ * @param playable the media object that was playing. It is assumed that its position property
+ * was updated before this method was called.
+ * @param ended if true, it signals that {@param playable} was played until its end.
+ * In such case, the position property of the media becomes irrelevant for most of
+ * the tasks (although it's still a good practice to keep it accurate).
+ * @param playingNext if true, it means another media object is being loaded in place of this one.
+ * Instances when we'd set it to false would be when we're not following the
+ * queue or when the queue has ended.
+ */
+ private void onPostPlayback(final Playable playable, boolean ended, boolean playingNext) {
if (playable == null) {
- Log.e(TAG, "Cannot end playback: media was null");
+ Log.e(TAG, "Cannot do post-playback processing: media was null");
return;
}
+ Log.d(TAG, "onPostPlayback(): media=" + playable.getEpisodeTitle());
- taskManager.cancelPositionSaver();
-
- boolean isInQueue = false;
- FeedItem nextItem = null;
+ if (!(playable instanceof FeedMedia)) {
+ Log.d(TAG, "Not doing post-playback processing: media not of type FeedMedia");
+ if (ended) {
+ playable.onPlaybackCompleted(getApplicationContext());
+ } else {
+ playable.onPlaybackPause(getApplicationContext());
+ }
+ return;
+ }
+ FeedMedia media = (FeedMedia) playable;
+ FeedItem item = media.getItem();
+ boolean smartMarkAsPlayed = playingNext && media.hasAlmostEnded();
+ if (!ended && smartMarkAsPlayed) {
+ Log.d(TAG, "smart mark as played");
+ }
- if (playable instanceof FeedMedia && ((FeedMedia) playable).getItem() != null) {
- FeedMedia media = (FeedMedia) playable;
- FeedItem item = media.getItem();
+ if (ended || smartMarkAsPlayed) {
+ media.onPlaybackCompleted(getApplicationContext());
+ } else {
+ media.onPlaybackPause(getApplicationContext());
+ }
- 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 {
final List<FeedItem> queue = taskManager.getQueue();
- isInQueue = QueueAccess.ItemListAccess(queue).contains(item.getId());
- nextItem = DBTasks.getQueueSuccessorOfItem(item.getId(), queue);
+ if (QueueAccess.ItemListAccess(queue).contains(item.getId())) {
+ // 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) {
e.printStackTrace();
// isInQueue remains false
}
-
- boolean shouldKeep = wasSkipped && UserPreferences.shouldSkipKeepEpisode();
-
- if (!shouldKeep) {
- // 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");
- }
+ // 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) {
- // 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
- 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();
- }
+ if (ended || playingNext) {
+ DBWriter.addItemToPlaybackHistory(media);
}
}
@@ -1218,28 +1234,23 @@ public class PlaybackService extends MediaBrowserServiceCompat {
/**
* Persists the current position and last played time of the media file.
*
- * @param updatePlayedDuration true if played_duration should be updated. This applies only to FeedMedia objects
- * @param deltaPlayedDuration value by which played_duration should be increased.
+ * @param fromMediaPlayer if true, the information is gathered from the current Media Player
+ * and {@param playable} and {@param position} become irrelevant.
+ * @param playable the playable for which the current position should be saved, unless
+ * {@param fromMediaPlayer} is true.
+ * @param position the position that should be saved, unless {@param fromMediaPlayer} is true.
*/
- private synchronized void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration) {
- int position = getCurrentPosition();
- int duration = getDuration();
- float playbackSpeed = getCurrentPlaybackSpeed();
- final Playable playable = mediaPlayer.getPlayable();
+ private synchronized void saveCurrentPosition(boolean fromMediaPlayer, Playable playable, int position) {
+ int duration;
+ if (fromMediaPlayer) {
+ position = getCurrentPosition();
+ duration = getDuration();
+ playable = mediaPlayer.getPlayable();
+ } else {
+ duration = playable.getDuration();
+ }
if (position != INVALID_TIME && duration != INVALID_TIME && playable != null) {
Log.d(TAG, "Saving current position to " + position);
- if (updatePlayedDuration && playable instanceof FeedMedia) {
- FeedMedia media = (FeedMedia) playable;
- FeedItem item = media.getItem();
- media.setPlayedDuration(media.getPlayedDuration() + ((int) (deltaPlayedDuration * playbackSpeed)));
- // Auto flattr
- if (isAutoFlattrable(media) &&
- (media.getPlayedDuration() > UserPreferences.getAutoFlattrPlayedDurationThreshold() * duration)) {
- Log.d(TAG, "saveCurrentPosition: performing auto flattr since played duration " + Integer.toString(media.getPlayedDuration())
- + " is " + UserPreferences.getAutoFlattrPlayedDurationThreshold() * 100 + "% of file duration " + Integer.toString(duration));
- DBTasks.flattrItemIfLoggedIn(this, item);
- }
- }
playable.saveCurrentPosition(
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()),
position,
@@ -1407,7 +1418,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
public void onReceive(Context context, Intent intent) {
if (TextUtils.equals(intent.getAction(), ACTION_SKIP_CURRENT_EPISODE)) {
Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent");
- mediaPlayer.endPlayback(true, false);
+ mediaPlayer.skip();
}
}
};
@@ -1500,26 +1511,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
public void seekTo(final int t) {
- if(mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING
- && GpodnetPreferences.loggedIn()) {
- final Playable playable = mediaPlayer.getPlayable();
- if (playable instanceof FeedMedia) {
- FeedMedia media = (FeedMedia) playable;
- FeedItem item = media.getItem();
- GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY)
- .currentDeviceId()
- .currentTimestamp()
- .started(startPosition / 1000)
- .position(getCurrentPosition() / 1000)
- .total(getDuration() / 1000)
- .build();
- GpodnetPreferences.enqueueEpisodeAction(action);
- }
- }
mediaPlayer.seekTo(t);
- if(mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING ) {
- startPosition = t;
- }
}
@@ -1528,10 +1520,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
/**
- * @see LocalPSMP#seekToChapter(de.danoeh.antennapod.core.feed.Chapter)
+ * Seek to the start of the specified chapter.
*/
public void seekToChapter(Chapter c) {
- mediaPlayer.seekToChapter(c);
+ seekTo((int) c.getStart());
}
/**
@@ -1558,15 +1550,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
return mediaPlayer.getVideoSize();
}
- private boolean isAutoFlattrable(FeedMedia media) {
- if (media != null) {
- FeedItem item = media.getItem();
- return item != null && FlattrUtils.hasToken() && UserPreferences.isAutoFlattr() && item.getPaymentLink() != null && item.getFlattrStatus().getUnflattred();
- } else {
- return false;
- }
- }
-
private final MediaSessionCompat.Callback sessionCallback = new MediaSessionCompat.Callback() {
private static final String TAG = "MediaSessionCompat";
@@ -1602,19 +1585,14 @@ public class PlaybackService extends MediaBrowserServiceCompat {
public void onPause() {
Log.d(TAG, "onPause()");
if (getStatus() == PlayerStatus.PLAYING) {
- pause(false, true);
- }
- if (UserPreferences.isPersistNotify()) {
- pause(false, true);
- } else {
- pause(true, true);
+ pause(!UserPreferences.isPersistNotify(), true);
}
}
@Override
public void onStop() {
Log.d(TAG, "onStop()");
- mediaPlayer.stop();
+ mediaPlayer.stopPlayback(true);
}
@Override
@@ -1639,7 +1617,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
public void onSkipToNext() {
Log.d(TAG, "onSkipToNext()");
if(UserPreferences.shouldHardwareButtonSkip()) {
- mediaPlayer.endPlayback(true, false);
+ mediaPlayer.skip();
} else {
seekDelta(UserPreferences.getFastFowardSecs() * 1000);
}
@@ -1682,7 +1660,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
PlaybackServiceMediaPlayer getMediaPlayer();
void setIsCasting(boolean isCasting);
void sendNotificationBroadcast(int type, int code);
- void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration);
+ void saveCurrentPosition(boolean fromMediaPlayer, Playable playable, int position);
void setupNotification(boolean connected, PlaybackServiceMediaPlayer.PSMPInfo info);
MediaSessionCompat getMediaSession();
Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter);
@@ -1716,8 +1694,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
@Override
- public void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration) {
- PlaybackService.this.saveCurrentPosition(updatePlayedDuration, deltaPlayedDuration);
+ public void saveCurrentPosition(boolean fromMediaPlayer, Playable playable, int position) {
+ PlaybackService.this.saveCurrentPosition(fromMediaPlayer, playable, position);
}
@Override
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 e05733135..aec059ca0 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
@@ -8,11 +8,9 @@ import android.util.Log;
import android.util.Pair;
import android.view.SurfaceHolder;
-import de.danoeh.antennapod.core.feed.Chapter;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.FeedMedia;
+import java.util.concurrent.Future;
+
import de.danoeh.antennapod.core.feed.MediaType;
-import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.playback.Playable;
@@ -130,13 +128,6 @@ public abstract class PlaybackServiceMediaPlayer {
public abstract void seekDelta(int d);
/**
- * Seek to the start of the specified chapter.
- */
- 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.
*/
public abstract int getDuration();
@@ -236,15 +227,44 @@ public abstract class PlaybackServiceMediaPlayer {
protected abstract void setPlayable(Playable playable);
- public abstract void endPlayback(boolean wasSkipped, boolean switchingPlayers);
+ public void skip() {
+ endPlayback(true, true, true);
+ }
/**
- * Moves the PSMP into STOPPED state. This call is only valid if the player is currently in
- * 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
- * abandoning audio focus have to be done with other methods.
+ * 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 abstract void stop();
+ public Future<?> stopPlayback(boolean toStoppedState) {
+ return endPlayback(true, false, toStoppedState);
+ }
+
+ /**
+ * Internal method that handles end of playback.
+ *
+ * Currently, it has 4 use cases:
+ * <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.
+ */
+ protected abstract Future<?> endPlayback(boolean wasSkipped, boolean shouldContinue, boolean toStoppedState);
/**
* @return {@code true} if the WifiLock feature should be used, {@code false} otherwise.
@@ -274,41 +294,39 @@ public abstract class PlaybackServiceMediaPlayer {
* <p/>
* This method will notify the callback about the change of the player status (even if the new status is the same
* as the old one).
+ * <p/>
+ * It will also call {@link PSMPCallback#onPlaybackPause(Playable, int)} or {@link PSMPCallback#onPlaybackStart(Playable, int)}
+ * depending on the status change.
*
* @param newStatus The new PlayerStatus. This must not be null.
* @param newMedia The new playable object of the PSMP object. This can be null.
+ * @param position The position to be set to the current Playable object in case playback started or paused.
+ * Will be ignored if given the value of {@link #INVALID_TIME}.
*/
- protected final synchronized void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia) {
+ protected final synchronized void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia, int position) {
Log.d(TAG, this.getClass().getSimpleName() + ": Setting player status to " + newStatus);
+ PlayerStatus oldStatus = playerStatus;
+
this.playerStatus = newStatus;
setPlayable(newMedia);
- if (playerStatus != null) {
- Log.d(TAG, "playerStatus: " + playerStatus.toString());
+ if (newMedia != null && newStatus != PlayerStatus.INDETERMINATE) {
+ if (oldStatus == PlayerStatus.PLAYING && newStatus != PlayerStatus.PLAYING) {
+ callback.onPlaybackPause(newMedia, position);
+ } else if (oldStatus != PlayerStatus.PLAYING && newStatus == PlayerStatus.PLAYING) {
+ callback.onPlaybackStart(newMedia, position);
+ }
}
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());
- }
- }
- }
+ /**
+ * @see #setPlayerStatus(PlayerStatus, Playable, int)
+ */
+ protected final void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia) {
+ setPlayerStatus(newStatus, newMedia, INVALID_TIME);
}
public interface PSMPCallback {
@@ -328,7 +346,15 @@ public abstract class PlaybackServiceMediaPlayer {
boolean onMediaPlayerError(Object inObj, int what, int extra);
- boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers);
+ void onPostPlayback(@NonNull Playable media, boolean ended, boolean playingNext);
+
+ void onPlaybackStart(@NonNull Playable playable, int position);
+
+ void onPlaybackPause(Playable playable, int position);
+
+ Playable getNextInQueue(Playable currentMedia);
+
+ void onPlaybackEnded(MediaType mediaType, boolean stopPlaying);
}
/**
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
index 7fd7602a8..0c7d5e718 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
@@ -299,7 +299,8 @@ public class PlaybackServiceTaskManager {
private static final String TAG = "SleepTimer";
private static final long UPDATE_INTERVAL = 1000L;
private static final long NOTIFICATION_THRESHOLD = 10000;
- private long waitingTime;
+ private final long waitingTime;
+ private long timeLeft;
private final boolean shakeToReset;
private final boolean vibrate;
private ShakeListener shakeListener;
@@ -307,6 +308,7 @@ public class PlaybackServiceTaskManager {
public SleepTimer(long waitingTime, boolean shakeToReset, boolean vibrate) {
super();
this.waitingTime = waitingTime;
+ this.timeLeft = waitingTime;
this.shakeToReset = shakeToReset;
this.vibrate = vibrate;
}
@@ -316,14 +318,14 @@ public class PlaybackServiceTaskManager {
Log.d(TAG, "Starting");
boolean notifiedAlmostExpired = false;
long lastTick = System.currentTimeMillis();
- while (waitingTime > 0) {
+ while (timeLeft > 0) {
try {
Thread.sleep(UPDATE_INTERVAL);
long now = System.currentTimeMillis();
- waitingTime -= now - lastTick;
+ timeLeft -= now - lastTick;
lastTick = now;
- if(waitingTime < NOTIFICATION_THRESHOLD && !notifiedAlmostExpired) {
+ if(timeLeft < NOTIFICATION_THRESHOLD && !notifiedAlmostExpired) {
Log.d(TAG, "Sleep timer is about to expire");
if(vibrate) {
Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
@@ -337,7 +339,7 @@ public class PlaybackServiceTaskManager {
callback.onSleepTimerAlmostExpired();
notifiedAlmostExpired = true;
}
- if (waitingTime <= 0) {
+ if (timeLeft <= 0) {
Log.d(TAG, "Sleep timer expired");
if(shakeListener != null) {
shakeListener.pause();
@@ -358,11 +360,11 @@ public class PlaybackServiceTaskManager {
}
public long getWaitingTime() {
- return waitingTime;
+ return timeLeft;
}
public void onShake() {
- setSleepTimer(15 * 60 * 1000, shakeToReset, vibrate);
+ setSleepTimer(waitingTime, shakeToReset, vibrate);
callback.onSleepTimerReset();
shakeListener.pause();
shakeListener = null;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java
index 7a8b2bc03..839e2ae0c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSMedia.java
@@ -7,8 +7,10 @@ import org.xml.sax.Attributes;
import java.util.concurrent.TimeUnit;
+import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
+import de.danoeh.antennapod.core.syndication.namespace.atom.AtomText;
import de.danoeh.antennapod.core.syndication.util.SyndTypeUtils;
/** Processes tags from the http://search.yahoo.com/mrss/ namespace. */
@@ -23,22 +25,35 @@ public class NSMedia extends Namespace {
private static final String SIZE = "fileSize";
private static final String MIME_TYPE = "type";
private static final String DURATION = "duration";
+ private static final String DEFAULT = "isDefault";
+
+ private static final String IMAGE = "thumbnail";
+ private static final String IMAGE_URL = "url";
+
+ private static final String DESCRIPTION = "description";
+ private static final String DESCRIPTION_TYPE = "type";
@Override
public SyndElement handleElementStart(String localName, HandlerState state,
- Attributes attributes) {
+ Attributes attributes) {
if (CONTENT.equals(localName)) {
String url = attributes.getValue(DOWNLOAD_URL);
String type = attributes.getValue(MIME_TYPE);
- boolean validType;
- if(SyndTypeUtils.enclosureTypeValid(type)) {
- validType = true;
- } else {
- type = SyndTypeUtils.getValidMimeTypeFromUrl(url);
- validType = type != null;
- }
- if (state.getCurrentItem() != null && state.getCurrentItem().getMedia() == null &&
- url != null && validType) {
+ String defaultStr = attributes.getValue(DEFAULT);
+ boolean validType;
+
+ boolean isDefault = "true".equals(defaultStr);
+
+ if (SyndTypeUtils.enclosureTypeValid(type)) {
+ validType = true;
+ } else {
+ type = SyndTypeUtils.getValidMimeTypeFromUrl(url);
+ validType = type != null;
+ }
+
+ if (state.getCurrentItem() != null &&
+ (state.getCurrentItem().getMedia() == null || isDefault) &&
+ url != null && validType) {
long size = 0;
String sizeStr = attributes.getValue(SIZE);
try {
@@ -51,25 +66,50 @@ public class NSMedia extends Namespace {
String durationStr = attributes.getValue(DURATION);
if (!TextUtils.isEmpty(durationStr)) {
try {
- long duration = Long.parseLong(durationStr);
+ long duration = Long.parseLong(durationStr);
durationMs = (int) TimeUnit.MILLISECONDS.convert(duration, TimeUnit.SECONDS);
} catch (NumberFormatException e) {
Log.e(TAG, "Duration \"" + durationStr + "\" could not be parsed");
}
}
FeedMedia media = new FeedMedia(state.getCurrentItem(), url, size, type);
- if(durationMs > 0) {
+ if (durationMs > 0) {
media.setDuration(durationMs);
}
state.getCurrentItem().setMedia(media);
}
+ } else if (IMAGE.equals(localName)) {
+ String url = attributes.getValue(IMAGE_URL);
+ if (url != null) {
+ FeedImage image = new FeedImage();
+ image.setDownload_url(url);
+
+ if (state.getCurrentItem() != null) {
+ image.setOwner(state.getCurrentItem());
+ state.getCurrentItem().setImage(image);
+ } else {
+ if (state.getFeed().getImage() == null) {
+ image.setOwner(state.getFeed());
+ state.getFeed().setImage(image);
+ }
+ }
+ }
+ } else if (DESCRIPTION.equals(localName)) {
+ String type = attributes.getValue(DESCRIPTION_TYPE);
+ return new AtomText(localName, this, type);
}
return new SyndElement(localName, this);
}
@Override
public void handleElementEnd(String localName, HandlerState state) {
-
+ if (DESCRIPTION.equals(localName)) {
+ String content = state.getContentBuf().toString();
+ if (state.getCurrentItem() != null && content != null &&
+ state.getCurrentItem().getDescription() == null) {
+ state.getCurrentItem().setDescription(content);
+ }
+ }
}
-
}
+
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java
index a37f98469..c4acdb65e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/ExternalMedia.java
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.core.util.playback;
+import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.media.MediaMetadataRetriever;
@@ -205,7 +206,12 @@ public class ExternalMedia implements Playable {
}
@Override
- public void onPlaybackCompleted() {
+ public void onPlaybackPause(Context context) {
+
+ }
+
+ @Override
+ public void onPlaybackCompleted(Context context) {
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java
index 6459d86ed..279c56338 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java
@@ -138,14 +138,27 @@ public interface Playable extends Parcelable,
void setLastPlayedTime(long lastPlayedTimestamp);
/**
- * Is called by the PlaybackService when playback starts.
+ * This method should be called every time playback starts on this object.
+ * <p/>
+ * Position held by this Playable should be set accurately before a call to this method is made.
*/
void onPlaybackStart();
/**
- * Is called by the PlaybackService when playback is completed.
+ * This method should be called every time playback pauses or stops on this object,
+ * including just before a seeking operation is performed, after which a call to
+ * {@link #onPlaybackStart()} should be made. If playback completes, calling this method is not
+ * necessary, as long as a call to {@link #onPlaybackCompleted(Context)} is made.
+ * <p/>
+ * Position held by this Playable should be set accurately before a call to this method is made.
*/
- void onPlaybackCompleted();
+ void onPlaybackPause(Context context);
+
+ /**
+ * This method should be called when playback completes for this object.
+ * @param context
+ */
+ void onPlaybackCompleted(Context context);
/**
* Returns an integer that must be unique among all Playable classes. The