-Added debouncing to index change reactor.
-Fixed repeat mode on background notification.
This commit is contained in:
parent
7d7a6f7ccc
commit
eb15c04254
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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) {
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue