-Changed media source manager near edge loading to no longer load while player position is not progressing.
-Changed main video player to always self-destruct on stop. -Extracted main video player lifecycle states into separate data class. -Fixed play queue in full repeat mode does not load first item after expiring.
This commit is contained in:
parent
c9915bba18
commit
42d19d98ad
8 changed files with 150 additions and 82 deletions
|
@ -164,6 +164,11 @@ public final class BackgroundPlayer extends Service {
|
|||
if (DEBUG) Log.d(TAG, "onScreenOnOff() called with: on = [" + on + "]");
|
||||
shouldUpdateOnProgress = on;
|
||||
basePlayerImpl.triggerProgressUpdate();
|
||||
if (on) {
|
||||
basePlayerImpl.startProgressLoop();
|
||||
} else {
|
||||
basePlayerImpl.stopProgressLoop();
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -549,7 +554,6 @@ public final class BackgroundPlayer extends Service {
|
|||
super.onPaused();
|
||||
|
||||
updateNotification(R.drawable.ic_play_arrow_white);
|
||||
if (isProgressLoopRunning()) stopProgressLoop();
|
||||
|
||||
lockManager.releaseWifiAndCpu();
|
||||
}
|
||||
|
|
|
@ -177,11 +177,11 @@ public abstract class BasePlayer implements
|
|||
}
|
||||
|
||||
public void setup() {
|
||||
if (simpleExoPlayer == null) initPlayer();
|
||||
if (simpleExoPlayer == null) initPlayer(/*playOnInit=*/true);
|
||||
initListeners();
|
||||
}
|
||||
|
||||
public void initPlayer() {
|
||||
public void initPlayer(final boolean playOnReady) {
|
||||
if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
|
||||
|
||||
if (databaseUpdateReactor != null) databaseUpdateReactor.dispose();
|
||||
|
@ -199,7 +199,7 @@ public abstract class BasePlayer implements
|
|||
final RenderersFactory renderFactory = new DefaultRenderersFactory(context);
|
||||
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(renderFactory, trackSelector, loadControl);
|
||||
simpleExoPlayer.addListener(this);
|
||||
simpleExoPlayer.setPlayWhenReady(true);
|
||||
simpleExoPlayer.setPlayWhenReady(playOnReady);
|
||||
simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context));
|
||||
|
||||
audioReactor = new AudioReactor(context, simpleExoPlayer);
|
||||
|
@ -237,15 +237,16 @@ public abstract class BasePlayer implements
|
|||
final float playbackPitch = intent.getFloatExtra(PLAYBACK_PITCH, getPlaybackPitch());
|
||||
|
||||
// Good to go...
|
||||
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch);
|
||||
initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, /*playOnInit=*/true);
|
||||
}
|
||||
|
||||
protected void initPlayback(@NonNull final PlayQueue queue,
|
||||
@Player.RepeatMode final int repeatMode,
|
||||
final float playbackSpeed,
|
||||
final float playbackPitch) {
|
||||
final float playbackPitch,
|
||||
final boolean playOnReady) {
|
||||
destroyPlayer();
|
||||
initPlayer();
|
||||
initPlayer(playOnReady);
|
||||
setRepeatMode(repeatMode);
|
||||
setPlaybackParameters(playbackSpeed, playbackPitch);
|
||||
|
||||
|
@ -770,9 +771,10 @@ public abstract class BasePlayer implements
|
|||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public boolean isNearPlaybackEdge(final long timeToEndMillis) {
|
||||
public boolean isApproachingPlaybackEdge(final long timeToEndMillis) {
|
||||
// If live, then not near playback edge
|
||||
if (simpleExoPlayer == null || isLive()) return false;
|
||||
// If not playing, then not approaching playback edge
|
||||
if (simpleExoPlayer == null || isLive() || !isPlaying()) return false;
|
||||
|
||||
final long currentPositionMillis = simpleExoPlayer.getCurrentPosition();
|
||||
final long currentDurationMillis = simpleExoPlayer.getDuration();
|
||||
|
|
|
@ -61,7 +61,6 @@ import org.schabi.newpipe.extractor.stream.VideoStream;
|
|||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItem;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItemBuilder;
|
||||
import org.schabi.newpipe.playlist.PlayQueueItemHolder;
|
||||
|
@ -97,12 +96,12 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
|
||||
private GestureDetector gestureDetector;
|
||||
|
||||
private boolean activityPaused;
|
||||
private VideoPlayerImpl playerImpl;
|
||||
|
||||
private SharedPreferences defaultPreferences;
|
||||
|
||||
@Nullable private StateSaver.SavedState savedState;
|
||||
@Nullable private PlayerState playerState;
|
||||
private boolean isInMultiWindow;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Activity LifeCycle
|
||||
|
@ -137,8 +136,9 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull Bundle bundle) {
|
||||
if (DEBUG) Log.d(TAG, "onRestoreInstanceState() called");
|
||||
super.onRestoreInstanceState(bundle);
|
||||
savedState = StateSaver.tryToRestore(bundle, this);
|
||||
StateSaver.tryToRestore(bundle, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -150,27 +150,28 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (DEBUG) Log.d(TAG, "onResume() called");
|
||||
if (isInMultiWindow()) return;
|
||||
if (playerImpl.getPlayer() != null && activityPaused && playerImpl.wasPlaying()
|
||||
&& !playerImpl.isPlaying()) {
|
||||
playerImpl.onPlay();
|
||||
}
|
||||
activityPaused = false;
|
||||
super.onResume();
|
||||
|
||||
if(globalScreenOrientationLocked()) {
|
||||
boolean lastOrientationWasLandscape
|
||||
= defaultPreferences.getBoolean(getString(R.string.last_orientation_landscape_key), false);
|
||||
if (globalScreenOrientationLocked()) {
|
||||
boolean lastOrientationWasLandscape = defaultPreferences.getBoolean(
|
||||
getString(R.string.last_orientation_landscape_key), false);
|
||||
setLandscape(lastOrientationWasLandscape);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (DEBUG) Log.d(TAG, "onBackPressed() called");
|
||||
super.onBackPressed();
|
||||
if (playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(false);
|
||||
// Upon going in or out of multiwindow mode, isInMultiWindow will always be false,
|
||||
// since the first onResume needs to restore the player.
|
||||
// Subsequent onResume calls while multiwindow mode remains the same and the player is
|
||||
// prepared should be ignored.
|
||||
if (isInMultiWindow) return;
|
||||
isInMultiWindow = isInMultiWindow();
|
||||
|
||||
if (playerState != null) {
|
||||
playerImpl.setPlaybackQuality(playerState.getPlaybackQuality());
|
||||
playerImpl.initPlayback(playerState.getPlayQueue(), playerState.getRepeatMode(),
|
||||
playerState.getPlaybackSpeed(), playerState.getPlaybackPitch(),
|
||||
playerState.wasPlaying());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -183,33 +184,24 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (DEBUG) Log.d(TAG, "onPause() called");
|
||||
if (isInMultiWindow()) return;
|
||||
if (playerImpl != null && playerImpl.getPlayer() != null && !activityPaused) {
|
||||
playerImpl.wasPlaying = playerImpl.isPlaying();
|
||||
playerImpl.onPause();
|
||||
}
|
||||
activityPaused = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
if (DEBUG) Log.d(TAG, "onSaveInstanceState() called");
|
||||
super.onSaveInstanceState(outState);
|
||||
if (playerImpl == null) return;
|
||||
|
||||
playerImpl.setRecovery();
|
||||
savedState = StateSaver.tryToSave(isChangingConfigurations(), savedState,
|
||||
outState, this);
|
||||
playerState = new PlayerState(playerImpl.getPlayQueue(), playerImpl.getRepeatMode(),
|
||||
playerImpl.getPlaybackSpeed(), playerImpl.getPlaybackPitch(),
|
||||
playerImpl.getPlaybackQuality(), playerImpl.isPlaying());
|
||||
StateSaver.tryToSave(isChangingConfigurations(), null, outState, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (DEBUG) Log.d(TAG, "onDestroy() called");
|
||||
if (playerImpl != null) playerImpl.destroy();
|
||||
protected void onStop() {
|
||||
if (DEBUG) Log.d(TAG, "onStop() called");
|
||||
super.onStop();
|
||||
playerImpl.destroy();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -224,26 +216,13 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||
@Override
|
||||
public void writeTo(Queue<Object> objectsToSave) {
|
||||
if (objectsToSave == null) return;
|
||||
objectsToSave.add(playerImpl.getPlayQueue());
|
||||
objectsToSave.add(playerImpl.getRepeatMode());
|
||||
objectsToSave.add(playerImpl.getPlaybackSpeed());
|
||||
objectsToSave.add(playerImpl.getPlaybackPitch());
|
||||
objectsToSave.add(playerImpl.getPlaybackQuality());
|
||||
objectsToSave.add(playerState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception {
|
||||
@NonNull final PlayQueue queue = (PlayQueue) savedObjects.poll();
|
||||
final int repeatMode = (int) savedObjects.poll();
|
||||
final float playbackSpeed = (float) savedObjects.poll();
|
||||
final float playbackPitch = (float) savedObjects.poll();
|
||||
@NonNull final String playbackQuality = (String) savedObjects.poll();
|
||||
|
||||
playerImpl.setPlaybackQuality(playbackQuality);
|
||||
playerImpl.initPlayback(queue, repeatMode, playbackSpeed, playbackPitch);
|
||||
|
||||
StateSaver.onDestroy(savedState);
|
||||
public void readFrom(@NonNull Queue<Object> savedObjects) {
|
||||
playerState = (PlayerState) savedObjects.poll();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
|
88
app/src/main/java/org/schabi/newpipe/player/PlayerState.java
Normal file
88
app/src/main/java/org/schabi/newpipe/player/PlayerState.java
Normal file
|
@ -0,0 +1,88 @@
|
|||
package org.schabi.newpipe.player;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import org.schabi.newpipe.playlist.PlayQueue;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class PlayerState implements Serializable {
|
||||
private final static String TAG = "PlayerState";
|
||||
|
||||
@NonNull private final PlayQueue playQueue;
|
||||
private final int repeatMode;
|
||||
private final float playbackSpeed;
|
||||
private final float playbackPitch;
|
||||
@Nullable private final String playbackQuality;
|
||||
private final boolean wasPlaying;
|
||||
|
||||
PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode,
|
||||
final float playbackSpeed, final float playbackPitch, final boolean wasPlaying) {
|
||||
this(playQueue, repeatMode, playbackSpeed, playbackPitch, null, wasPlaying);
|
||||
}
|
||||
|
||||
PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode,
|
||||
final float playbackSpeed, final float playbackPitch,
|
||||
@Nullable final String playbackQuality, final boolean wasPlaying) {
|
||||
this.playQueue = playQueue;
|
||||
this.repeatMode = repeatMode;
|
||||
this.playbackSpeed = playbackSpeed;
|
||||
this.playbackPitch = playbackPitch;
|
||||
this.playbackQuality = playbackQuality;
|
||||
this.wasPlaying = wasPlaying;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Serdes
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Nullable
|
||||
public static PlayerState fromJson(@NonNull final String json) {
|
||||
try {
|
||||
return new Gson().fromJson(json, PlayerState.class);
|
||||
} catch (JsonSyntaxException error) {
|
||||
Log.e(TAG, "Failed to deserialize PlayerState from json=[" + json + "]", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String toJson() {
|
||||
return new Gson().toJson(this);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@NonNull
|
||||
public PlayQueue getPlayQueue() {
|
||||
return playQueue;
|
||||
}
|
||||
|
||||
public int getRepeatMode() {
|
||||
return repeatMode;
|
||||
}
|
||||
|
||||
public float getPlaybackSpeed() {
|
||||
return playbackSpeed;
|
||||
}
|
||||
|
||||
public float getPlaybackPitch() {
|
||||
return playbackPitch;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPlaybackQuality() {
|
||||
return playbackQuality;
|
||||
}
|
||||
|
||||
public boolean wasPlaying() {
|
||||
return wasPlaying;
|
||||
}
|
||||
}
|
|
@ -228,8 +228,8 @@ public abstract class VideoPlayer extends BasePlayer
|
|||
}
|
||||
|
||||
@Override
|
||||
public void initPlayer() {
|
||||
super.initPlayer();
|
||||
public void initPlayer(final boolean playOnReady) {
|
||||
super.initPlayer(playOnReady);
|
||||
|
||||
// Setup video view
|
||||
simpleExoPlayer.setVideoSurfaceView(surfaceView);
|
||||
|
|
|
@ -87,16 +87,6 @@ public class ManagedMediaSourcePlaylist {
|
|||
internalSource.moveMediaSource(source, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the {@link ManagedMediaSource} at the given index by replacing it
|
||||
* with a {@link PlaceholderMediaSource}.
|
||||
* @see #invalidate(int, Runnable)
|
||||
* @see #update(int, ManagedMediaSource, Runnable)
|
||||
* */
|
||||
public synchronized void invalidate(final int index) {
|
||||
invalidate(index, /*doNothing=*/null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the {@link ManagedMediaSource} at the given index by replacing it
|
||||
* with a {@link PlaceholderMediaSource}.
|
||||
|
|
|
@ -24,8 +24,10 @@ import org.schabi.newpipe.playlist.events.RemoveEvent;
|
|||
import org.schabi.newpipe.playlist.events.ReorderEvent;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
@ -38,6 +40,7 @@ import io.reactivex.disposables.Disposable;
|
|||
import io.reactivex.disposables.SerialDisposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.internal.subscriptions.EmptySubscription;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
import static org.schabi.newpipe.player.mediasource.FailedMediaSource.MediaSourceResolutionException;
|
||||
|
@ -338,12 +341,14 @@ public class MediaSourceManager {
|
|||
|
||||
private Observable<Long> getEdgeIntervalSignal() {
|
||||
return Observable.interval(progressUpdateIntervalMillis, TimeUnit.MILLISECONDS)
|
||||
.filter(ignored -> playbackListener.isNearPlaybackEdge(playbackNearEndGapMillis));
|
||||
.filter(ignored ->
|
||||
playbackListener.isApproachingPlaybackEdge(playbackNearEndGapMillis));
|
||||
}
|
||||
|
||||
private Disposable getDebouncedLoader() {
|
||||
return debouncedSignal.mergeWith(nearEndIntervalSignal)
|
||||
.debounce(loadDebounceMillis, TimeUnit.MILLISECONDS)
|
||||
.subscribeOn(Schedulers.single())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(timestamp -> loadImmediate());
|
||||
}
|
||||
|
@ -352,7 +357,7 @@ public class MediaSourceManager {
|
|||
debouncedSignal.onNext(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private synchronized void loadImmediate() {
|
||||
private void loadImmediate() {
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - loadImmediate() called");
|
||||
final ItemsToLoad itemsToLoad = getItemsToLoad(playQueue, WINDOW_SIZE);
|
||||
if (itemsToLoad == null) return;
|
||||
|
@ -411,7 +416,7 @@ public class MediaSourceManager {
|
|||
|
||||
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 (isCorrectionNeeded(item)) {
|
||||
if (DEBUG) Log.d(TAG, "MediaSource - Updating index=[" + itemIndex + "] with " +
|
||||
"title=[" + item.getTitle() + "] at url=[" + item.getUrl() + "]");
|
||||
playlist.update(itemIndex, mediaSource, this::maybeSynchronizePlayer);
|
||||
|
|
|
@ -13,13 +13,13 @@ import java.util.List;
|
|||
public interface PlaybackListener {
|
||||
|
||||
/**
|
||||
* Called to check if the currently playing stream is close to the end of its playback.
|
||||
* Implementation should return true when the current playback position is within
|
||||
* timeToEndMillis or less until its playback completes or transitions.
|
||||
* Called to check if the currently playing stream is approaching the end of its playback.
|
||||
* Implementation should return true when the current playback position is progressing within
|
||||
* timeToEndMillis or less to its playback during.
|
||||
*
|
||||
* May be called at any time.
|
||||
* */
|
||||
boolean isNearPlaybackEdge(final long timeToEndMillis);
|
||||
boolean isApproachingPlaybackEdge(final long timeToEndMillis);
|
||||
|
||||
/**
|
||||
* Called when the stream at the current queue index is not ready yet.
|
||||
|
|
Loading…
Reference in a new issue