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

View File

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

View File

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

View File

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

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.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

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;
@ -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<Integer> 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<StreamInfo> onSuccess = new Consumer<StreamInfo>() {
final Consumer<StreamInfo> syncPlayback = new Consumer<StreamInfo>() {
@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<StreamInfo>() {
@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) {

View File

@ -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<PlayQueueItem> streams;
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 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<PlayQueueItem> 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<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 {
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;
}
}