-Added load debouncing to MediaSourceManager to prevent mass loading due to rapid timeline change.
-Added marquee title to main video player. -Modified destroyPlayer to always dispose play queue and media source manager. -Remove unused code from players.
This commit is contained in:
parent
1fb3774e03
commit
b4fdbdeb1b
|
@ -19,18 +19,13 @@
|
|||
|
||||
package org.schabi.newpipe.player;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
@ -67,11 +62,11 @@ import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListene
|
|||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.player.playback.MediaSourceManager;
|
||||
import org.schabi.newpipe.player.playback.PlaybackListener;
|
||||
import org.schabi.newpipe.player.helper.AudioReactor;
|
||||
import org.schabi.newpipe.player.helper.CacheFactory;
|
||||
import org.schabi.newpipe.player.helper.LoadController;
|
||||
import org.schabi.newpipe.player.playback.MediaSourceManager;
|
||||
import org.schabi.newpipe.player.playback.PlaybackListener;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlayQueueAdapter;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
|
@ -93,14 +88,13 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString;
|
|||
*
|
||||
* @author mauriciocolli
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
@SuppressWarnings({"WeakerAccess"})
|
||||
public abstract class BasePlayer implements Player.EventListener, PlaybackListener {
|
||||
|
||||
public static final boolean DEBUG = true;
|
||||
public static final String TAG = "BasePlayer";
|
||||
|
||||
protected Context context;
|
||||
protected SharedPreferences sharedPreferences;
|
||||
|
||||
protected BroadcastReceiver broadcastReceiver;
|
||||
protected IntentFilter intentFilter;
|
||||
|
@ -156,7 +150,6 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
|
||||
public BasePlayer(Context context) {
|
||||
this.context = context;
|
||||
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
this.broadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
|
@ -234,17 +227,15 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
|
||||
// Re-initialization
|
||||
destroyPlayer();
|
||||
if (playQueue != null) playQueue.dispose();
|
||||
if (playbackManager != null) playbackManager.dispose();
|
||||
initPlayer();
|
||||
setRepeatMode(repeatMode);
|
||||
setPlaybackParameters(playbackSpeed, playbackPitch);
|
||||
|
||||
// Good to go...
|
||||
initPlayback(this, queue);
|
||||
initPlayback(queue);
|
||||
}
|
||||
|
||||
protected void initPlayback(@NonNull final PlaybackListener listener, @NonNull final PlayQueue queue) {
|
||||
protected void initPlayback(@NonNull final PlayQueue queue) {
|
||||
playQueue = queue;
|
||||
playQueue.init();
|
||||
playbackManager = new MediaSourceManager(this, playQueue);
|
||||
|
@ -279,6 +270,8 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
simpleExoPlayer.release();
|
||||
}
|
||||
if (isProgressLoopRunning()) stopProgressLoop();
|
||||
if (playQueue != null) playQueue.dispose();
|
||||
if (playbackManager != null) playbackManager.dispose();
|
||||
if (audioReactor != null) audioReactor.abandonAudioFocus();
|
||||
}
|
||||
|
||||
|
@ -288,9 +281,6 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
clearThumbnailCache();
|
||||
unregisterBroadcastReceiver();
|
||||
|
||||
if (playQueue != null) playQueue.dispose();
|
||||
if (playbackManager != null) playbackManager.dispose();
|
||||
|
||||
trackSelector = null;
|
||||
simpleExoPlayer = null;
|
||||
}
|
||||
|
@ -602,7 +592,10 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
// If the user selects a new track, then the discontinuity occurs after the index is changed.
|
||||
// Therefore, the only source that causes a discrepancy would be autoplay,
|
||||
// which can only offset the current track by +1.
|
||||
if (newWindowIndex != playQueue.getIndex()) playQueue.offsetIndex(+1);
|
||||
if (newWindowIndex != playQueue.getIndex() && playbackManager != null) {
|
||||
playQueue.offsetIndex(+1);
|
||||
playbackManager.load();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -659,9 +652,6 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
@Override
|
||||
public void shutdown() {
|
||||
if (DEBUG) Log.d(TAG, "Shutting down...");
|
||||
|
||||
playbackManager.dispose();
|
||||
playQueue.dispose();
|
||||
destroy();
|
||||
}
|
||||
|
||||
|
@ -817,35 +807,6 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
);
|
||||
}
|
||||
|
||||
public void animateAudio(final float from, final float to, int duration) {
|
||||
ValueAnimator valueAnimator = new ValueAnimator();
|
||||
valueAnimator.setFloatValues(from, to);
|
||||
valueAnimator.setDuration(duration);
|
||||
valueAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
if (simpleExoPlayer != null) simpleExoPlayer.setVolume(from);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
if (simpleExoPlayer != null) simpleExoPlayer.setVolume(to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (simpleExoPlayer != null) simpleExoPlayer.setVolume(to);
|
||||
}
|
||||
});
|
||||
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
if (simpleExoPlayer != null) simpleExoPlayer.setVolume(((float) animation.getAnimatedValue()));
|
||||
}
|
||||
});
|
||||
valueAnimator.start();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Getters and Setters
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -854,10 +815,6 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen
|
|||
return simpleExoPlayer;
|
||||
}
|
||||
|
||||
public SharedPreferences getSharedPreferences() {
|
||||
return sharedPreferences;
|
||||
}
|
||||
|
||||
public AudioReactor getAudioReactor() {
|
||||
return audioReactor;
|
||||
}
|
||||
|
|
|
@ -144,7 +144,7 @@ public final class MainVideoPlayer extends Activity {
|
|||
playerImpl.getPlayPauseButton().setImageResource(R.drawable.ic_play_arrow_white);
|
||||
|
||||
playerImpl.getPlayer().setPlayWhenReady(playerImpl.wasPlaying);
|
||||
playerImpl.initPlayback(playerImpl, playerImpl.playQueue);
|
||||
playerImpl.initPlayback(playerImpl.playQueue);
|
||||
|
||||
if (playerImpl.trackSelector != null && parameters != null) {
|
||||
playerImpl.trackSelector.setParameters(parameters);
|
||||
|
@ -265,6 +265,9 @@ public final class MainVideoPlayer extends Activity {
|
|||
this.playPreviousButton = rootView.findViewById(R.id.playPreviousButton);
|
||||
this.playNextButton = rootView.findViewById(R.id.playNextButton);
|
||||
|
||||
titleTextView.setSelected(true);
|
||||
channelTextView.setSelected(true);
|
||||
|
||||
getRootView().setKeepScreenOn(true);
|
||||
}
|
||||
|
||||
|
@ -350,9 +353,9 @@ public final class MainVideoPlayer extends Activity {
|
|||
this.getPlaybackQuality()
|
||||
);
|
||||
context.startService(intent);
|
||||
destroyPlayer();
|
||||
|
||||
((View) getControlAnimationView().getParent()).setVisibility(View.GONE);
|
||||
destroy();
|
||||
finish();
|
||||
}
|
||||
|
||||
|
|
|
@ -884,7 +884,7 @@ public final class PopupVideoPlayer extends Service {
|
|||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
playerImpl.initPlayback(playerImpl, new SinglePlayQueue(info));
|
||||
playerImpl.initPlayback(new SinglePlayQueue(info));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -475,7 +475,6 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
|
|||
if (selectedStreamIndex == menuItemIndex ||
|
||||
availableStreams == null || availableStreams.size() <= menuItemIndex) return true;
|
||||
|
||||
final String oldResolution = getPlaybackQuality();
|
||||
final String newResolution = availableStreams.get(menuItemIndex).resolution;
|
||||
setRecovery();
|
||||
setPlaybackQuality(newResolution);
|
||||
|
|
|
@ -17,13 +17,16 @@ import org.schabi.newpipe.playlist.events.RemoveEvent;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.annotations.NonNull;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.disposables.SerialDisposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
public class MediaSourceManager implements DeferredMediaSource.Callback {
|
||||
public class MediaSourceManager {
|
||||
private final String TAG = "MediaSourceManager@" + Integer.toHexString(hashCode());
|
||||
// One-side rolling window size for default loading
|
||||
// Effectively loads windowSize * 2 + 1 streams, must be greater than 0
|
||||
|
@ -31,6 +34,16 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
private final PlaybackListener playbackListener;
|
||||
private final PlayQueue playQueue;
|
||||
|
||||
// Process only the last load order when receiving a stream of load orders (lessens IO)
|
||||
// The lower it is, the faster the error processing during loading
|
||||
// The higher it is, the less loading occurs during rapid timeline changes
|
||||
// Not recommended to go below 50ms or above 500ms
|
||||
private final long loadDebounceMillis;
|
||||
private final PublishSubject<Long> loadSignal;
|
||||
private final Disposable debouncedLoader;
|
||||
|
||||
private final DeferredMediaSource.Callback sourceBuilder;
|
||||
|
||||
private DynamicConcatenatingMediaSource sources;
|
||||
|
||||
private Subscription playQueueReactor;
|
||||
|
@ -40,17 +53,27 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
|
||||
public MediaSourceManager(@NonNull final PlaybackListener listener,
|
||||
@NonNull final PlayQueue playQueue) {
|
||||
this(listener, playQueue, 1);
|
||||
this(listener, playQueue, 1, 200L);
|
||||
}
|
||||
|
||||
public MediaSourceManager(@NonNull final PlaybackListener listener,
|
||||
@NonNull final PlayQueue playQueue,
|
||||
final int windowSize) {
|
||||
private MediaSourceManager(@NonNull final PlaybackListener listener,
|
||||
@NonNull final PlayQueue playQueue,
|
||||
final int windowSize,
|
||||
final long loadDebounceMillis) {
|
||||
if (windowSize <= 0) {
|
||||
throw new UnsupportedOperationException("MediaSourceManager window size must be greater than 0");
|
||||
}
|
||||
|
||||
this.playbackListener = listener;
|
||||
this.playQueue = playQueue;
|
||||
this.windowSize = windowSize;
|
||||
this.loadDebounceMillis = loadDebounceMillis;
|
||||
|
||||
this.syncReactor = new SerialDisposable();
|
||||
this.loadSignal = PublishSubject.create();
|
||||
this.debouncedLoader = getDebouncedLoader();
|
||||
|
||||
this.sourceBuilder = getSourceBuilder();
|
||||
|
||||
this.sources = new DynamicConcatenatingMediaSource();
|
||||
|
||||
|
@ -63,9 +86,13 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
// DeferredMediaSource listener
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) {
|
||||
return playbackListener.sourceOf(item, info);
|
||||
private DeferredMediaSource.Callback getSourceBuilder() {
|
||||
return new DeferredMediaSource.Callback() {
|
||||
@Override
|
||||
public MediaSource sourceOf(PlayQueueItem item, StreamInfo info) {
|
||||
return playbackListener.sourceOf(item, info);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -75,6 +102,8 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
* Dispose the manager and releases all message buses and loaders.
|
||||
* */
|
||||
public void dispose() {
|
||||
if (loadSignal != null) loadSignal.onComplete();
|
||||
if (debouncedLoader != null) debouncedLoader.dispose();
|
||||
if (playQueueReactor != null) playQueueReactor.cancel();
|
||||
if (syncReactor != null) syncReactor.dispose();
|
||||
if (sources != null) sources.releaseSource();
|
||||
|
@ -90,23 +119,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
* Unblocks the player once the item at the current index is loaded.
|
||||
* */
|
||||
public void load() {
|
||||
// The current item has higher priority
|
||||
final int currentIndex = playQueue.getIndex();
|
||||
final PlayQueueItem currentItem = playQueue.getItem(currentIndex);
|
||||
if (currentItem == null) return;
|
||||
load(currentItem);
|
||||
|
||||
// The rest are just for seamless playback
|
||||
final int leftBound = Math.max(0, currentIndex - windowSize);
|
||||
final int rightLimit = currentIndex + windowSize + 1;
|
||||
final int rightBound = Math.min(playQueue.size(), rightLimit);
|
||||
final List<PlayQueueItem> items = new ArrayList<>(playQueue.getStreams().subList(leftBound, rightBound));
|
||||
|
||||
// Do a round robin
|
||||
final int excess = rightLimit - playQueue.size();
|
||||
if (excess >= 0) items.addAll(playQueue.getStreams().subList(0, Math.min(playQueue.size(), excess)));
|
||||
|
||||
for (final PlayQueueItem item: items) load(item);
|
||||
loadSignal.onNext(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -241,7 +254,27 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
syncReactor.set(currentItem.getStream().subscribe(syncPlayback, onError));
|
||||
}
|
||||
|
||||
private void load(@Nullable final PlayQueueItem item) {
|
||||
private void loadInternal() {
|
||||
// The current item has higher priority
|
||||
final int currentIndex = playQueue.getIndex();
|
||||
final PlayQueueItem currentItem = playQueue.getItem(currentIndex);
|
||||
if (currentItem == null) return;
|
||||
loadItem(currentItem);
|
||||
|
||||
// The rest are just for seamless playback
|
||||
final int leftBound = Math.max(0, currentIndex - windowSize);
|
||||
final int rightLimit = currentIndex + windowSize + 1;
|
||||
final int rightBound = Math.min(playQueue.size(), rightLimit);
|
||||
final List<PlayQueueItem> items = new ArrayList<>(playQueue.getStreams().subList(leftBound, rightBound));
|
||||
|
||||
// Do a round robin
|
||||
final int excess = rightLimit - playQueue.size();
|
||||
if (excess >= 0) items.addAll(playQueue.getStreams().subList(0, Math.min(playQueue.size(), excess)));
|
||||
|
||||
for (final PlayQueueItem item: items) loadItem(item);
|
||||
}
|
||||
|
||||
private void loadItem(@Nullable final PlayQueueItem item) {
|
||||
if (item == null) return;
|
||||
|
||||
final int index = playQueue.indexOf(item);
|
||||
|
@ -261,10 +294,21 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
if (sources == null) return;
|
||||
|
||||
for (final PlayQueueItem item : playQueue.getStreams()) {
|
||||
insert(playQueue.indexOf(item), new DeferredMediaSource(item, this));
|
||||
insert(playQueue.indexOf(item), new DeferredMediaSource(item, sourceBuilder));
|
||||
}
|
||||
}
|
||||
|
||||
private Disposable getDebouncedLoader() {
|
||||
return loadSignal
|
||||
.debounce(loadDebounceMillis, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Consumer<Long>() {
|
||||
@Override
|
||||
public void accept(Long timestamp) throws Exception {
|
||||
loadInternal();
|
||||
}
|
||||
});
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Media Source List Manipulation
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
@ -287,7 +331,7 @@ public class MediaSourceManager implements DeferredMediaSource.Callback {
|
|||
* If the play queue index does not exist, the removal is ignored.
|
||||
* */
|
||||
private void remove(final int queueIndex) {
|
||||
if (queueIndex < 0) return;
|
||||
if (queueIndex < 0 || queueIndex > sources.getSize()) return;
|
||||
|
||||
sources.removeMediaSource(queueIndex);
|
||||
}
|
||||
|
|
|
@ -158,11 +158,16 @@
|
|||
android:id="@+id/titleTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:scrollHorizontally="true"
|
||||
android:singleLine="true"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="The Video Title LONG very LONG"/>
|
||||
|
||||
|
@ -170,10 +175,15 @@
|
|||
android:id="@+id/channelTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:scrollHorizontally="true"
|
||||
android:singleLine="true"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
tools:text="The Video Artist LONG very LONG very Long"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
|
Loading…
Reference in New Issue