From eb15c04254ee9f26c5be0fd5a7706154f8747081 Mon Sep 17 00:00:00 2001 From: John Zhen M Date: Tue, 5 Sep 2017 23:49:00 -0700 Subject: [PATCH] -Added debouncing to index change reactor. -Fixed repeat mode on background notification. --- .../newpipe/player/BackgroundPlayer.java | 10 +- .../org/schabi/newpipe/player/BasePlayer.java | 39 ++++--- .../newpipe/player/MainVideoPlayer.java | 6 +- .../newpipe/player/PopupVideoPlayer.java | 6 +- .../schabi/newpipe/player/VideoPlayer.java | 6 +- .../player/playback/PlaybackListener.java | 37 ++++++ .../PlaybackManager.java} | 110 +++++++----------- .../schabi/newpipe/playlist/PlayQueue.java | 45 ++++--- .../newpipe/playlist/events/NextEvent.java | 19 --- .../newpipe/playlist/events/RemoveEvent.java | 8 +- 10 files changed, 147 insertions(+), 139 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java rename app/src/main/java/org/schabi/newpipe/player/{MediaSourceManager.java => playback/PlaybackManager.java} (78%) delete mode 100644 app/src/main/java/org/schabi/newpipe/playlist/events/NextEvent.java 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 34865b1ba..0cc6a7628 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -311,7 +311,7 @@ public class BackgroundPlayer extends Service { super.onRepeatClicked(); int opacity = 255; - switch (currentRepeatMode) { + switch (simpleExoPlayer.getRepeatMode()) { case Player.REPEAT_MODE_OFF: opacity = 77; break; @@ -338,17 +338,17 @@ public class BackgroundPlayer extends Service { @Override public void onFastRewind() { - if (isPlayerBuffering()) return; + if (!isPlayerReady()) return; - playQueue.setIndex(playQueue.getIndex() - 1); + playQueue.offsetIndex(-1); triggerProgressUpdate(); } @Override public void onFastForward() { - if (isPlayerBuffering()) return; + if (!isPlayerReady()) return; - playQueue.setIndex(playQueue.getIndex() + 1); + playQueue.offsetIndex(+1); triggerProgressUpdate(); } 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 773080d94..526084552 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -74,6 +74,8 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; +import org.schabi.newpipe.player.playback.PlaybackManager; +import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.playlist.ExternalPlayQueue; import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.PlayQueueItem; @@ -96,10 +98,10 @@ import java.util.concurrent.atomic.AtomicBoolean; */ @SuppressWarnings({"WeakerAccess", "unused"}) public abstract class BasePlayer implements Player.EventListener, - AudioManager.OnAudioFocusChangeListener, MediaSourceManager.PlaybackListener { + AudioManager.OnAudioFocusChangeListener, PlaybackListener { // TODO: Check api version for deprecated audio manager methods - public static final boolean DEBUG = true; + public static final boolean DEBUG = false; public static final String TAG = "BasePlayer"; protected Context context; @@ -139,7 +141,7 @@ public abstract class BasePlayer implements Player.EventListener, // Playlist //////////////////////////////////////////////////////////////////////////*/ - protected MediaSourceManager playbackManager; + protected PlaybackManager playbackManager; protected PlayQueue playQueue; protected int restoreQueueIndex; @@ -270,7 +272,7 @@ public abstract class BasePlayer implements Player.EventListener, playQueue = new ExternalPlayQueue(serviceId, nextPageUrl, info, index); playQueue.init(); - playbackManager = new MediaSourceManager(this, playQueue); + playbackManager = new PlaybackManager(this, playQueue); } @SuppressWarnings("unchecked") @@ -281,7 +283,7 @@ public abstract class BasePlayer implements Player.EventListener, playQueue = new SinglePlayQueue((StreamInfo) serializable, PlayQueueItem.DEFAULT_QUALITY); playQueue.init(); - playbackManager = new MediaSourceManager(this, playQueue); + playbackManager = new PlaybackManager(this, playQueue); } public void initThumbnail() { @@ -494,9 +496,6 @@ public abstract class BasePlayer implements Player.EventListener, // Repeat //////////////////////////////////////////////////////////////////////////*/ - protected int currentRepeatMode = Player.REPEAT_MODE_OFF; - - public void onRepeatClicked() { if (DEBUG) Log.d(TAG, "onRepeatClicked() called"); @@ -569,6 +568,10 @@ public abstract class BasePlayer implements Player.EventListener, changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED); break; case Player.STATE_ENDED: // 4 + if (playQueue.getIndex() < playQueue.size() - 1) { + playQueue.offsetIndex(+1); + break; + } changeState(STATE_COMPLETED); isPrepared = false; break; @@ -589,9 +592,7 @@ public abstract class BasePlayer implements Player.EventListener, int newIndex = simpleExoPlayer.getCurrentWindowIndex(); if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with: index = [" + newIndex + "]"); - if (newIndex == playbackManager.getCurrentSourceIndex() + 1) { - playbackManager.refresh(newIndex); - } + playbackManager.refresh(newIndex); } /*////////////////////////////////////////////////////////////////////////// @@ -601,7 +602,7 @@ public abstract class BasePlayer implements Player.EventListener, @Override public void block() { if (simpleExoPlayer == null) return; - Log.d(TAG, "Blocking..."); + if (DEBUG) Log.d(TAG, "Blocking..."); simpleExoPlayer.stop(); @@ -611,7 +612,7 @@ public abstract class BasePlayer implements Player.EventListener, @Override public void unblock() { if (simpleExoPlayer == null) return; - Log.d(TAG, "Unblocking..."); + if (DEBUG) Log.d(TAG, "Unblocking..."); if (restoreQueueIndex != playQueue.getIndex()) { restoreQueueIndex = playQueue.getIndex(); @@ -626,7 +627,7 @@ public abstract class BasePlayer implements Player.EventListener, @Override public void sync(final StreamInfo info, final int sortedStreamsIndex) { if (simpleExoPlayer == null) return; - Log.d(TAG, "Syncing..."); + if (DEBUG) Log.d(TAG, "Syncing..."); videoUrl = info.url; videoThumbnailUrl = info.thumbnail_url; @@ -635,11 +636,11 @@ public abstract class BasePlayer implements Player.EventListener, initThumbnail(); if (simpleExoPlayer.getCurrentWindowIndex() != playbackManager.getCurrentSourceIndex()) { - Log.w(TAG, "Rewinding to correct window"); + if (DEBUG) Log.w(TAG, "Rewinding to correct window"); if (simpleExoPlayer.getCurrentTimeline().getWindowCount() > playbackManager.getCurrentSourceIndex()) { simpleExoPlayer.seekToDefaultPosition(playbackManager.getCurrentSourceIndex()); } else { - Toast.makeText(context, "Player out of sync", Toast.LENGTH_SHORT).show(); + Toast.makeText(context, "Play Queue out of sync", Toast.LENGTH_SHORT).show(); simpleExoPlayer.seekToDefaultPosition(); } } @@ -649,7 +650,7 @@ public abstract class BasePlayer implements Player.EventListener, @Override public void shutdown() { - Log.d(TAG, "Shutting down..."); + if (DEBUG) Log.d(TAG, "Shutting down..."); playbackManager.dispose(); playQueue.dispose(); @@ -898,7 +899,7 @@ public abstract class BasePlayer implements Player.EventListener, return playQueue; } - public boolean isPlayerBuffering() { - return currentState == STATE_BUFFERING; + public boolean isPlayerReady() { + return currentState == STATE_PLAYING || currentState == STATE_COMPLETED || currentState == STATE_PAUSED; } } 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 ef3741f24..a2f1c3a9c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -471,13 +471,13 @@ public class MainVideoPlayer extends Activity { public boolean onDoubleTap(MotionEvent e) { if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); //if (!playerImpl.isPlaying()) return false; - if (playerImpl.isPlayerBuffering()) return false; + if (!playerImpl.isPlayerReady()) return false; if (e.getX() > playerImpl.getRootView().getWidth() / 2) - playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() + 1); + playerImpl.playQueue.offsetIndex(+1); //playerImpl.onFastForward(); else - playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() - 1); + playerImpl.playQueue.offsetIndex(-1); //playerImpl.onFastRewind(); return true; 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 b739c1c18..c8eb98263 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -580,14 +580,14 @@ public class PopupVideoPlayer extends Service { public boolean onDoubleTap(MotionEvent e) { if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); - if (!playerImpl.isPlaying() || playerImpl.isPlayerBuffering()) return false; + if (!playerImpl.isPlaying() || !playerImpl.isPlayerReady()) return false; if (e.getX() > popupWidth / 2) { //playerImpl.onFastForward(); - playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() + 1); + playerImpl.playQueue.offsetIndex(+1); } else { //playerImpl.onFastRewind(); - playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() - 1); + playerImpl.playQueue.offsetIndex(-1); } return true; 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 97a476e50..4837a00c0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -57,6 +57,7 @@ import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; +import org.schabi.newpipe.player.playback.PlaybackManager; import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.SinglePlayQueue; @@ -66,7 +67,6 @@ import org.schabi.newpipe.util.ListHelper; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import java.util.Vector; import static org.schabi.newpipe.util.AnimationUtils.animateView; @@ -226,7 +226,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. playQueue = new SinglePlayQueue((StreamInfo) serializable, sortedStreamsIndex); playQueue.init(); - playbackManager = new MediaSourceManager(this, playQueue); + playbackManager = new PlaybackManager(this, playQueue); } @SuppressWarnings("unchecked") @@ -239,7 +239,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer. playQueue = (PlayQueue) serializable; playQueue.init(); - playbackManager = new MediaSourceManager(this, playQueue); + playbackManager = new PlaybackManager(this, playQueue); } @Override 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 new file mode 100644 index 000000000..9474fece6 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java @@ -0,0 +1,37 @@ +package org.schabi.newpipe.player.playback; + +import com.google.android.exoplayer2.source.MediaSource; + +import org.schabi.newpipe.extractor.stream.StreamInfo; + +public interface PlaybackListener { + /* + * Called when the stream at the current queue index is not ready yet. + * Signals to the listener to block the player from playing anything. + * */ + void block(); + + /* + * Called when the stream at the current queue index is ready. + * Signals to the listener to resume the player. + * May be called at any time, even when the player is unblocked. + * */ + void unblock(); + + /* + * Called when the queue index is refreshed. + * Signals to the listener to synchronize the player's window to the manager's + * window. + * + * CAN ONLY BE CALLED ONCE UNBLOCKED! + * */ + void sync(final StreamInfo info, final int sortedStreamsIndex); + + /* + * Requests the listener to resolve a stream info into a media source respective + * of the listener's implementation (background, popup or main video player), + * */ + MediaSource sourceOf(final StreamInfo info, final int sortedStreamsIndex); + + void shutdown(); +} diff --git a/app/src/main/java/org/schabi/newpipe/player/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackManager.java similarity index 78% rename from app/src/main/java/org/schabi/newpipe/player/MediaSourceManager.java rename to app/src/main/java/org/schabi/newpipe/player/playback/PlaybackManager.java index c7e9fb64d..28527f1fb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackManager.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player; +package org.schabi.newpipe.player.playback; import android.support.annotation.Nullable; @@ -25,11 +25,11 @@ import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; -class MediaSourceManager { - private final String TAG = "MediaSourceManager@" + Integer.toHexString(hashCode()); +public class PlaybackManager { + private final String TAG = "PlaybackManager@" + Integer.toHexString(hashCode()); // One-side rolling window size for default loading // Effectively loads WINDOW_SIZE * 2 streams - private static final int WINDOW_SIZE = 3; + private static final int WINDOW_SIZE = 2; private final PlaybackListener playbackListener; private final PlayQueue playQueue; @@ -41,43 +41,13 @@ class MediaSourceManager { private List sourceToQueueIndex; private Subscription playQueueReactor; - private Subscription loadingReactor; + private Disposable syncReactor; private CompositeDisposable disposables; private boolean isBlocked; - interface PlaybackListener { - /* - * Called when the stream at the current queue index is not ready yet. - * Signals to the listener to block the player from playing anything. - * */ - void block(); - - /* - * Called when the stream at the current queue index is ready. - * Signals to the listener to resume the player. - * May be called at any time, even when the player is unblocked. - * */ - void unblock(); - - /* - * Called when the queue index is refreshed. - * Signals to the listener to synchronize the player's window to the manager's - * window. - * */ - void sync(final StreamInfo info, final int sortedStreamsIndex); - - /* - * Requests the listener to resolve a stream info into a media source respective - * of the listener's implementation (background, popup or main video player), - * */ - MediaSource sourceOf(final StreamInfo info, final int sortedStreamsIndex); - - void shutdown(); - } - - MediaSourceManager(@NonNull final MediaSourceManager.PlaybackListener listener, - @NonNull final PlayQueue playQueue) { + public PlaybackManager(@NonNull final PlaybackListener listener, + @NonNull final PlayQueue playQueue) { this.playbackListener = listener; this.playQueue = playQueue; @@ -98,25 +68,28 @@ class MediaSourceManager { /* * Returns the media source index of the currently playing stream. * */ - int getCurrentSourceIndex() { + public int getCurrentSourceIndex() { return sourceToQueueIndex.indexOf(playQueue.getIndex()); } @NonNull - DynamicConcatenatingMediaSource getMediaSource() { + public DynamicConcatenatingMediaSource getMediaSource() { return sources; } /* * Called when the player has transitioned to another stream. * */ - void refresh(final int newSourceIndex) { - if (sourceToQueueIndex.indexOf(newSourceIndex) != -1) { - playQueue.setIndex(sourceToQueueIndex.indexOf(newSourceIndex)); + public void refresh(final int newSourceIndex) { + if (sourceToQueueIndex.indexOf(newSourceIndex) != -1 && newSourceIndex == getCurrentSourceIndex() + 1) { + playQueue.offsetIndex(+1); + + // free up some memory + if (sourceToQueueIndex.size() > 1) remove(sourceToQueueIndex.get(0)); } } - void report(final Exception error) { + public void report(final Exception error) { // ignore error checking for now, just remove the current index if (error == null || !tryBlock()) return; @@ -127,27 +100,28 @@ class MediaSourceManager { load(); } - int queueIndexOf(final int sourceIndex) { - return sourceIndex < sourceToQueueIndex.size() ? sourceToQueueIndex.get(sourceIndex) : -1; - } - - void updateCurrent(final int newSortedStreamsIndex) { + public void updateCurrent(final int newSortedStreamsIndex) { if (!tryBlock()) return; PlayQueueItem item = playQueue.getCurrent(); item.setSortedQualityIndex(newSortedStreamsIndex); + resetSources(); load(); } - void dispose() { - if (loadingReactor != null) loadingReactor.cancel(); + public void dispose() { if (playQueueReactor != null) playQueueReactor.cancel(); if (disposables != null) disposables.dispose(); + if (syncReactor != null) syncReactor.dispose(); + if (sources != null) sources.releaseSource(); + if (sourceToQueueIndex != null) sourceToQueueIndex.clear(); - loadingReactor = null; playQueueReactor = null; disposables = null; + syncReactor = null; + sources = null; + sourceToQueueIndex = null; } /*////////////////////////////////////////////////////////////////////////// @@ -182,8 +156,8 @@ class MediaSourceManager { case SWAP: final SwapEvent swapEvent = (SwapEvent) event; swap(swapEvent.getFrom(), swapEvent.getTo()); + load(); break; - case NEXT: default: break; } @@ -240,14 +214,18 @@ class MediaSourceManager { /* * Responds to a SELECT event. - * When a change occur, the manager prepares by loading more. - * If the current item has not been fully loaded, + * If the selected item is already loaded, then we simply synchronize and + * start loading some more items. + * + * If the current item has not been fully loaded, then the player will be + * blocked. The sources will be reset and reloaded, to conserve memory. * */ private void onSelect() { - if (isCurrentIndexLoaded()) { + if (isCurrentIndexLoaded() && !isBlocked) { sync(); } else { tryBlock(); + resetSources(); } load(); @@ -256,14 +234,14 @@ class MediaSourceManager { private void sync() { final PlayQueueItem currentItem = playQueue.getCurrent(); - final Consumer onSuccess = new Consumer() { + final Consumer syncPlayback = new Consumer() { @Override public void accept(StreamInfo streamInfo) throws Exception { playbackListener.sync(streamInfo, currentItem.getSortedQualityIndex()); } }; - currentItem.getStream().subscribe(onSuccess); + currentItem.getStream().subscribe(syncPlayback); } private void load() { @@ -287,11 +265,13 @@ class MediaSourceManager { item.getStream().subscribe(new SingleObserver() { @Override public void onSubscribe(@NonNull Disposable d) { - if (disposables != null) { - disposables.add(d); - } else { + if (disposables == null) { d.dispose(); + return; } + + if (disposables.size() > 8) disposables.clear(); + disposables.add(d); } @Override @@ -321,16 +301,6 @@ class MediaSourceManager { // Media Source List Manipulation //////////////////////////////////////////////////////////////////////////*/ - private void reset(final int queueIndex) { - if (queueIndex < 0) return; - - final int sourceIndex = sourceToQueueIndex.indexOf(queueIndex); - if (sourceIndex != -1) { - sourceToQueueIndex.remove(sourceIndex); - sources.removeMediaSource(sourceIndex); - } - } - // Insert source into playlist with position in respect to the play queue // If the play queue index already exists, then the insert is ignored private void insert(final int queueIndex, final MediaSource source) { diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java index aee56230c..01b98e19f 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java @@ -5,12 +5,8 @@ import android.util.Log; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; -import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.StreamingService; -import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.playlist.events.AppendEvent; import org.schabi.newpipe.playlist.events.InitEvent; -import org.schabi.newpipe.playlist.events.NextEvent; import org.schabi.newpipe.playlist.events.PlayQueueMessage; import org.schabi.newpipe.playlist.events.RemoveEvent; import org.schabi.newpipe.playlist.events.SelectEvent; @@ -21,6 +17,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import io.reactivex.BackpressureStrategy; @@ -29,12 +26,15 @@ import io.reactivex.subjects.BehaviorSubject; public abstract class PlayQueue implements Serializable { private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode()); - public static final boolean DEBUG = true; + private final int INDEX_CHANGE_DEBOUNCE = 350; + + public static final boolean DEBUG = false; private final ArrayList streams; private final AtomicInteger queueIndex; - private transient BehaviorSubject eventBroadcast; + private transient BehaviorSubject streamsEventBroadcast; + private transient BehaviorSubject indexEventBroadcast; private transient Flowable broadcastReceiver; private transient Subscription reportingReactor; @@ -54,16 +54,19 @@ public abstract class PlayQueue implements Serializable { //////////////////////////////////////////////////////////////////////////*/ public void init() { - eventBroadcast = BehaviorSubject.create(); - broadcastReceiver = eventBroadcast - .startWith(new InitEvent()) - .toFlowable(BackpressureStrategy.BUFFER); + streamsEventBroadcast = BehaviorSubject.create(); + indexEventBroadcast = BehaviorSubject.create(); + + broadcastReceiver = Flowable.merge( + streamsEventBroadcast.toFlowable(BackpressureStrategy.BUFFER), + indexEventBroadcast.toFlowable(BackpressureStrategy.BUFFER).debounce(INDEX_CHANGE_DEBOUNCE, TimeUnit.MILLISECONDS) + ).startWith(new InitEvent()); if (DEBUG) broadcastReceiver.subscribe(getSelfReporter()); } public void dispose() { - eventBroadcast.onComplete(); + streamsEventBroadcast.onComplete(); if (reportingReactor != null) reportingReactor.cancel(); reportingReactor = null; @@ -121,9 +124,15 @@ public abstract class PlayQueue implements Serializable { // Write ops //////////////////////////////////////////////////////////////////////////*/ - public synchronized void setIndex(final int index) { + private synchronized void setIndex(final int index) { + if (index < 0 || index >= streams.size()) return; + queueIndex.set(Math.min(Math.max(0, index), streams.size() - 1)); - broadcast(new SelectEvent(index)); + indexEventBroadcast.onNext(new SelectEvent(index)); + } + + public synchronized void offsetIndex(final int offset) { + setIndex(getIndex() + offset); } protected synchronized void append(final PlayQueueItem item) { @@ -137,7 +146,9 @@ public abstract class PlayQueue implements Serializable { } public synchronized void remove(final int index) { - if (index >= streams.size()) return; + if (index >= streams.size() || index < 0) return; + + final boolean isCurrent = index == getIndex(); streams.remove(index); // Nudge the index if it becomes larger than the queue size @@ -145,10 +156,12 @@ public abstract class PlayQueue implements Serializable { queueIndex.set(size() - 1); } - broadcast(new RemoveEvent(index)); + broadcast(new RemoveEvent(index, isCurrent)); } protected synchronized void swap(final int source, final int target) { + if (source < 0 || target < 0) return; + final List items = streams; if (source < items.size() && target < items.size()) { // Swap two items @@ -174,7 +187,7 @@ public abstract class PlayQueue implements Serializable { //////////////////////////////////////////////////////////////////////////*/ private void broadcast(final PlayQueueMessage event) { - eventBroadcast.onNext(event); + streamsEventBroadcast.onNext(event); } private Subscriber getSelfReporter() { diff --git a/app/src/main/java/org/schabi/newpipe/playlist/events/NextEvent.java b/app/src/main/java/org/schabi/newpipe/playlist/events/NextEvent.java deleted file mode 100644 index e11842643..000000000 --- a/app/src/main/java/org/schabi/newpipe/playlist/events/NextEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.schabi.newpipe.playlist.events; - - -public class NextEvent implements PlayQueueMessage { - final private int newIndex; - - @Override - public PlayQueueEvent type() { - return PlayQueueEvent.NEXT; - } - - public NextEvent(final int newIndex) { - this.newIndex = newIndex; - } - - public int index() { - return newIndex; - } -} diff --git a/app/src/main/java/org/schabi/newpipe/playlist/events/RemoveEvent.java b/app/src/main/java/org/schabi/newpipe/playlist/events/RemoveEvent.java index 0250560ec..d7ae6fb8a 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/events/RemoveEvent.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/events/RemoveEvent.java @@ -3,17 +3,23 @@ package org.schabi.newpipe.playlist.events; public class RemoveEvent implements PlayQueueMessage { final private int index; + final private boolean isCurrent; @Override public PlayQueueEvent type() { return PlayQueueEvent.REMOVE; } - public RemoveEvent(final int index) { + public RemoveEvent(final int index, final boolean isCurrent) { this.index = index; + this.isCurrent = isCurrent; } public int index() { return index; } + + public boolean isCurrent() { + return isCurrent; + } }