Player/handleIntent: separate out the timestamp request into enum
Instead of implicitely reconstructing whether the intent was intended (lol) to be a timestamp change, we create a new kind of intent that *only* sets the data we need to switch to a new timestamp. This means that the logic of what to do (opening a popup player) gets moved from `InternalUrlsHandler.playOnPopup` to the `Player.handleIntent` method, we only pass that we want to jump to a new timestamp. Thus, the stream is now loaded *after* sending the intent instead of before sending. This is somewhat messy right now and still does not fix the issue of queue deletion, but from now on the queue logic should get more straightforward to implement. In the end, everything should be a giant switch. Thus we don’t fall-through anymore, but run the post-setup code manually by calling `handeIntentPost` and then returning.
This commit is contained in:
parent
66cf7bf4ca
commit
2d0b9700d3
6 changed files with 121 additions and 84 deletions
|
|
@ -109,6 +109,7 @@ import org.schabi.newpipe.player.playback.MediaSourceManager;
|
|||
import org.schabi.newpipe.player.playback.PlaybackListener;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
|
||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType;
|
||||
|
|
@ -118,8 +119,10 @@ import org.schabi.newpipe.player.ui.PlayerUiList;
|
|||
import org.schabi.newpipe.player.ui.PopupPlayerUi;
|
||||
import org.schabi.newpipe.player.ui.VideoPlayerUi;
|
||||
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.SerializedCache;
|
||||
import org.schabi.newpipe.util.StreamTypeUtil;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
|
|
@ -130,9 +133,11 @@ import java.util.stream.IntStream;
|
|||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.disposables.SerialDisposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public final class Player implements PlaybackListener, Listener {
|
||||
public static final boolean DEBUG = MainActivity.DEBUG;
|
||||
|
|
@ -160,6 +165,7 @@ public final class Player implements PlaybackListener, Listener {
|
|||
public static final String PLAY_WHEN_READY = "play_when_ready";
|
||||
public static final String PLAYER_TYPE = "player_type";
|
||||
public static final String PLAYER_INTENT_TYPE = "player_intent_type";
|
||||
public static final String PLAYER_INTENT_DATA = "player_intent_data";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Time constants
|
||||
|
|
@ -244,6 +250,8 @@ public final class Player implements PlaybackListener, Listener {
|
|||
private final SerialDisposable progressUpdateDisposable = new SerialDisposable();
|
||||
@NonNull
|
||||
private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable();
|
||||
@NonNull
|
||||
private final CompositeDisposable streamItemDisposable = new CompositeDisposable();
|
||||
|
||||
// This is the only listener we need for thumbnail loading, since there is always at most only
|
||||
// one thumbnail being loaded at a time. This field is also here to maintain a strong reference,
|
||||
|
|
@ -344,18 +352,31 @@ public final class Player implements PlaybackListener, Listener {
|
|||
|
||||
@SuppressWarnings("MethodLength")
|
||||
public void handleIntent(@NonNull final Intent intent) {
|
||||
// fail fast if no play queue was provided
|
||||
final String queueCache = intent.getStringExtra(PLAY_QUEUE_KEY);
|
||||
if (queueCache == null) {
|
||||
|
||||
final PlayerIntentType playerIntentType = intent.getParcelableExtra(PLAYER_INTENT_TYPE);
|
||||
if (playerIntentType == null) {
|
||||
return;
|
||||
}
|
||||
final PlayQueue newQueue = SerializedCache.getInstance().take(queueCache, PlayQueue.class);
|
||||
if (newQueue == null) {
|
||||
return;
|
||||
final PlayerType newPlayerType;
|
||||
// TODO: this should be in the second switch below, but I’m not sure whether I
|
||||
// can move the initUIs stuff without breaking the setup for edge cases somehow.
|
||||
switch (playerIntentType) {
|
||||
case TimestampChange -> {
|
||||
// TODO: this breaks out of the pattern of asking for the permission before
|
||||
// sending the PlayerIntent, but I’m not sure yet how to combine the permissions
|
||||
// with the new enum approach. Maybe it’s better that the player asks anyway?
|
||||
if (!PermissionHelper.isPopupEnabledElseAsk(context)) {
|
||||
return;
|
||||
}
|
||||
newPlayerType = PlayerType.POPUP;
|
||||
}
|
||||
default -> {
|
||||
newPlayerType = PlayerType.retrieveFromIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
final PlayerType oldPlayerType = playerType;
|
||||
playerType = PlayerType.retrieveFromIntent(intent);
|
||||
playerType = newPlayerType;
|
||||
initUIsForCurrentPlayerType();
|
||||
isAudioOnly = audioPlayerSelected();
|
||||
|
||||
|
|
@ -363,29 +384,61 @@ public final class Player implements PlaybackListener, Listener {
|
|||
videoResolver.setPlaybackQuality(intent.getStringExtra(PLAYBACK_QUALITY));
|
||||
}
|
||||
|
||||
final PlayerIntentType playerIntentType = intent.getParcelableExtra(PLAYER_INTENT_TYPE);
|
||||
final boolean playWhenReady = intent.getBooleanExtra(PLAY_WHEN_READY, true);
|
||||
|
||||
switch (playerIntentType) {
|
||||
case Enqueue -> {
|
||||
if (playQueue != null) {
|
||||
final PlayQueue newQueue = getPlayQueueFromCache(intent);
|
||||
if (newQueue == null) {
|
||||
return;
|
||||
}
|
||||
playQueue.append(newQueue.getStreams());
|
||||
}
|
||||
return;
|
||||
}
|
||||
case EnqueueNext -> {
|
||||
if (playQueue != null) {
|
||||
final PlayQueue newQueue = getPlayQueueFromCache(intent);
|
||||
if (newQueue == null) {
|
||||
return;
|
||||
}
|
||||
final int currentIndex = playQueue.getIndex();
|
||||
playQueue.append(newQueue.getStreams());
|
||||
playQueue.move(playQueue.size() - 1, currentIndex + 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
case TimestampChange -> {
|
||||
final TimestampChangeData dat = intent.getParcelableExtra(PLAYER_INTENT_DATA);
|
||||
assert dat != null;
|
||||
final Single<StreamInfo> single =
|
||||
ExtractorHelper.getStreamInfo(dat.getServiceId(), dat.getUrl(), false);
|
||||
streamItemDisposable.add(single.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(info -> {
|
||||
final PlayQueue newPlayQueue = new SinglePlayQueue(info,
|
||||
dat.getSeconds() * 1000L);
|
||||
NavigationHelper.playOnPopupPlayer(context, playQueue, false);
|
||||
}, throwable -> {
|
||||
// This will only show a snackbar if the passed context has a root view:
|
||||
// otherwise it will resort to showing a notification, so we are safe
|
||||
// here.
|
||||
ErrorUtil.createNotification(context,
|
||||
new ErrorInfo(throwable, UserAction.PLAY_ON_POPUP, dat.getUrl(),
|
||||
null, dat.getUrl()));
|
||||
}));
|
||||
return;
|
||||
}
|
||||
case AllOthers -> {
|
||||
// fallthrough; TODO: put other intent data in separate cases
|
||||
}
|
||||
}
|
||||
|
||||
final boolean playWhenReady = intent.getBooleanExtra(PLAY_WHEN_READY, true);
|
||||
final PlayQueue newQueue = getPlayQueueFromCache(intent);
|
||||
if (newQueue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// branching parameters for below
|
||||
final boolean samePlayQueue = playQueue != null && playQueue.equalStreamsAndIndex(newQueue);
|
||||
|
|
@ -466,6 +519,10 @@ public final class Player implements PlaybackListener, Listener {
|
|||
initPlayback(samePlayQueue ? playQueue : newQueue, playWhenReady);
|
||||
}
|
||||
|
||||
handleIntentPost(oldPlayerType);
|
||||
}
|
||||
|
||||
private void handleIntentPost(final PlayerType oldPlayerType) {
|
||||
if (oldPlayerType != playerType && playQueue != null) {
|
||||
// If playerType changes from one to another we should reload the player
|
||||
// (to disable/enable video stream or to set quality)
|
||||
|
|
@ -476,6 +533,19 @@ public final class Player implements PlaybackListener, Listener {
|
|||
NavigationHelper.sendPlayerStartedEvent(context);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PlayQueue getPlayQueueFromCache(@NonNull final Intent intent) {
|
||||
final String queueCache = intent.getStringExtra(PLAY_QUEUE_KEY);
|
||||
if (queueCache == null) {
|
||||
return null;
|
||||
}
|
||||
final PlayQueue newQueue = SerializedCache.getInstance().take(queueCache, PlayQueue.class);
|
||||
if (newQueue == null) {
|
||||
return null;
|
||||
}
|
||||
return newQueue;
|
||||
}
|
||||
|
||||
private void initUIsForCurrentPlayerType() {
|
||||
if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN)
|
||||
|| (UIs.get(PopupPlayerUi.class).isPresent() && playerType == PlayerType.POPUP)) {
|
||||
|
|
@ -605,6 +675,7 @@ public final class Player implements PlaybackListener, Listener {
|
|||
|
||||
databaseUpdateDisposable.clear();
|
||||
progressUpdateDisposable.set(null);
|
||||
streamItemDisposable.clear();
|
||||
cancelLoadingCurrentThumbnail();
|
||||
|
||||
UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object
|
||||
|
|
|
|||
|
|
@ -11,5 +11,16 @@ import kotlinx.parcelize.Parcelize
|
|||
enum class PlayerIntentType : Parcelable {
|
||||
Enqueue,
|
||||
EnqueueNext,
|
||||
TimestampChange,
|
||||
AllOthers
|
||||
}
|
||||
|
||||
/**
|
||||
* A timestamp on the given was clicked and we should switch the playing stream to it.
|
||||
*/
|
||||
@Parcelize
|
||||
data class TimestampChangeData(
|
||||
val serviceId: Int,
|
||||
val url: String,
|
||||
val seconds: Int
|
||||
) : Parcelable
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue