diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index f002115f8..f688b3aa8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -380,11 +380,10 @@ public final class BackgroundPlayer extends Service { // Playback Listener //////////////////////////////////////////////////////////////////////////*/ - @Override - public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) { - if (currentItem == item && currentInfo == info) return; - super.sync(item, info); - + protected void onMetadataChanged(@NonNull final PlayQueueItem item, + @Nullable final StreamInfo info, + final int newPlayQueueIndex, + final boolean hasPlayQueueItemChanged) { resetNotification(); updateNotification(-1); updateMetadata(); @@ -405,8 +404,8 @@ public final class BackgroundPlayer extends Service { } @Override - public void shutdown() { - super.shutdown(); + public void onPlaybackShutdown() { + super.onPlaybackShutdown(); onClose(); } 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 86a4d1234..bf661c897 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -61,6 +61,7 @@ import org.schabi.newpipe.history.HistoryRecordManager; import org.schabi.newpipe.player.helper.AudioReactor; import org.schabi.newpipe.player.helper.LoadController; import org.schabi.newpipe.player.helper.PlayerDataSource; +import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.playback.CustomTrackSelector; import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; @@ -196,6 +197,7 @@ public abstract class BasePlayer implements simpleExoPlayer.addListener(this); simpleExoPlayer.setPlayWhenReady(true); + simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context)); } public void initListeners() {} @@ -688,7 +690,7 @@ public abstract class BasePlayer implements break; default: showUnrecoverableError(error); - shutdown(); + onPlaybackShutdown(); break; } } @@ -774,9 +776,9 @@ public abstract class BasePlayer implements //////////////////////////////////////////////////////////////////////////*/ @Override - public void block() { + public void onPlaybackBlock() { if (simpleExoPlayer == null) return; - if (DEBUG) Log.d(TAG, "Playback - block() called"); + if (DEBUG) Log.d(TAG, "Playback - onPlaybackBlock() called"); currentItem = null; currentInfo = null; @@ -787,9 +789,9 @@ public abstract class BasePlayer implements } @Override - public void unblock(final MediaSource mediaSource) { + public void onPlaybackUnblock(final MediaSource mediaSource) { if (simpleExoPlayer == null) return; - if (DEBUG) Log.d(TAG, "Playback - unblock() called"); + if (DEBUG) Log.d(TAG, "Playback - onPlaybackUnblock() called"); if (getCurrentState() == STATE_BLOCKED) changeState(STATE_BUFFERING); @@ -798,34 +800,50 @@ public abstract class BasePlayer implements } @Override - public void sync(@NonNull final PlayQueueItem item, - @Nullable final StreamInfo info) { - if (currentItem == item && currentInfo == info) return; - currentItem = item; - currentInfo = info; - - if (DEBUG) Log.d(TAG, "Playback - sync() called with " + + public void onPlaybackSynchronize(@NonNull final PlayQueueItem item, + @Nullable final StreamInfo info) { + if (DEBUG) Log.d(TAG, "Playback - onPlaybackSynchronize() called with " + (info == null ? "available" : "null") + " info, " + "item=[" + item.getTitle() + "], url=[" + item.getUrl() + "]"); - if (simpleExoPlayer == null) return; + + final boolean hasPlayQueueItemChanged = currentItem != item; + final boolean hasStreamInfoChanged = currentInfo != info; + if (!hasPlayQueueItemChanged && !hasStreamInfoChanged) { + return; // Nothing to synchronize + } + + currentItem = item; + currentInfo = info; + if (hasPlayQueueItemChanged) { + // updates only to the stream info should not trigger another view count + registerView(); + initThumbnail(info == null ? item.getThumbnailUrl() : info.getThumbnailUrl()); + } + + final int currentSourceIndex = playQueue.indexOf(item); + onMetadataChanged(item, info, currentSourceIndex, hasPlayQueueItemChanged); // Check if on wrong window - final int currentSourceIndex = playQueue.indexOf(item); + if (simpleExoPlayer == null) return; if (currentSourceIndex != playQueue.getIndex()) { Log.e(TAG, "Play Queue may be desynchronized: item index=[" + currentSourceIndex + "], queue index=[" + playQueue.getIndex() + "]"); - } else if (simpleExoPlayer.getCurrentPeriodIndex() != currentSourceIndex || !isPlaying()) { + + // on metadata changed + } else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex || !isPlaying()) { final long startPos = info != null ? info.start_position : 0; if (DEBUG) Log.d(TAG, "Rewinding to correct window=[" + currentSourceIndex + "]," + " at=[" + getTimeString((int)startPos) + "]," + - " from=[" + simpleExoPlayer.getCurrentPeriodIndex() + "]."); + " from=[" + simpleExoPlayer.getCurrentWindowIndex() + "]."); simpleExoPlayer.seekTo(currentSourceIndex, startPos); } - - registerView(); - initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url); } + abstract protected void onMetadataChanged(@NonNull final PlayQueueItem item, + @Nullable final StreamInfo info, + final int newPlayQueueIndex, + final boolean hasPlayQueueItemChanged); + @Nullable @Override public MediaSource sourceOf(PlayQueueItem item, StreamInfo info) { @@ -839,7 +857,7 @@ public abstract class BasePlayer implements } @Override - public void shutdown() { + public void onPlaybackShutdown() { if (DEBUG) Log.d(TAG, "Shutting down..."); destroy(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index d48994d0f..04d93bbdc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -391,31 +391,32 @@ public final class MainVideoPlayer extends Activity { updatePlaybackButtons(); } - /*////////////////////////////////////////////////////////////////////////// - // Playback Listener - //////////////////////////////////////////////////////////////////////////*/ - - @Override - public void shutdown() { - super.shutdown(); - finish(); - } - - @Override - public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) { - super.sync(item, info); - titleTextView.setText(getVideoTitle()); - channelTextView.setText(getUploaderName()); - - //playPauseButton.setImageResource(R.drawable.ic_pause_white); - } - @Override public void onShuffleClicked() { super.onShuffleClicked(); updatePlaybackButtons(); } + /*////////////////////////////////////////////////////////////////////////// + // Playback Listener + //////////////////////////////////////////////////////////////////////////*/ + + protected void onMetadataChanged(@NonNull final PlayQueueItem item, + @Nullable final StreamInfo info, + final int newPlayQueueIndex, + final boolean hasPlayQueueItemChanged) { + super.onMetadataChanged(item, info, newPlayQueueIndex, false); + + titleTextView.setText(getVideoTitle()); + channelTextView.setText(getUploaderName()); + } + + @Override + public void onPlaybackShutdown() { + super.onPlaybackShutdown(); + finish(); + } + /*////////////////////////////////////////////////////////////////////////// // Player Overrides //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 6263541bb..80063e547 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -535,7 +535,8 @@ public final class PopupVideoPlayer extends Service { private void updatePlayback() { if (activityListener != null && simpleExoPlayer != null && playQueue != null) { - activityListener.onPlaybackUpdate(currentState, getRepeatMode(), playQueue.isShuffled(), simpleExoPlayer.getPlaybackParameters()); + activityListener.onPlaybackUpdate(currentState, getRepeatMode(), + playQueue.isShuffled(), simpleExoPlayer.getPlaybackParameters()); } } @@ -574,16 +575,17 @@ public final class PopupVideoPlayer extends Service { // Playback Listener //////////////////////////////////////////////////////////////////////////*/ - @Override - public void sync(@NonNull PlayQueueItem item, @Nullable StreamInfo info) { - if (currentItem == item && currentInfo == info) return; - super.sync(item, info); + protected void onMetadataChanged(@NonNull final PlayQueueItem item, + @Nullable final StreamInfo info, + final int newPlayQueueIndex, + final boolean hasPlayQueueItemChanged) { + super.onMetadataChanged(item, info, newPlayQueueIndex, false); updateMetadata(); } @Override - public void shutdown() { - super.shutdown(); + public void onPlaybackShutdown() { + super.onPlaybackShutdown(); onClose(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index 58de44130..5b4a1f80b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -324,9 +324,10 @@ public abstract class VideoPlayer extends BasePlayer protected abstract int getOverrideResolutionIndex(final List sortedVideos, final String playbackQuality); - @Override - public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) { - super.sync(item, info); + protected void onMetadataChanged(@NonNull final PlayQueueItem item, + @Nullable final StreamInfo info, + final int newPlayQueueIndex, + final boolean hasPlayQueueItemChanged) { qualityTextView.setVisibility(View.GONE); playbackSpeedTextView.setVisibility(View.GONE); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index 813c69c22..553163d21 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -5,6 +5,7 @@ import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.support.annotation.NonNull; +import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.util.MimeTypes; @@ -109,6 +110,12 @@ public class PlayerHelper { return isRememberingPopupDimensions(context, true); } + @NonNull + public static SeekParameters getSeekParameters(@NonNull final Context context) { + return isUsingInexactSeek(context, false) ? + SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT; + } + public static long getPreferredCacheSize(@NonNull final Context context) { return 64 * 1024 * 1024L; } @@ -176,4 +183,8 @@ public class PlayerHelper { private static boolean isRememberingPopupDimensions(@Nonnull final Context context, final boolean b) { return getPreferences(context).getBoolean(context.getString(R.string.popup_remember_size_pos_key), b); } + + private static boolean isUsingInexactSeek(@NonNull final Context context, final boolean b) { + return getPreferences(context).getBoolean(context.getString(R.string.use_inexact_seek_key), b); + } } 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 bc7f92b42..3f7a8522c 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 @@ -50,7 +50,7 @@ public class MediaSourceManager { * streams before will only be cached for future usage. * * @see #onMediaSourceReceived(PlayQueueItem, ManagedMediaSource) - * @see #update(int, MediaSource) + * @see #update(int, MediaSource, Runnable) * */ private final static int WINDOW_SIZE = 1; @@ -95,15 +95,13 @@ public class MediaSourceManager { * */ private final static int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1; @NonNull private final CompositeDisposable loaderReactor; - @NonNull private Set loadingItems; + @NonNull private final Set loadingItems; @NonNull private final SerialDisposable syncReactor; @NonNull private final AtomicBoolean isBlocked; @NonNull private DynamicConcatenatingMediaSource sources; - @Nullable private PlayQueueItem syncedItem; - public MediaSourceManager(@NonNull final PlaybackListener listener, @NonNull final PlayQueue playQueue) { this(listener, playQueue, @@ -159,8 +157,6 @@ public class MediaSourceManager { loaderReactor.dispose(); syncReactor.dispose(); sources.releaseSource(); - - syncedItem = null; } /** @@ -182,9 +178,7 @@ public class MediaSourceManager { public void reset() { if (DEBUG) Log.d(TAG, "reset() called."); - tryBlock(); - - syncedItem = null; + maybeBlock(); populateSources(); } /*////////////////////////////////////////////////////////////////////////// @@ -215,7 +209,7 @@ public class MediaSourceManager { private void onPlayQueueChanged(final PlayQueueEvent event) { if (playQueue.isEmpty() && playQueue.isComplete()) { - playbackListener.shutdown(); + playbackListener.onPlaybackShutdown(); return; } @@ -261,7 +255,7 @@ public class MediaSourceManager { } if (!isPlayQueueReady()) { - tryBlock(); + maybeBlock(); playQueue.fetch(); } playQueueReactor.request(1); @@ -290,23 +284,23 @@ public class MediaSourceManager { return false; } - private void tryBlock() { - if (DEBUG) Log.d(TAG, "tryBlock() called."); + private void maybeBlock() { + if (DEBUG) Log.d(TAG, "maybeBlock() called."); if (isBlocked.get()) return; - playbackListener.block(); + playbackListener.onPlaybackBlock(); resetSources(); isBlocked.set(true); } - private void tryUnblock() { - if (DEBUG) Log.d(TAG, "tryUnblock() called."); + private void maybeUnblock() { + if (DEBUG) Log.d(TAG, "maybeUnblock() called."); if (isPlayQueueReady() && isPlaybackReady() && isBlocked.get()) { isBlocked.set(false); - playbackListener.unblock(sources); + playbackListener.onPlaybackUnblock(sources); } } @@ -314,8 +308,8 @@ public class MediaSourceManager { // Metadata Synchronization TODO: maybe this should be a separate manager //////////////////////////////////////////////////////////////////////////*/ - private void sync() { - if (DEBUG) Log.d(TAG, "sync() called."); + private void maybeSync() { + if (DEBUG) Log.d(TAG, "onPlaybackSynchronize() called."); final PlayQueueItem currentItem = playQueue.getItem(); if (isBlocked.get() || currentItem == null) return; @@ -323,23 +317,25 @@ public class MediaSourceManager { final Consumer onSuccess = info -> syncInternal(currentItem, info); final Consumer onError = throwable -> syncInternal(currentItem, null); - if (syncedItem != currentItem) { - syncedItem = currentItem; - final Disposable sync = currentItem.getStream() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(onSuccess, onError); - syncReactor.set(sync); - } + final Disposable sync = currentItem.getStream() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(onSuccess, onError); + syncReactor.set(sync); } private void syncInternal(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) { // Ensure the current item is up to date with the play queue - if (playQueue.getItem() == item && playQueue.getItem() == syncedItem) { - playbackListener.sync(item, info); + if (playQueue.getItem() == item) { + playbackListener.onPlaybackSynchronize(item, info); } } + private void maybeSynchronizePlayer() { + maybeUnblock(); + maybeSync(); + } + /*////////////////////////////////////////////////////////////////////////// // MediaSource Loading //////////////////////////////////////////////////////////////////////////*/ @@ -404,8 +400,7 @@ public class MediaSourceManager { loaderReactor.add(loader); } - tryUnblock(); - sync(); + maybeSynchronizePlayer(); } private Single getLoadedMediaSource(@NonNull final PlayQueueItem stream) { @@ -431,17 +426,15 @@ public class MediaSourceManager { if (DEBUG) Log.d(TAG, "MediaSource - Loaded: [" + item.getTitle() + "] with url: " + item.getUrl()); + loadingItems.remove(item); + final int itemIndex = playQueue.indexOf(item); // Only update the playlist timeline for items at the current index or after. if (itemIndex >= playQueue.getIndex() && isCorrectionNeeded(item)) { if (DEBUG) Log.d(TAG, "MediaSource - Updating: [" + item.getTitle() + "] with url: " + item.getUrl()); - update(itemIndex, mediaSource); + update(itemIndex, mediaSource, this::maybeSynchronizePlayer); } - - loadingItems.remove(item); - tryUnblock(); - sync(); } /** @@ -533,10 +526,11 @@ public class MediaSourceManager { * this will modify the playback timeline prior to the index and may cause desynchronization * on the playing item between {@link PlayQueue} and {@link DynamicConcatenatingMediaSource}. * */ - private synchronized void update(final int index, @NonNull final MediaSource source) { + private synchronized void update(final int index, @NonNull final MediaSource source, + @Nullable final Runnable finalizingAction) { if (index < 0 || index >= sources.getSize()) return; sources.addMediaSource(index + 1, source, () -> - sources.removeMediaSource(index)); + sources.removeMediaSource(index, finalizingAction)); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java index c6fdde656..b37a269e2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java @@ -18,7 +18,7 @@ public interface PlaybackListener { * * May be called at any time. * */ - void block(); + void onPlaybackBlock(); /** * Called when the stream at the current queue index is ready. @@ -26,18 +26,16 @@ public interface PlaybackListener { * * May be called only when the player is blocked. * */ - void unblock(final MediaSource mediaSource); + void onPlaybackUnblock(final MediaSource mediaSource); /** * Called when the queue index is refreshed. * Signals to the listener to synchronize the player's window to the manager's * window. * - * Occurs once only per play queue item change. - * - * May be called only after unblock is called. + * May be called anytime at any amount once unblock is called. * */ - void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info); + void onPlaybackSynchronize(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info); /** * Requests the listener to resolve a stream info into a media source @@ -55,5 +53,5 @@ public interface PlaybackListener { * * May be called at any time. * */ - void shutdown(); + void onPlaybackShutdown(); } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index fc31ee02c..c207ed712 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -20,6 +20,7 @@ player_gesture_controls resume_on_audio_focus_gain popup_remember_size_pos_key + use_inexact_seek_key default_resolution 360p diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9dd5dcddb..6a8123bde 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -72,6 +72,8 @@ Black Remember popup size and position Remember last size and position of popup + Use fast inexact seek + Inexact seek allows the player to seek to positions faster with reduced precision Player gesture controls Use gestures to control the brightness and volume of the player Search suggestions diff --git a/app/src/main/res/xml/video_audio_settings.xml b/app/src/main/res/xml/video_audio_settings.xml index 7551834a2..e17b46ebc 100644 --- a/app/src/main/res/xml/video_audio_settings.xml +++ b/app/src/main/res/xml/video_audio_settings.xml @@ -100,5 +100,10 @@ android:summary="@string/popup_remember_size_pos_summary" android:title="@string/popup_remember_size_pos_title"/> +