-Added toggle to enable fast inexact seek in players.

-Improved player sync calls to recognize different metadata updates.
-Changed MediaSourceManager to synchronize only after timeline changes and reenabled multiple sync calls to player.
-Renamed listener and synchronization methods related to MediaSourceManager.
This commit is contained in:
John Zhen Mo 2018-03-03 14:24:21 -08:00
parent 0c17f0825b
commit a88e19a8ed
11 changed files with 132 additions and 100 deletions

View File

@ -380,11 +380,10 @@ public final class BackgroundPlayer extends Service {
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
if (currentItem == item && currentInfo == info) return;
super.sync(item, info);
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info,
final int newPlayQueueIndex,
final boolean hasPlayQueueItemChanged) {
resetNotification();
updateNotification(-1);
updateMetadata();
@ -405,8 +404,8 @@ public final class BackgroundPlayer extends Service {
}
@Override
public void shutdown() {
super.shutdown();
public void onPlaybackShutdown() {
super.onPlaybackShutdown();
onClose();
}

View File

@ -61,6 +61,7 @@ import org.schabi.newpipe.history.HistoryRecordManager;
import org.schabi.newpipe.player.helper.AudioReactor;
import org.schabi.newpipe.player.helper.LoadController;
import org.schabi.newpipe.player.helper.PlayerDataSource;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.playback.CustomTrackSelector;
import org.schabi.newpipe.player.playback.MediaSourceManager;
import org.schabi.newpipe.player.playback.PlaybackListener;
@ -196,6 +197,7 @@ public abstract class BasePlayer implements
simpleExoPlayer.addListener(this);
simpleExoPlayer.setPlayWhenReady(true);
simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context));
}
public void initListeners() {}
@ -688,7 +690,7 @@ public abstract class BasePlayer implements
break;
default:
showUnrecoverableError(error);
shutdown();
onPlaybackShutdown();
break;
}
}
@ -774,9 +776,9 @@ public abstract class BasePlayer implements
//////////////////////////////////////////////////////////////////////////*/
@Override
public void block() {
public void onPlaybackBlock() {
if (simpleExoPlayer == null) return;
if (DEBUG) Log.d(TAG, "Playback - block() called");
if (DEBUG) Log.d(TAG, "Playback - onPlaybackBlock() called");
currentItem = null;
currentInfo = null;
@ -787,9 +789,9 @@ public abstract class BasePlayer implements
}
@Override
public void unblock(final MediaSource mediaSource) {
public void onPlaybackUnblock(final MediaSource mediaSource) {
if (simpleExoPlayer == null) return;
if (DEBUG) Log.d(TAG, "Playback - unblock() called");
if (DEBUG) Log.d(TAG, "Playback - onPlaybackUnblock() called");
if (getCurrentState() == STATE_BLOCKED) changeState(STATE_BUFFERING);
@ -798,34 +800,50 @@ public abstract class BasePlayer implements
}
@Override
public void sync(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info) {
if (currentItem == item && currentInfo == info) return;
currentItem = item;
currentInfo = info;
if (DEBUG) Log.d(TAG, "Playback - sync() called with " +
public void onPlaybackSynchronize(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info) {
if (DEBUG) Log.d(TAG, "Playback - onPlaybackSynchronize() called with " +
(info == null ? "available" : "null") + " info, " +
"item=[" + item.getTitle() + "], url=[" + item.getUrl() + "]");
if (simpleExoPlayer == null) return;
final boolean hasPlayQueueItemChanged = currentItem != item;
final boolean hasStreamInfoChanged = currentInfo != info;
if (!hasPlayQueueItemChanged && !hasStreamInfoChanged) {
return; // Nothing to synchronize
}
currentItem = item;
currentInfo = info;
if (hasPlayQueueItemChanged) {
// updates only to the stream info should not trigger another view count
registerView();
initThumbnail(info == null ? item.getThumbnailUrl() : info.getThumbnailUrl());
}
final int currentSourceIndex = playQueue.indexOf(item);
onMetadataChanged(item, info, currentSourceIndex, hasPlayQueueItemChanged);
// Check if on wrong window
final int currentSourceIndex = playQueue.indexOf(item);
if (simpleExoPlayer == null) return;
if (currentSourceIndex != playQueue.getIndex()) {
Log.e(TAG, "Play Queue may be desynchronized: item index=[" + currentSourceIndex +
"], queue index=[" + playQueue.getIndex() + "]");
} else if (simpleExoPlayer.getCurrentPeriodIndex() != currentSourceIndex || !isPlaying()) {
// on metadata changed
} else if (simpleExoPlayer.getCurrentWindowIndex() != currentSourceIndex || !isPlaying()) {
final long startPos = info != null ? info.start_position : 0;
if (DEBUG) Log.d(TAG, "Rewinding to correct window=[" + currentSourceIndex + "]," +
" at=[" + getTimeString((int)startPos) + "]," +
" from=[" + simpleExoPlayer.getCurrentPeriodIndex() + "].");
" from=[" + simpleExoPlayer.getCurrentWindowIndex() + "].");
simpleExoPlayer.seekTo(currentSourceIndex, startPos);
}
registerView();
initThumbnail(info == null ? item.getThumbnailUrl() : info.thumbnail_url);
}
abstract protected void onMetadataChanged(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info,
final int newPlayQueueIndex,
final boolean hasPlayQueueItemChanged);
@Nullable
@Override
public MediaSource sourceOf(PlayQueueItem item, StreamInfo info) {
@ -839,7 +857,7 @@ public abstract class BasePlayer implements
}
@Override
public void shutdown() {
public void onPlaybackShutdown() {
if (DEBUG) Log.d(TAG, "Shutting down...");
destroy();
}

View File

@ -391,31 +391,32 @@ public final class MainVideoPlayer extends Activity {
updatePlaybackButtons();
}
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void shutdown() {
super.shutdown();
finish();
}
@Override
public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
super.sync(item, info);
titleTextView.setText(getVideoTitle());
channelTextView.setText(getUploaderName());
//playPauseButton.setImageResource(R.drawable.ic_pause_white);
}
@Override
public void onShuffleClicked() {
super.onShuffleClicked();
updatePlaybackButtons();
}
/*//////////////////////////////////////////////////////////////////////////
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info,
final int newPlayQueueIndex,
final boolean hasPlayQueueItemChanged) {
super.onMetadataChanged(item, info, newPlayQueueIndex, false);
titleTextView.setText(getVideoTitle());
channelTextView.setText(getUploaderName());
}
@Override
public void onPlaybackShutdown() {
super.onPlaybackShutdown();
finish();
}
/*//////////////////////////////////////////////////////////////////////////
// Player Overrides
//////////////////////////////////////////////////////////////////////////*/

View File

@ -535,7 +535,8 @@ public final class PopupVideoPlayer extends Service {
private void updatePlayback() {
if (activityListener != null && simpleExoPlayer != null && playQueue != null) {
activityListener.onPlaybackUpdate(currentState, getRepeatMode(), playQueue.isShuffled(), simpleExoPlayer.getPlaybackParameters());
activityListener.onPlaybackUpdate(currentState, getRepeatMode(),
playQueue.isShuffled(), simpleExoPlayer.getPlaybackParameters());
}
}
@ -574,16 +575,17 @@ public final class PopupVideoPlayer extends Service {
// Playback Listener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void sync(@NonNull PlayQueueItem item, @Nullable StreamInfo info) {
if (currentItem == item && currentInfo == info) return;
super.sync(item, info);
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info,
final int newPlayQueueIndex,
final boolean hasPlayQueueItemChanged) {
super.onMetadataChanged(item, info, newPlayQueueIndex, false);
updateMetadata();
}
@Override
public void shutdown() {
super.shutdown();
public void onPlaybackShutdown() {
super.onPlaybackShutdown();
onClose();
}

View File

@ -324,9 +324,10 @@ public abstract class VideoPlayer extends BasePlayer
protected abstract int getOverrideResolutionIndex(final List<VideoStream> sortedVideos, final String playbackQuality);
@Override
public void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info) {
super.sync(item, info);
protected void onMetadataChanged(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info,
final int newPlayQueueIndex,
final boolean hasPlayQueueItemChanged) {
qualityTextView.setVisibility(View.GONE);
playbackSpeedTextView.setVisibility(View.GONE);

View File

@ -5,6 +5,7 @@ import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.util.MimeTypes;
@ -109,6 +110,12 @@ public class PlayerHelper {
return isRememberingPopupDimensions(context, true);
}
@NonNull
public static SeekParameters getSeekParameters(@NonNull final Context context) {
return isUsingInexactSeek(context, false) ?
SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT;
}
public static long getPreferredCacheSize(@NonNull final Context context) {
return 64 * 1024 * 1024L;
}
@ -176,4 +183,8 @@ public class PlayerHelper {
private static boolean isRememberingPopupDimensions(@Nonnull final Context context, final boolean b) {
return getPreferences(context).getBoolean(context.getString(R.string.popup_remember_size_pos_key), b);
}
private static boolean isUsingInexactSeek(@NonNull final Context context, final boolean b) {
return getPreferences(context).getBoolean(context.getString(R.string.use_inexact_seek_key), b);
}
}

View File

@ -50,7 +50,7 @@ public class MediaSourceManager {
* streams before will only be cached for future usage.
*
* @see #onMediaSourceReceived(PlayQueueItem, ManagedMediaSource)
* @see #update(int, MediaSource)
* @see #update(int, MediaSource, Runnable)
* */
private final static int WINDOW_SIZE = 1;
@ -95,15 +95,13 @@ public class MediaSourceManager {
* */
private final static int MAXIMUM_LOADER_SIZE = WINDOW_SIZE * 2 + 1;
@NonNull private final CompositeDisposable loaderReactor;
@NonNull private Set<PlayQueueItem> loadingItems;
@NonNull private final Set<PlayQueueItem> loadingItems;
@NonNull private final SerialDisposable syncReactor;
@NonNull private final AtomicBoolean isBlocked;
@NonNull private DynamicConcatenatingMediaSource sources;
@Nullable private PlayQueueItem syncedItem;
public MediaSourceManager(@NonNull final PlaybackListener listener,
@NonNull final PlayQueue playQueue) {
this(listener, playQueue,
@ -159,8 +157,6 @@ public class MediaSourceManager {
loaderReactor.dispose();
syncReactor.dispose();
sources.releaseSource();
syncedItem = null;
}
/**
@ -182,9 +178,7 @@ public class MediaSourceManager {
public void reset() {
if (DEBUG) Log.d(TAG, "reset() called.");
tryBlock();
syncedItem = null;
maybeBlock();
populateSources();
}
/*//////////////////////////////////////////////////////////////////////////
@ -215,7 +209,7 @@ public class MediaSourceManager {
private void onPlayQueueChanged(final PlayQueueEvent event) {
if (playQueue.isEmpty() && playQueue.isComplete()) {
playbackListener.shutdown();
playbackListener.onPlaybackShutdown();
return;
}
@ -261,7 +255,7 @@ public class MediaSourceManager {
}
if (!isPlayQueueReady()) {
tryBlock();
maybeBlock();
playQueue.fetch();
}
playQueueReactor.request(1);
@ -290,23 +284,23 @@ public class MediaSourceManager {
return false;
}
private void tryBlock() {
if (DEBUG) Log.d(TAG, "tryBlock() called.");
private void maybeBlock() {
if (DEBUG) Log.d(TAG, "maybeBlock() called.");
if (isBlocked.get()) return;
playbackListener.block();
playbackListener.onPlaybackBlock();
resetSources();
isBlocked.set(true);
}
private void tryUnblock() {
if (DEBUG) Log.d(TAG, "tryUnblock() called.");
private void maybeUnblock() {
if (DEBUG) Log.d(TAG, "maybeUnblock() called.");
if (isPlayQueueReady() && isPlaybackReady() && isBlocked.get()) {
isBlocked.set(false);
playbackListener.unblock(sources);
playbackListener.onPlaybackUnblock(sources);
}
}
@ -314,8 +308,8 @@ public class MediaSourceManager {
// Metadata Synchronization TODO: maybe this should be a separate manager
//////////////////////////////////////////////////////////////////////////*/
private void sync() {
if (DEBUG) Log.d(TAG, "sync() called.");
private void maybeSync() {
if (DEBUG) Log.d(TAG, "onPlaybackSynchronize() called.");
final PlayQueueItem currentItem = playQueue.getItem();
if (isBlocked.get() || currentItem == null) return;
@ -323,23 +317,25 @@ public class MediaSourceManager {
final Consumer<StreamInfo> onSuccess = info -> syncInternal(currentItem, info);
final Consumer<Throwable> onError = throwable -> syncInternal(currentItem, null);
if (syncedItem != currentItem) {
syncedItem = currentItem;
final Disposable sync = currentItem.getStream()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(onSuccess, onError);
syncReactor.set(sync);
}
final Disposable sync = currentItem.getStream()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(onSuccess, onError);
syncReactor.set(sync);
}
private void syncInternal(@NonNull final PlayQueueItem item,
@Nullable final StreamInfo info) {
// Ensure the current item is up to date with the play queue
if (playQueue.getItem() == item && playQueue.getItem() == syncedItem) {
playbackListener.sync(item, info);
if (playQueue.getItem() == item) {
playbackListener.onPlaybackSynchronize(item, info);
}
}
private void maybeSynchronizePlayer() {
maybeUnblock();
maybeSync();
}
/*//////////////////////////////////////////////////////////////////////////
// MediaSource Loading
//////////////////////////////////////////////////////////////////////////*/
@ -404,8 +400,7 @@ public class MediaSourceManager {
loaderReactor.add(loader);
}
tryUnblock();
sync();
maybeSynchronizePlayer();
}
private Single<ManagedMediaSource> getLoadedMediaSource(@NonNull final PlayQueueItem stream) {
@ -431,17 +426,15 @@ public class MediaSourceManager {
if (DEBUG) Log.d(TAG, "MediaSource - Loaded: [" + item.getTitle() +
"] with url: " + item.getUrl());
loadingItems.remove(item);
final int itemIndex = playQueue.indexOf(item);
// Only update the playlist timeline for items at the current index or after.
if (itemIndex >= playQueue.getIndex() && isCorrectionNeeded(item)) {
if (DEBUG) Log.d(TAG, "MediaSource - Updating: [" + item.getTitle() +
"] with url: " + item.getUrl());
update(itemIndex, mediaSource);
update(itemIndex, mediaSource, this::maybeSynchronizePlayer);
}
loadingItems.remove(item);
tryUnblock();
sync();
}
/**
@ -533,10 +526,11 @@ public class MediaSourceManager {
* this will modify the playback timeline prior to the index and may cause desynchronization
* on the playing item between {@link PlayQueue} and {@link DynamicConcatenatingMediaSource}.
* */
private synchronized void update(final int index, @NonNull final MediaSource source) {
private synchronized void update(final int index, @NonNull final MediaSource source,
@Nullable final Runnable finalizingAction) {
if (index < 0 || index >= sources.getSize()) return;
sources.addMediaSource(index + 1, source, () ->
sources.removeMediaSource(index));
sources.removeMediaSource(index, finalizingAction));
}
}

View File

@ -18,7 +18,7 @@ public interface PlaybackListener {
*
* May be called at any time.
* */
void block();
void onPlaybackBlock();
/**
* Called when the stream at the current queue index is ready.
@ -26,18 +26,16 @@ public interface PlaybackListener {
*
* May be called only when the player is blocked.
* */
void unblock(final MediaSource mediaSource);
void onPlaybackUnblock(final MediaSource mediaSource);
/**
* Called when the queue index is refreshed.
* Signals to the listener to synchronize the player's window to the manager's
* window.
*
* Occurs once only per play queue item change.
*
* May be called only after unblock is called.
* May be called anytime at any amount once unblock is called.
* */
void sync(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info);
void onPlaybackSynchronize(@NonNull final PlayQueueItem item, @Nullable final StreamInfo info);
/**
* Requests the listener to resolve a stream info into a media source
@ -55,5 +53,5 @@ public interface PlaybackListener {
*
* May be called at any time.
* */
void shutdown();
void onPlaybackShutdown();
}

View File

@ -20,6 +20,7 @@
<string name="player_gesture_controls_key" translatable="false">player_gesture_controls</string>
<string name="resume_on_audio_focus_gain_key" translatable="false">resume_on_audio_focus_gain</string>
<string name="popup_remember_size_pos_key" translatable="false">popup_remember_size_pos_key</string>
<string name="use_inexact_seek_key" translatable="false">use_inexact_seek_key</string>
<string name="default_resolution_key" translatable="false">default_resolution</string>
<string name="default_resolution_value" translatable="false">360p</string>

View File

@ -72,6 +72,8 @@
<string name="black_theme_title">Black</string>
<string name="popup_remember_size_pos_title">Remember popup size and position</string>
<string name="popup_remember_size_pos_summary">Remember last size and position of popup</string>
<string name="use_inexact_seek_title">Use fast inexact seek</string>
<string name="use_inexact_seek_summary">Inexact seek allows the player to seek to positions faster with reduced precision</string>
<string name="player_gesture_controls_title">Player gesture controls</string>
<string name="player_gesture_controls_summary">Use gestures to control the brightness and volume of the player</string>
<string name="show_search_suggestions_title">Search suggestions</string>

View File

@ -100,5 +100,10 @@
android:summary="@string/popup_remember_size_pos_summary"
android:title="@string/popup_remember_size_pos_title"/>
<SwitchPreference
android:defaultValue="false"
android:key="@string/use_inexact_seek_key"
android:summary="@string/use_inexact_seek_summary"
android:title="@string/use_inexact_seek_title"/>
</PreferenceCategory>
</PreferenceScreen>