-Added debouncing to index change reactor.

-Fixed repeat mode on background notification.
This commit is contained in:
John Zhen M 2017-09-05 23:49:00 -07:00 committed by John Zhen Mo
parent 7d7a6f7ccc
commit eb15c04254
10 changed files with 147 additions and 139 deletions

View File

@ -311,7 +311,7 @@ public class BackgroundPlayer extends Service {
super.onRepeatClicked(); super.onRepeatClicked();
int opacity = 255; int opacity = 255;
switch (currentRepeatMode) { switch (simpleExoPlayer.getRepeatMode()) {
case Player.REPEAT_MODE_OFF: case Player.REPEAT_MODE_OFF:
opacity = 77; opacity = 77;
break; break;
@ -338,17 +338,17 @@ public class BackgroundPlayer extends Service {
@Override @Override
public void onFastRewind() { public void onFastRewind() {
if (isPlayerBuffering()) return; if (!isPlayerReady()) return;
playQueue.setIndex(playQueue.getIndex() - 1); playQueue.offsetIndex(-1);
triggerProgressUpdate(); triggerProgressUpdate();
} }
@Override @Override
public void onFastForward() { public void onFastForward() {
if (isPlayerBuffering()) return; if (!isPlayerReady()) return;
playQueue.setIndex(playQueue.getIndex() + 1); playQueue.offsetIndex(+1);
triggerProgressUpdate(); triggerProgressUpdate();
} }

View File

@ -74,6 +74,8 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; 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.ExternalPlayQueue;
import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.PlayQueueItem;
@ -96,10 +98,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/ */
@SuppressWarnings({"WeakerAccess", "unused"}) @SuppressWarnings({"WeakerAccess", "unused"})
public abstract class BasePlayer implements Player.EventListener, public abstract class BasePlayer implements Player.EventListener,
AudioManager.OnAudioFocusChangeListener, MediaSourceManager.PlaybackListener { AudioManager.OnAudioFocusChangeListener, PlaybackListener {
// TODO: Check api version for deprecated audio manager methods // 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"; public static final String TAG = "BasePlayer";
protected Context context; protected Context context;
@ -139,7 +141,7 @@ public abstract class BasePlayer implements Player.EventListener,
// Playlist // Playlist
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
protected MediaSourceManager playbackManager; protected PlaybackManager playbackManager;
protected PlayQueue playQueue; protected PlayQueue playQueue;
protected int restoreQueueIndex; protected int restoreQueueIndex;
@ -270,7 +272,7 @@ public abstract class BasePlayer implements Player.EventListener,
playQueue = new ExternalPlayQueue(serviceId, nextPageUrl, info, index); playQueue = new ExternalPlayQueue(serviceId, nextPageUrl, info, index);
playQueue.init(); playQueue.init();
playbackManager = new MediaSourceManager(this, playQueue); playbackManager = new PlaybackManager(this, playQueue);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -281,7 +283,7 @@ public abstract class BasePlayer implements Player.EventListener,
playQueue = new SinglePlayQueue((StreamInfo) serializable, PlayQueueItem.DEFAULT_QUALITY); playQueue = new SinglePlayQueue((StreamInfo) serializable, PlayQueueItem.DEFAULT_QUALITY);
playQueue.init(); playQueue.init();
playbackManager = new MediaSourceManager(this, playQueue); playbackManager = new PlaybackManager(this, playQueue);
} }
public void initThumbnail() { public void initThumbnail() {
@ -494,9 +496,6 @@ public abstract class BasePlayer implements Player.EventListener,
// Repeat // Repeat
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
protected int currentRepeatMode = Player.REPEAT_MODE_OFF;
public void onRepeatClicked() { public void onRepeatClicked() {
if (DEBUG) Log.d(TAG, "onRepeatClicked() called"); if (DEBUG) Log.d(TAG, "onRepeatClicked() called");
@ -569,6 +568,10 @@ public abstract class BasePlayer implements Player.EventListener,
changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED); changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED);
break; break;
case Player.STATE_ENDED: // 4 case Player.STATE_ENDED: // 4
if (playQueue.getIndex() < playQueue.size() - 1) {
playQueue.offsetIndex(+1);
break;
}
changeState(STATE_COMPLETED); changeState(STATE_COMPLETED);
isPrepared = false; isPrepared = false;
break; break;
@ -589,9 +592,7 @@ public abstract class BasePlayer implements Player.EventListener,
int newIndex = simpleExoPlayer.getCurrentWindowIndex(); int newIndex = simpleExoPlayer.getCurrentWindowIndex();
if (DEBUG) Log.d(TAG, "onPositionDiscontinuity() called with: index = [" + newIndex + "]"); 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 @Override
public void block() { public void block() {
if (simpleExoPlayer == null) return; if (simpleExoPlayer == null) return;
Log.d(TAG, "Blocking..."); if (DEBUG) Log.d(TAG, "Blocking...");
simpleExoPlayer.stop(); simpleExoPlayer.stop();
@ -611,7 +612,7 @@ public abstract class BasePlayer implements Player.EventListener,
@Override @Override
public void unblock() { public void unblock() {
if (simpleExoPlayer == null) return; if (simpleExoPlayer == null) return;
Log.d(TAG, "Unblocking..."); if (DEBUG) Log.d(TAG, "Unblocking...");
if (restoreQueueIndex != playQueue.getIndex()) { if (restoreQueueIndex != playQueue.getIndex()) {
restoreQueueIndex = playQueue.getIndex(); restoreQueueIndex = playQueue.getIndex();
@ -626,7 +627,7 @@ public abstract class BasePlayer implements Player.EventListener,
@Override @Override
public void sync(final StreamInfo info, final int sortedStreamsIndex) { public void sync(final StreamInfo info, final int sortedStreamsIndex) {
if (simpleExoPlayer == null) return; if (simpleExoPlayer == null) return;
Log.d(TAG, "Syncing..."); if (DEBUG) Log.d(TAG, "Syncing...");
videoUrl = info.url; videoUrl = info.url;
videoThumbnailUrl = info.thumbnail_url; videoThumbnailUrl = info.thumbnail_url;
@ -635,11 +636,11 @@ public abstract class BasePlayer implements Player.EventListener,
initThumbnail(); initThumbnail();
if (simpleExoPlayer.getCurrentWindowIndex() != playbackManager.getCurrentSourceIndex()) { 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()) { if (simpleExoPlayer.getCurrentTimeline().getWindowCount() > playbackManager.getCurrentSourceIndex()) {
simpleExoPlayer.seekToDefaultPosition(playbackManager.getCurrentSourceIndex()); simpleExoPlayer.seekToDefaultPosition(playbackManager.getCurrentSourceIndex());
} else { } 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(); simpleExoPlayer.seekToDefaultPosition();
} }
} }
@ -649,7 +650,7 @@ public abstract class BasePlayer implements Player.EventListener,
@Override @Override
public void shutdown() { public void shutdown() {
Log.d(TAG, "Shutting down..."); if (DEBUG) Log.d(TAG, "Shutting down...");
playbackManager.dispose(); playbackManager.dispose();
playQueue.dispose(); playQueue.dispose();
@ -898,7 +899,7 @@ public abstract class BasePlayer implements Player.EventListener,
return playQueue; return playQueue;
} }
public boolean isPlayerBuffering() { public boolean isPlayerReady() {
return currentState == STATE_BUFFERING; return currentState == STATE_PLAYING || currentState == STATE_COMPLETED || currentState == STATE_PAUSED;
} }
} }

View File

@ -471,13 +471,13 @@ public class MainVideoPlayer extends Activity {
public boolean onDoubleTap(MotionEvent e) { 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 (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.isPlaying()) return false;
if (playerImpl.isPlayerBuffering()) return false; if (!playerImpl.isPlayerReady()) return false;
if (e.getX() > playerImpl.getRootView().getWidth() / 2) if (e.getX() > playerImpl.getRootView().getWidth() / 2)
playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() + 1); playerImpl.playQueue.offsetIndex(+1);
//playerImpl.onFastForward(); //playerImpl.onFastForward();
else else
playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() - 1); playerImpl.playQueue.offsetIndex(-1);
//playerImpl.onFastRewind(); //playerImpl.onFastRewind();
return true; return true;

View File

@ -580,14 +580,14 @@ public class PopupVideoPlayer extends Service {
public boolean onDoubleTap(MotionEvent e) { public boolean onDoubleTap(MotionEvent e) {
if (DEBUG) if (DEBUG)
Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); 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) { if (e.getX() > popupWidth / 2) {
//playerImpl.onFastForward(); //playerImpl.onFastForward();
playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() + 1); playerImpl.playQueue.offsetIndex(+1);
} else { } else {
//playerImpl.onFastRewind(); //playerImpl.onFastRewind();
playerImpl.playQueue.setIndex(playerImpl.playQueue.getIndex() - 1); playerImpl.playQueue.offsetIndex(-1);
} }
return true; return true;

View File

@ -57,6 +57,7 @@ import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.VideoStream; 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.PlayQueue;
import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.PlayQueueItem;
import org.schabi.newpipe.playlist.SinglePlayQueue; import org.schabi.newpipe.playlist.SinglePlayQueue;
@ -66,7 +67,6 @@ import org.schabi.newpipe.util.ListHelper;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Vector;
import static org.schabi.newpipe.util.AnimationUtils.animateView; 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 = new SinglePlayQueue((StreamInfo) serializable, sortedStreamsIndex);
playQueue.init(); playQueue.init();
playbackManager = new MediaSourceManager(this, playQueue); playbackManager = new PlaybackManager(this, playQueue);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -239,7 +239,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
playQueue = (PlayQueue) serializable; playQueue = (PlayQueue) serializable;
playQueue.init(); playQueue.init();
playbackManager = new MediaSourceManager(this, playQueue); playbackManager = new PlaybackManager(this, playQueue);
} }
@Override @Override

View File

@ -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();
}

View File

@ -1,4 +1,4 @@
package org.schabi.newpipe.player; package org.schabi.newpipe.player.playback;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -25,11 +25,11 @@ import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer; import io.reactivex.functions.Consumer;
class MediaSourceManager { public class PlaybackManager {
private final String TAG = "MediaSourceManager@" + Integer.toHexString(hashCode()); private final String TAG = "PlaybackManager@" + Integer.toHexString(hashCode());
// One-side rolling window size for default loading // One-side rolling window size for default loading
// Effectively loads WINDOW_SIZE * 2 streams // 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 PlaybackListener playbackListener;
private final PlayQueue playQueue; private final PlayQueue playQueue;
@ -41,43 +41,13 @@ class MediaSourceManager {
private List<Integer> sourceToQueueIndex; private List<Integer> sourceToQueueIndex;
private Subscription playQueueReactor; private Subscription playQueueReactor;
private Subscription loadingReactor; private Disposable syncReactor;
private CompositeDisposable disposables; private CompositeDisposable disposables;
private boolean isBlocked; private boolean isBlocked;
interface PlaybackListener { public PlaybackManager(@NonNull final PlaybackListener listener,
/* @NonNull final PlayQueue playQueue) {
* 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) {
this.playbackListener = listener; this.playbackListener = listener;
this.playQueue = playQueue; this.playQueue = playQueue;
@ -98,25 +68,28 @@ class MediaSourceManager {
/* /*
* Returns the media source index of the currently playing stream. * Returns the media source index of the currently playing stream.
* */ * */
int getCurrentSourceIndex() { public int getCurrentSourceIndex() {
return sourceToQueueIndex.indexOf(playQueue.getIndex()); return sourceToQueueIndex.indexOf(playQueue.getIndex());
} }
@NonNull @NonNull
DynamicConcatenatingMediaSource getMediaSource() { public DynamicConcatenatingMediaSource getMediaSource() {
return sources; return sources;
} }
/* /*
* Called when the player has transitioned to another stream. * Called when the player has transitioned to another stream.
* */ * */
void refresh(final int newSourceIndex) { public void refresh(final int newSourceIndex) {
if (sourceToQueueIndex.indexOf(newSourceIndex) != -1) { if (sourceToQueueIndex.indexOf(newSourceIndex) != -1 && newSourceIndex == getCurrentSourceIndex() + 1) {
playQueue.setIndex(sourceToQueueIndex.indexOf(newSourceIndex)); 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 // ignore error checking for now, just remove the current index
if (error == null || !tryBlock()) return; if (error == null || !tryBlock()) return;
@ -127,27 +100,28 @@ class MediaSourceManager {
load(); load();
} }
int queueIndexOf(final int sourceIndex) { public void updateCurrent(final int newSortedStreamsIndex) {
return sourceIndex < sourceToQueueIndex.size() ? sourceToQueueIndex.get(sourceIndex) : -1;
}
void updateCurrent(final int newSortedStreamsIndex) {
if (!tryBlock()) return; if (!tryBlock()) return;
PlayQueueItem item = playQueue.getCurrent(); PlayQueueItem item = playQueue.getCurrent();
item.setSortedQualityIndex(newSortedStreamsIndex); item.setSortedQualityIndex(newSortedStreamsIndex);
resetSources(); resetSources();
load(); load();
} }
void dispose() { public void dispose() {
if (loadingReactor != null) loadingReactor.cancel();
if (playQueueReactor != null) playQueueReactor.cancel(); if (playQueueReactor != null) playQueueReactor.cancel();
if (disposables != null) disposables.dispose(); if (disposables != null) disposables.dispose();
if (syncReactor != null) syncReactor.dispose();
if (sources != null) sources.releaseSource();
if (sourceToQueueIndex != null) sourceToQueueIndex.clear();
loadingReactor = null;
playQueueReactor = null; playQueueReactor = null;
disposables = null; disposables = null;
syncReactor = null;
sources = null;
sourceToQueueIndex = null;
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -182,8 +156,8 @@ class MediaSourceManager {
case SWAP: case SWAP:
final SwapEvent swapEvent = (SwapEvent) event; final SwapEvent swapEvent = (SwapEvent) event;
swap(swapEvent.getFrom(), swapEvent.getTo()); swap(swapEvent.getFrom(), swapEvent.getTo());
load();
break; break;
case NEXT:
default: default:
break; break;
} }
@ -240,14 +214,18 @@ class MediaSourceManager {
/* /*
* Responds to a SELECT event. * Responds to a SELECT event.
* When a change occur, the manager prepares by loading more. * If the selected item is already loaded, then we simply synchronize and
* If the current item has not been fully loaded, * 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() { private void onSelect() {
if (isCurrentIndexLoaded()) { if (isCurrentIndexLoaded() && !isBlocked) {
sync(); sync();
} else { } else {
tryBlock(); tryBlock();
resetSources();
} }
load(); load();
@ -256,14 +234,14 @@ class MediaSourceManager {
private void sync() { private void sync() {
final PlayQueueItem currentItem = playQueue.getCurrent(); final PlayQueueItem currentItem = playQueue.getCurrent();
final Consumer<StreamInfo> onSuccess = new Consumer<StreamInfo>() { final Consumer<StreamInfo> syncPlayback = new Consumer<StreamInfo>() {
@Override @Override
public void accept(StreamInfo streamInfo) throws Exception { public void accept(StreamInfo streamInfo) throws Exception {
playbackListener.sync(streamInfo, currentItem.getSortedQualityIndex()); playbackListener.sync(streamInfo, currentItem.getSortedQualityIndex());
} }
}; };
currentItem.getStream().subscribe(onSuccess); currentItem.getStream().subscribe(syncPlayback);
} }
private void load() { private void load() {
@ -287,11 +265,13 @@ class MediaSourceManager {
item.getStream().subscribe(new SingleObserver<StreamInfo>() { item.getStream().subscribe(new SingleObserver<StreamInfo>() {
@Override @Override
public void onSubscribe(@NonNull Disposable d) { public void onSubscribe(@NonNull Disposable d) {
if (disposables != null) { if (disposables == null) {
disposables.add(d);
} else {
d.dispose(); d.dispose();
return;
} }
if (disposables.size() > 8) disposables.clear();
disposables.add(d);
} }
@Override @Override
@ -321,16 +301,6 @@ class MediaSourceManager {
// Media Source List Manipulation // 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 // Insert source into playlist with position in respect to the play queue
// If the play queue index already exists, then the insert is ignored // If the play queue index already exists, then the insert is ignored
private void insert(final int queueIndex, final MediaSource source) { private void insert(final int queueIndex, final MediaSource source) {

View File

@ -5,12 +5,8 @@ import android.util.Log;
import org.reactivestreams.Subscriber; import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription; 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.AppendEvent;
import org.schabi.newpipe.playlist.events.InitEvent; 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.PlayQueueMessage;
import org.schabi.newpipe.playlist.events.RemoveEvent; import org.schabi.newpipe.playlist.events.RemoveEvent;
import org.schabi.newpipe.playlist.events.SelectEvent; import org.schabi.newpipe.playlist.events.SelectEvent;
@ -21,6 +17,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import io.reactivex.BackpressureStrategy; import io.reactivex.BackpressureStrategy;
@ -29,12 +26,15 @@ import io.reactivex.subjects.BehaviorSubject;
public abstract class PlayQueue implements Serializable { public abstract class PlayQueue implements Serializable {
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode()); 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<PlayQueueItem> streams; private final ArrayList<PlayQueueItem> streams;
private final AtomicInteger queueIndex; private final AtomicInteger queueIndex;
private transient BehaviorSubject<PlayQueueMessage> eventBroadcast; private transient BehaviorSubject<PlayQueueMessage> streamsEventBroadcast;
private transient BehaviorSubject<PlayQueueMessage> indexEventBroadcast;
private transient Flowable<PlayQueueMessage> broadcastReceiver; private transient Flowable<PlayQueueMessage> broadcastReceiver;
private transient Subscription reportingReactor; private transient Subscription reportingReactor;
@ -54,16 +54,19 @@ public abstract class PlayQueue implements Serializable {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public void init() { public void init() {
eventBroadcast = BehaviorSubject.create(); streamsEventBroadcast = BehaviorSubject.create();
broadcastReceiver = eventBroadcast indexEventBroadcast = BehaviorSubject.create();
.startWith(new InitEvent())
.toFlowable(BackpressureStrategy.BUFFER); 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()); if (DEBUG) broadcastReceiver.subscribe(getSelfReporter());
} }
public void dispose() { public void dispose() {
eventBroadcast.onComplete(); streamsEventBroadcast.onComplete();
if (reportingReactor != null) reportingReactor.cancel(); if (reportingReactor != null) reportingReactor.cancel();
reportingReactor = null; reportingReactor = null;
@ -121,9 +124,15 @@ public abstract class PlayQueue implements Serializable {
// Write ops // 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)); 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) { protected synchronized void append(final PlayQueueItem item) {
@ -137,7 +146,9 @@ public abstract class PlayQueue implements Serializable {
} }
public synchronized void remove(final int index) { 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); streams.remove(index);
// Nudge the index if it becomes larger than the queue size // 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); queueIndex.set(size() - 1);
} }
broadcast(new RemoveEvent(index)); broadcast(new RemoveEvent(index, isCurrent));
} }
protected synchronized void swap(final int source, final int target) { protected synchronized void swap(final int source, final int target) {
if (source < 0 || target < 0) return;
final List<PlayQueueItem> items = streams; final List<PlayQueueItem> items = streams;
if (source < items.size() && target < items.size()) { if (source < items.size() && target < items.size()) {
// Swap two items // Swap two items
@ -174,7 +187,7 @@ public abstract class PlayQueue implements Serializable {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private void broadcast(final PlayQueueMessage event) { private void broadcast(final PlayQueueMessage event) {
eventBroadcast.onNext(event); streamsEventBroadcast.onNext(event);
} }
private Subscriber<PlayQueueMessage> getSelfReporter() { private Subscriber<PlayQueueMessage> getSelfReporter() {

View File

@ -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;
}
}

View File

@ -3,17 +3,23 @@ package org.schabi.newpipe.playlist.events;
public class RemoveEvent implements PlayQueueMessage { public class RemoveEvent implements PlayQueueMessage {
final private int index; final private int index;
final private boolean isCurrent;
@Override @Override
public PlayQueueEvent type() { public PlayQueueEvent type() {
return PlayQueueEvent.REMOVE; return PlayQueueEvent.REMOVE;
} }
public RemoveEvent(final int index) { public RemoveEvent(final int index, final boolean isCurrent) {
this.index = index; this.index = index;
this.isCurrent = isCurrent;
} }
public int index() { public int index() {
return index; return index;
} }
public boolean isCurrent() {
return isCurrent;
}
} }