From c9915bba18eab44c82d9236f12a29a993678a257 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Sat, 7 Apr 2018 16:45:42 -0700 Subject: [PATCH] -Reversed special seek logic for short buffer livestreams. -Fixed loader cleaning potentially canceling existing correct loading items. -Updated ExoPlayer to 2.7.3. --- app/build.gradle | 2 +- .../org/schabi/newpipe/player/BasePlayer.java | 22 +--- .../player/playback/MediaSourceManager.java | 105 ++++++++++++------ 3 files changed, 73 insertions(+), 56 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index cf4db5ccc..a0b4da510 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { ext { supportLibVersion = '27.1.0' - exoPlayerLibVersion = '2.7.2' + exoPlayerLibVersion = '2.7.3' roomDbLibVersion = '1.0.0' leakCanaryLibVersion = '1.5.4' okHttpLibVersion = '1.5.0' diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index e3d351671..c9b67a7c9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -144,7 +144,6 @@ public abstract class BasePlayer implements protected final static int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds protected final static int PROGRESS_LOOP_INTERVAL_MILLIS = 500; protected final static int RECOVERY_SKIP_THRESHOLD_MILLIS = 3000; // 3 seconds - protected final static int SHORT_LIVESTREAM_CHUNK_LENGTH_MILLIS = 60000; // 1 minute protected CustomTrackSelector trackSelector; protected PlayerDataSource dataSource; @@ -647,7 +646,7 @@ public abstract class BasePlayer implements // Is still synchronizing? seekToDefault(); - } else if (isSynchronizing && presetStartPositionMillis != 0L) { + } else if (isSynchronizing && presetStartPositionMillis > 0L) { if (DEBUG) Log.d(TAG, "Playback - Seeking to preset start " + "position=[" + presetStartPositionMillis + "]"); // Has another start position? @@ -1033,25 +1032,8 @@ public abstract class BasePlayer implements && simpleExoPlayer.getCurrentPosition() >= 0; } - /** - * Seeks to the default position of the currently playing - * {@link com.google.android.exoplayer2.Timeline.Window}. Does nothing if the - * {@link #simpleExoPlayer} is not initialized. - *

- * If the current window is non-live, then this will seek to the start of the window. - * If the window is live but has a buffer length greater than - * {@link #SHORT_LIVESTREAM_CHUNK_LENGTH_MILLIS}, then this will seek to the default - * live edge position through {@link SimpleExoPlayer#seekToDefaultPosition}. - * Otherwise, this will seek to the maximum position possible for the current buffer - * given by {@link SimpleExoPlayer#getDuration}. - * - * @see SimpleExoPlayer#seekToDefaultPosition - * */ public void seekToDefault() { - if (simpleExoPlayer == null) return; - if (isLive() && simpleExoPlayer.getDuration() < SHORT_LIVESTREAM_CHUNK_LENGTH_MILLIS) { - simpleExoPlayer.seekTo(simpleExoPlayer.getDuration()); - } else { + if (simpleExoPlayer != null) { simpleExoPlayer.seekToDefaultPosition(); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index 583c4b8e7..a0f67d398 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -24,6 +24,7 @@ import org.schabi.newpipe.playlist.events.RemoveEvent; import org.schabi.newpipe.playlist.events.ReorderEvent; import org.schabi.newpipe.util.ServiceHelper; +import java.util.Collection; import java.util.Collections; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -39,7 +40,8 @@ import io.reactivex.functions.Consumer; import io.reactivex.internal.subscriptions.EmptySubscription; import io.reactivex.subjects.PublishSubject; -import static org.schabi.newpipe.player.mediasource.FailedMediaSource.*; +import static org.schabi.newpipe.player.mediasource.FailedMediaSource.MediaSourceResolutionException; +import static org.schabi.newpipe.player.mediasource.FailedMediaSource.StreamInfoLoadException; import static org.schabi.newpipe.playlist.PlayQueue.DEBUG; public class MediaSourceManager { @@ -267,6 +269,8 @@ public class MediaSourceManager { } private boolean isPlaybackReady() { + if (playlist.size() != playQueue.size()) return false; + final ManagedMediaSource mediaSource = playlist.get(playQueue.getIndex()); if (mediaSource == null) return false; @@ -288,7 +292,7 @@ public class MediaSourceManager { private void maybeUnblock() { if (DEBUG) Log.d(TAG, "maybeUnblock() called."); - if (isPlayQueueReady() && isPlaybackReady() && isBlocked.get()) { + if (isBlocked.get()) { isBlocked.set(false); playbackListener.onPlaybackUnblock(playlist.getParentMediaSource()); } @@ -299,10 +303,10 @@ public class MediaSourceManager { //////////////////////////////////////////////////////////////////////////*/ private void maybeSync() { - if (DEBUG) Log.d(TAG, "onPlaybackSynchronize() called."); + if (DEBUG) Log.d(TAG, "maybeSync() called."); final PlayQueueItem currentItem = playQueue.getItem(); - if (isBlocked.get() || !isPlaybackReady() || currentItem == null) return; + if (isBlocked.get() || currentItem == null) return; final Consumer onSuccess = info -> syncInternal(currentItem, info); final Consumer onError = throwable -> syncInternal(currentItem, null); @@ -321,9 +325,11 @@ public class MediaSourceManager { } } - private void maybeSynchronizePlayer() { - maybeUnblock(); - maybeSync(); + private synchronized void maybeSynchronizePlayer() { + if (isPlayQueueReady() && isPlaybackReady()) { + maybeUnblock(); + maybeSync(); + } } /*////////////////////////////////////////////////////////////////////////// @@ -346,37 +352,16 @@ public class MediaSourceManager { debouncedSignal.onNext(System.currentTimeMillis()); } - private void loadImmediate() { + private synchronized void loadImmediate() { if (DEBUG) Log.d(TAG, "MediaSource - loadImmediate() called"); - // The current item has higher priority - final int currentIndex = playQueue.getIndex(); - final PlayQueueItem currentItem = playQueue.getItem(currentIndex); - if (currentItem == null) return; + final ItemsToLoad itemsToLoad = getItemsToLoad(playQueue, WINDOW_SIZE); + if (itemsToLoad == null) return; - // Evict the items being loaded to free up memory - if (loaderReactor.size() > MAXIMUM_LOADER_SIZE) { - loaderReactor.clear(); - loadingItems.clear(); - } - maybeLoadItem(currentItem); + // Evict the previous items being loaded to free up memory, before start loading new ones + maybeClearLoaders(); - // The rest are just for seamless playback - // Although timeline is not updated prior to the current index, these sources are still - // loaded into the cache for faster retrieval at a potentially later time. - final int leftBound = Math.max(0, currentIndex - WINDOW_SIZE); - final int rightLimit = currentIndex + WINDOW_SIZE + 1; - final int rightBound = Math.min(playQueue.size(), rightLimit); - final Set items = new ArraySet<>( - playQueue.getStreams().subList(leftBound,rightBound)); - - // Do a round robin - final int excess = rightLimit - playQueue.size(); - if (excess >= 0) { - items.addAll(playQueue.getStreams().subList(0, Math.min(playQueue.size(), excess))); - } - items.remove(currentItem); - - for (final PlayQueueItem item : items) { + maybeLoadItem(itemsToLoad.center); + for (final PlayQueueItem item : itemsToLoad.neighbors) { maybeLoadItem(item); } } @@ -476,6 +461,15 @@ public class MediaSourceManager { "index=[" + currentIndex + "], item=[" + currentItem.getTitle() + "]"); playlist.invalidate(currentIndex, this::loadImmediate); } + + private void maybeClearLoaders() { + if (DEBUG) Log.d(TAG, "MediaSource - maybeClearLoaders() called."); + if (!loadingItems.contains(playQueue.getItem()) && + loaderReactor.size() > MAXIMUM_LOADER_SIZE) { + loaderReactor.clear(); + loadingItems.clear(); + } + } /*////////////////////////////////////////////////////////////////////////// // MediaSource Playlist Helpers //////////////////////////////////////////////////////////////////////////*/ @@ -493,4 +487,45 @@ public class MediaSourceManager { playlist.expand(); } } + + /*////////////////////////////////////////////////////////////////////////// + // Manager Helpers + //////////////////////////////////////////////////////////////////////////*/ + @Nullable + private static ItemsToLoad getItemsToLoad(@NonNull final PlayQueue playQueue, + final int windowSize) { + // The current item has higher priority + final int currentIndex = playQueue.getIndex(); + final PlayQueueItem currentItem = playQueue.getItem(currentIndex); + if (currentItem == null) return null; + + // The rest are just for seamless playback + // Although timeline is not updated prior to the current index, these sources are still + // loaded into the cache for faster retrieval at a potentially later time. + final int leftBound = Math.max(0, currentIndex - windowSize); + final int rightLimit = currentIndex + windowSize + 1; + final int rightBound = Math.min(playQueue.size(), rightLimit); + final Set neighbors = new ArraySet<>( + playQueue.getStreams().subList(leftBound,rightBound)); + + // Do a round robin + final int excess = rightLimit - playQueue.size(); + if (excess >= 0) { + neighbors.addAll(playQueue.getStreams().subList(0, Math.min(playQueue.size(), excess))); + } + neighbors.remove(currentItem); + + return new ItemsToLoad(currentItem, neighbors); + } + + private static class ItemsToLoad { + @NonNull final private PlayQueueItem center; + @NonNull final private Collection neighbors; + + ItemsToLoad(@NonNull final PlayQueueItem center, + @NonNull final Collection neighbors) { + this.center = center; + this.neighbors = neighbors; + } + } }