-Fixed bad window timeline caused by reusing media source on unblocking.

-Fixed timeline recovery skipping.
-Fixed timeline updates resumes playing when player is paused.
This commit is contained in:
John Zhen M 2017-09-14 08:44:09 -07:00 committed by John Zhen Mo
parent e9cdb28a06
commit 6b16b08712
8 changed files with 112 additions and 63 deletions

View File

@ -458,8 +458,8 @@ public class BackgroundPlayer extends Service {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onLoading() { public void onBlocked() {
super.onLoading(); super.onBlocked();
setControlsOpacity(77); setControlsOpacity(77);
updateNotification(-1); updateNotification(-1);

View File

@ -136,7 +136,7 @@ public abstract class BasePlayer implements Player.EventListener,
public static final String RESTORE_WINDOW_POS = "restore_window_pos"; public static final String RESTORE_WINDOW_POS = "restore_window_pos";
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Playlist // Playback
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
protected PlaybackManager playbackManager; protected PlaybackManager playbackManager;
@ -157,6 +157,7 @@ public abstract class BasePlayer implements Player.EventListener,
protected SimpleExoPlayer simpleExoPlayer; protected SimpleExoPlayer simpleExoPlayer;
protected boolean isPrepared = false; protected boolean isPrepared = false;
protected boolean wasPlaying = false;
protected CacheDataSourceFactory cacheDataSourceFactory; protected CacheDataSourceFactory cacheDataSourceFactory;
protected final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); protected final DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
@ -218,6 +219,7 @@ public abstract class BasePlayer implements Player.EventListener,
final RenderersFactory renderFactory = new DefaultRenderersFactory(context); final RenderersFactory renderFactory = new DefaultRenderersFactory(context);
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(renderFactory, defaultTrackSelector, loadControl); simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(renderFactory, defaultTrackSelector, loadControl);
simpleExoPlayer.addListener(this); simpleExoPlayer.addListener(this);
simpleExoPlayer.setPlayWhenReady(true);
} }
public void initListeners() {} public void initListeners() {}
@ -275,10 +277,8 @@ public abstract class BasePlayer implements Player.EventListener,
} }
} }
playQueue = new ExternalPlayQueue(serviceId, nextPageUrl, info, index); final PlayQueue queue = new ExternalPlayQueue(serviceId, nextPageUrl, info, index);
playQueue.init(); initPlayback(this, queue);
playbackManager = new PlaybackManager(this, playQueue);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -286,9 +286,16 @@ public abstract class BasePlayer implements Player.EventListener,
final Serializable serializable = intent.getSerializableExtra(SinglePlayQueue.STREAM); final Serializable serializable = intent.getSerializableExtra(SinglePlayQueue.STREAM);
if (!(serializable instanceof StreamInfo)) return; if (!(serializable instanceof StreamInfo)) return;
playQueue = new SinglePlayQueue((StreamInfo) serializable, PlayQueueItem.DEFAULT_QUALITY); final PlayQueue queue = new SinglePlayQueue((StreamInfo) serializable, PlayQueueItem.DEFAULT_QUALITY);
playQueue.init(); initPlayback(this, queue);
}
protected void initPlayback(@NonNull final PlaybackListener listener, @NonNull final PlayQueue queue) {
if (playQueue != null) playQueue.dispose();
if (playbackManager != null) playbackManager.dispose();
playQueue = queue;
playQueue.init();
playbackManager = new PlaybackManager(this, playQueue); playbackManager = new PlaybackManager(this, playQueue);
} }
@ -322,6 +329,14 @@ public abstract class BasePlayer implements Player.EventListener,
audioManager.abandonAudioFocus(this); audioManager.abandonAudioFocus(this);
audioManager = null; audioManager = null;
} }
if (playQueue != null) {
playQueue.dispose();
playQueue = null;
}
if (playbackManager != null) {
playbackManager.dispose();
playbackManager = null;
}
} }
public void destroy() { public void destroy() {
@ -440,7 +455,7 @@ public abstract class BasePlayer implements Player.EventListener,
// States Implementation // States Implementation
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public static final int STATE_LOADING = 123; public static final int STATE_BLOCKED = 123;
public static final int STATE_PLAYING = 124; public static final int STATE_PLAYING = 124;
public static final int STATE_BUFFERING = 125; public static final int STATE_BUFFERING = 125;
public static final int STATE_PAUSED = 126; public static final int STATE_PAUSED = 126;
@ -454,8 +469,8 @@ public abstract class BasePlayer implements Player.EventListener,
if (DEBUG) Log.d(TAG, "changeState() called with: state = [" + state + "]"); if (DEBUG) Log.d(TAG, "changeState() called with: state = [" + state + "]");
currentState = state; currentState = state;
switch (state) { switch (state) {
case STATE_LOADING: case STATE_BLOCKED:
onLoading(); onBlocked();
break; break;
case STATE_PLAYING: case STATE_PLAYING:
onPlaying(); onPlaying();
@ -475,8 +490,8 @@ public abstract class BasePlayer implements Player.EventListener,
} }
} }
public void onLoading() { public void onBlocked() {
if (DEBUG) Log.d(TAG, "onLoading() called"); if (DEBUG) Log.d(TAG, "onBlocked() called");
if (!isProgressLoopRunning()) startProgressLoop(); if (!isProgressLoopRunning()) startProgressLoop();
} }
@ -497,6 +512,7 @@ public abstract class BasePlayer implements Player.EventListener,
public void onCompleted() { public void onCompleted() {
if (DEBUG) Log.d(TAG, "onCompleted() called"); if (DEBUG) Log.d(TAG, "onCompleted() called");
if (playQueue.getIndex() < playQueue.size() - 1) playQueue.offsetIndex(+1);
if (isProgressLoopRunning()) stopProgressLoop(); if (isProgressLoopRunning()) stopProgressLoop();
} }
@ -536,6 +552,13 @@ public abstract class BasePlayer implements Player.EventListener,
final int currentSourceIndex = playbackManager.getCurrentSourceIndex(); final int currentSourceIndex = playbackManager.getCurrentSourceIndex();
// Sanity check
if (currentSourceIndex < 0) return;
// Check if already playing correct window
final boolean isCurrentWindowCorrect = simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex;
if (isCurrentWindowCorrect && getCurrentState() == STATE_PLAYING) return;
// Check timeline has window // Check timeline has window
if (simpleExoPlayer.getCurrentTimeline().getWindowCount() <= currentSourceIndex) return; if (simpleExoPlayer.getCurrentTimeline().getWindowCount() <= currentSourceIndex) return;
@ -544,23 +567,22 @@ public abstract class BasePlayer implements Player.EventListener,
simpleExoPlayer.getCurrentTimeline().getWindow(currentSourceIndex, window); simpleExoPlayer.getCurrentTimeline().getWindow(currentSourceIndex, window);
if (window.isDynamic) return; if (window.isDynamic) return;
// Check if already playing correct window // Check if on wrong window
final boolean isCurrentWindowCorrect = simpleExoPlayer.getCurrentWindowIndex() == currentSourceIndex; if (!isCurrentWindowCorrect) {
if (isCurrentWindowCorrect && getCurrentState() == STATE_PLAYING) return;
// Check if recovering on correct item
if (isRecovery && queuePos == playQueue.getIndex() && isCurrentWindowCorrect) {
if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex + " at: " + getTimeString((int)videoPos));
simpleExoPlayer.seekTo(currentSourceIndex, videoPos);
isRecovery = false;
} else if (!isCurrentWindowCorrect) { // Or if on wrong window
final long startPos = currentInfo != null ? currentInfo.start_position : 0; final long startPos = currentInfo != null ? currentInfo.start_position : 0;
if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + " at: " + getTimeString((int)startPos)); if (DEBUG) Log.d(TAG, "Rewinding to correct window: " + currentSourceIndex + " at: " + getTimeString((int)startPos));
simpleExoPlayer.seekTo(currentSourceIndex, startPos); simpleExoPlayer.seekTo(currentSourceIndex, startPos);
} }
// Check if recovering on correct item
if (isRecovery && queuePos == playQueue.getIndex() && isCurrentWindowCorrect) {
if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex + " at: " + getTimeString((int)videoPos));
simpleExoPlayer.seekTo(videoPos);
isRecovery = false;
}
// Good to go... // Good to go...
simpleExoPlayer.setPlayWhenReady(true); simpleExoPlayer.setPlayWhenReady(wasPlaying);
} }
@Override @Override
@ -584,8 +606,8 @@ public abstract class BasePlayer implements Player.EventListener,
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (DEBUG) if (DEBUG)
Log.d(TAG, "onPlayerStateChanged() called with: playWhenReady = [" + playWhenReady + "], playbackState = [" + playbackState + "]"); Log.d(TAG, "onPlayerStateChanged() called with: playWhenReady = [" + playWhenReady + "], playbackState = [" + playbackState + "]");
if (getCurrentState() == STATE_PAUSED_SEEK) { if (getCurrentState() == STATE_PAUSED_SEEK || getCurrentState() == STATE_BLOCKED) {
if (DEBUG) Log.d(TAG, "onPlayerStateChanged() currently on PausedSeek"); if (DEBUG) Log.d(TAG, "onPlayerStateChanged() is currently blocked");
return; return;
} }
@ -594,7 +616,7 @@ public abstract class BasePlayer implements Player.EventListener,
isPrepared = false; isPrepared = false;
break; break;
case Player.STATE_BUFFERING: // 2 case Player.STATE_BUFFERING: // 2
if (isPrepared && getCurrentState() != STATE_LOADING) changeState(STATE_BUFFERING); if (isPrepared) changeState(STATE_BUFFERING);
break; break;
case Player.STATE_READY: //3 case Player.STATE_READY: //3
if (!isPrepared) { if (!isPrepared) {
@ -606,12 +628,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) { if (isPrepared) {
playQueue.offsetIndex(+1);
break;
}
changeState(STATE_COMPLETED); changeState(STATE_COMPLETED);
isPrepared = false; isPrepared = false;
}
break; break;
} }
} }
@ -648,10 +668,21 @@ public abstract class BasePlayer implements Player.EventListener,
if (simpleExoPlayer == null) return; if (simpleExoPlayer == null) return;
if (DEBUG) Log.d(TAG, "Blocking..."); if (DEBUG) Log.d(TAG, "Blocking...");
changeState(STATE_BLOCKED);
wasPlaying = simpleExoPlayer.getPlayWhenReady();
simpleExoPlayer.setPlayWhenReady(false);
}
@Override
public void prepare(final MediaSource mediaSource) {
if (simpleExoPlayer == null) return;
if (DEBUG) Log.d(TAG, "Preparing...");
simpleExoPlayer.stop(); simpleExoPlayer.stop();
isPrepared = false; isPrepared = false;
changeState(STATE_BUFFERING); simpleExoPlayer.prepare(mediaSource);
} }
@Override @Override
@ -659,7 +690,7 @@ public abstract class BasePlayer implements Player.EventListener,
if (simpleExoPlayer == null) return; if (simpleExoPlayer == null) return;
if (DEBUG) Log.d(TAG, "Unblocking..."); if (DEBUG) Log.d(TAG, "Unblocking...");
simpleExoPlayer.prepare(playbackManager.getMediaSource(), true, true); if (getCurrentState() == STATE_BLOCKED) changeState(STATE_BUFFERING);
} }
@Override @Override
@ -874,6 +905,10 @@ public abstract class BasePlayer implements Player.EventListener,
return playQueue != null ? playQueue.getIndex() : -1; return playQueue != null ? playQueue.getIndex() : -1;
} }
public long getPlayerCurrentPosition() {
return simpleExoPlayer != null ? simpleExoPlayer.getCurrentPosition() : 0L;
}
public PlayQueue getPlayQueue() { public PlayQueue getPlayQueue() {
return playQueue; return playQueue;
} }

View File

@ -342,8 +342,8 @@ public class MainVideoPlayer extends Activity {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onLoading() { public void onBlocked() {
super.onLoading(); super.onBlocked();
playPauseButton.setImageResource(R.drawable.ic_pause_white); playPauseButton.setImageResource(R.drawable.ic_pause_white);
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 100); animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 100);
getRootView().setKeepScreenOn(true); getRootView().setKeepScreenOn(true);

View File

@ -517,8 +517,8 @@ public class PopupVideoPlayer extends Service {
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onLoading() { public void onBlocked() {
super.onLoading(); super.onBlocked();
updateNotification(R.drawable.ic_play_arrow_white); updateNotification(R.drawable.ic_play_arrow_white);
} }

View File

@ -104,7 +104,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
private static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f}; private static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f};
private boolean startedFromNewPipe = true; private boolean startedFromNewPipe = true;
private boolean wasPlaying = false;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Views // Views
@ -223,10 +222,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
final int sortedStreamsIndex = intent.getIntExtra(INDEX_SEL_VIDEO_STREAM, -1); final int sortedStreamsIndex = intent.getIntExtra(INDEX_SEL_VIDEO_STREAM, -1);
playQueue = new SinglePlayQueue((StreamInfo) serializable, sortedStreamsIndex); final PlayQueue queue = new SinglePlayQueue((StreamInfo) serializable, sortedStreamsIndex);
playQueue.init(); initPlayback(this, queue);
playbackManager = new PlaybackManager(this, playQueue);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -234,10 +231,8 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
final Serializable serializable = intent.getSerializableExtra(PLAY_QUEUE); final Serializable serializable = intent.getSerializableExtra(PLAY_QUEUE);
if (!(serializable instanceof PlayQueue)) return; if (!(serializable instanceof PlayQueue)) return;
playQueue = (PlayQueue) serializable; final PlayQueue queue = (PlayQueue) serializable;
playQueue.init(); initPlayback(this, queue);
playbackManager = new PlaybackManager(this, playQueue);
} }
@Override @Override
@ -304,7 +299,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public void onLoading() { public void onBlocked() {
if (DEBUG) Log.d(TAG, "onLoading() called"); if (DEBUG) Log.d(TAG, "onLoading() called");
if (!isProgressLoopRunning()) startProgressLoop(); if (!isProgressLoopRunning()) startProgressLoop();
@ -446,7 +441,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
protected void onFullScreenButtonClicked() { protected void onFullScreenButtonClicked() {
if (!isPlayerReady()) return; if (!isPlayerReady()) return;
changeState(STATE_BUFFERING); changeState(STATE_BLOCKED);
} }
@Override @Override
@ -523,7 +518,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
VideoStream videoStream = getSelectedVideoStream(); VideoStream videoStream = getSelectedVideoStream();
qualityTextView.setText(MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution); qualityTextView.setText(MediaFormat.getNameById(videoStream.format) + " " + videoStream.resolution);
wasPlaying = isPlaying(); wasPlaying = simpleExoPlayer.getPlayWhenReady();
} }
private void onPlaybackSpeedClicked() { private void onPlaybackSpeedClicked() {
@ -549,7 +544,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
if (DEBUG) Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]"); if (DEBUG) Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]");
if (getCurrentState() != STATE_PAUSED_SEEK) changeState(STATE_PAUSED_SEEK); if (getCurrentState() != STATE_PAUSED_SEEK) changeState(STATE_PAUSED_SEEK);
wasPlaying = isPlaying(); wasPlaying = simpleExoPlayer.getPlayWhenReady();
if (isPlaying()) simpleExoPlayer.setPlayWhenReady(false); if (isPlaying()) simpleExoPlayer.setPlayWhenReady(false);
showControls(0); showControls(0);

View File

@ -8,13 +8,26 @@ public interface PlaybackListener {
/* /*
* Called when the stream at the current queue index is not ready yet. * Called when the stream at the current queue index is not ready yet.
* Signals to the listener to block the player from playing anything. * Signals to the listener to block the player from playing anything.
*
* May be called at any time.
* */ * */
void block(); void block();
/*
* Called when the media source is rebuilt.
* Signals to the listener to prepare the media source again.
* The provided media source is always non-empty.
*
* May be called only after blocking and before unblocking.
* */
void prepare(final MediaSource mediaSource);
/* /*
* Called when the stream at the current queue index is ready. * Called when the stream at the current queue index is ready.
* Signals to the listener to resume the player. * Signals to the listener to resume the player.
* May be called at any time, even when the player is unblocked. *
* May be called only when the player is blocked.
* */ * */
void unblock(); void unblock();
@ -23,15 +36,24 @@ public interface PlaybackListener {
* Signals to the listener to synchronize the player's window to the manager's * Signals to the listener to synchronize the player's window to the manager's
* window. * window.
* *
* CAN ONLY BE CALLED ONCE UNBLOCKED! * May be called only when playback is unblocked.
* */ * */
void sync(final StreamInfo info, final int sortedStreamsIndex); void sync(final StreamInfo info, final int sortedStreamsIndex);
/* /*
* Requests the listener to resolve a stream info into a media source respective * Requests the listener to resolve a stream info into a media source
* of the listener's implementation (background, popup or main video player), * according to the listener's implementation (background, popup or main video player).
*
* May be called at any time.
* */ * */
MediaSource sourceOf(final StreamInfo info, final int sortedStreamsIndex); MediaSource sourceOf(final StreamInfo info, final int sortedStreamsIndex);
/*
* Called when the play queue can no longer to played or used.
* Currently, this means the play queue is empty and complete.
* Signals to the listener that it should shutdown.
*
* May be called at any time.
* */
void shutdown(); void shutdown();
} }

View File

@ -71,11 +71,6 @@ public class PlaybackManager {
return sourceToQueueIndex.indexOf(playQueue.getIndex()); return sourceToQueueIndex.indexOf(playQueue.getIndex());
} }
@NonNull
public DynamicConcatenatingMediaSource getMediaSource() {
return sources;
}
public void dispose() { public void dispose() {
if (playQueueReactor != null) playQueueReactor.cancel(); if (playQueueReactor != null) playQueueReactor.cancel();
@ -109,7 +104,8 @@ public class PlaybackManager {
// why no pattern matching in Java =( // why no pattern matching in Java =(
switch (event.type()) { switch (event.type()) {
case INIT: case INIT:
isBlocked = true; tryBlock();
resetSources();
break; break;
case APPEND: case APPEND:
break; break;
@ -245,6 +241,7 @@ public class PlaybackManager {
if (this.sourceToQueueIndex != null) this.sourceToQueueIndex.clear(); if (this.sourceToQueueIndex != null) this.sourceToQueueIndex.clear();
this.sources = new DynamicConcatenatingMediaSource(); this.sources = new DynamicConcatenatingMediaSource();
playbackListener.prepare(this.sources);
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////

View File

@ -72,7 +72,7 @@ public class NavigationHelper {
.putExtra(BasePlayer.INTENT_TYPE, VideoPlayer.PLAYER_INTENT) .putExtra(BasePlayer.INTENT_TYPE, VideoPlayer.PLAYER_INTENT)
.putExtra(VideoPlayer.PLAY_QUEUE, instance.getPlayQueue()) .putExtra(VideoPlayer.PLAY_QUEUE, instance.getPlayQueue())
.putExtra(VideoPlayer.RESTORE_QUEUE_INDEX, instance.getCurrentQueueIndex()) .putExtra(VideoPlayer.RESTORE_QUEUE_INDEX, instance.getCurrentQueueIndex())
.putExtra(BasePlayer.START_POSITION, instance.getPlayer().getCurrentPosition()) .putExtra(BasePlayer.START_POSITION, instance.getPlayerCurrentPosition())
.putExtra(BasePlayer.PLAYBACK_SPEED, instance.getPlaybackSpeed()); .putExtra(BasePlayer.PLAYBACK_SPEED, instance.getPlaybackSpeed());
} }