Skip to content

Commit a579cdb

Browse files
committed
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.
1 parent c2fa538 commit a579cdb

6 files changed

Lines changed: 124 additions & 85 deletions

File tree

app/src/main/java/org/schabi/newpipe/player/Player.java

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import static org.schabi.newpipe.util.ListHelper.getResolutionIndex;
4747
import static java.util.concurrent.TimeUnit.MILLISECONDS;
4848

49+
import android.app.AlertDialog;
4950
import android.content.BroadcastReceiver;
5051
import android.content.Context;
5152
import android.content.Intent;
@@ -86,6 +87,7 @@
8687
import org.schabi.newpipe.R;
8788
import org.schabi.newpipe.databinding.PlayerBinding;
8889
import org.schabi.newpipe.error.ErrorInfo;
90+
import org.schabi.newpipe.error.ErrorPanelHelper;
8991
import org.schabi.newpipe.error.ErrorUtil;
9092
import org.schabi.newpipe.error.UserAction;
9193
import org.schabi.newpipe.extractor.Image;
@@ -109,6 +111,7 @@
109111
import org.schabi.newpipe.player.playback.PlaybackListener;
110112
import org.schabi.newpipe.player.playqueue.PlayQueue;
111113
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
114+
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
112115
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
113116
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
114117
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType;
@@ -118,21 +121,25 @@
118121
import org.schabi.newpipe.player.ui.PopupPlayerUi;
119122
import org.schabi.newpipe.player.ui.VideoPlayerUi;
120123
import org.schabi.newpipe.util.DependentPreferenceHelper;
124+
import org.schabi.newpipe.util.ExtractorHelper;
121125
import org.schabi.newpipe.util.ListHelper;
122126
import org.schabi.newpipe.util.NavigationHelper;
127+
import org.schabi.newpipe.util.PermissionHelper;
128+
import org.schabi.newpipe.util.image.PicassoHelper;
123129
import org.schabi.newpipe.util.SerializedCache;
124130
import org.schabi.newpipe.util.StreamTypeUtil;
125-
import org.schabi.newpipe.util.image.PicassoHelper;
126131

127132
import java.util.List;
128133
import java.util.Optional;
129134
import java.util.stream.IntStream;
130135

131136
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
132137
import io.reactivex.rxjava3.core.Observable;
138+
import io.reactivex.rxjava3.core.Single;
133139
import io.reactivex.rxjava3.disposables.CompositeDisposable;
134140
import io.reactivex.rxjava3.disposables.Disposable;
135141
import io.reactivex.rxjava3.disposables.SerialDisposable;
142+
import io.reactivex.rxjava3.schedulers.Schedulers;
136143

137144
public final class Player implements PlaybackListener, Listener {
138145
public static final boolean DEBUG = MainActivity.DEBUG;
@@ -160,6 +167,7 @@ public final class Player implements PlaybackListener, Listener {
160167
public static final String PLAY_WHEN_READY = "play_when_ready";
161168
public static final String PLAYER_TYPE = "player_type";
162169
public static final String PLAYER_INTENT_TYPE = "player_intent_type";
170+
public static final String PLAYER_INTENT_DATA = "player_intent_data";
163171

164172
/*//////////////////////////////////////////////////////////////////////////
165173
// Time constants
@@ -244,6 +252,8 @@ public final class Player implements PlaybackListener, Listener {
244252
private final SerialDisposable progressUpdateDisposable = new SerialDisposable();
245253
@NonNull
246254
private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable();
255+
@NonNull
256+
private final CompositeDisposable streamItemDisposable = new CompositeDisposable();
247257

248258
// This is the only listener we need for thumbnail loading, since there is always at most only
249259
// one thumbnail being loaded at a time. This field is also here to maintain a strong reference,
@@ -344,18 +354,31 @@ public int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
344354

345355
@SuppressWarnings("MethodLength")
346356
public void handleIntent(@NonNull final Intent intent) {
347-
// fail fast if no play queue was provided
348-
final String queueCache = intent.getStringExtra(PLAY_QUEUE_KEY);
349-
if (queueCache == null) {
357+
358+
final PlayerIntentType playerIntentType = intent.getParcelableExtra(PLAYER_INTENT_TYPE);
359+
if (playerIntentType == null) {
350360
return;
351361
}
352-
final PlayQueue newQueue = SerializedCache.getInstance().take(queueCache, PlayQueue.class);
353-
if (newQueue == null) {
354-
return;
362+
final PlayerType newPlayerType;
363+
// TODO: this should be in the second switch below, but I’m not sure whether I
364+
// can move the initUIs stuff without breaking the setup for edge cases somehow.
365+
switch (playerIntentType) {
366+
case TimestampChange -> {
367+
// TODO: this breaks out of the pattern of asking for the permission before
368+
// sending the PlayerIntent, but I’m not sure yet how to combine the permissions
369+
// with the new enum approach. Maybe it’s better that the player asks anyway?
370+
if (!PermissionHelper.isPopupEnabledElseAsk(context)) {
371+
return;
372+
}
373+
newPlayerType = PlayerType.POPUP;
374+
}
375+
default -> {
376+
newPlayerType = PlayerType.retrieveFromIntent(intent);
377+
}
355378
}
356379

357380
final PlayerType oldPlayerType = playerType;
358-
playerType = PlayerType.retrieveFromIntent(intent);
381+
playerType = newPlayerType;
359382
initUIsForCurrentPlayerType();
360383
// TODO: what does the following comment mean? Is that a relict?
361384
// We need to setup audioOnly before super(), see "sourceOf"
@@ -365,29 +388,61 @@ public void handleIntent(@NonNull final Intent intent) {
365388
videoResolver.setPlaybackQuality(intent.getStringExtra(PLAYBACK_QUALITY));
366389
}
367390

368-
final PlayerIntentType playerIntentType = intent.getParcelableExtra(PLAYER_INTENT_TYPE);
391+
final boolean playWhenReady = intent.getBooleanExtra(PLAY_WHEN_READY, true);
369392

370393
switch (playerIntentType) {
371394
case Enqueue -> {
372395
if (playQueue != null) {
396+
final PlayQueue newQueue = getPlayQueueFromCache(intent);
397+
if (newQueue == null) {
398+
return;
399+
}
373400
playQueue.append(newQueue.getStreams());
374401
}
375402
return;
376403
}
377404
case EnqueueNext -> {
378405
if (playQueue != null) {
406+
final PlayQueue newQueue = getPlayQueueFromCache(intent);
407+
if (newQueue == null) {
408+
return;
409+
}
379410
final int currentIndex = playQueue.getIndex();
380411
playQueue.append(newQueue.getStreams());
381412
playQueue.move(playQueue.size() - 1, currentIndex + 1);
382413
}
383414
return;
384415
}
416+
case TimestampChange -> {
417+
final TimestampChangeData dat = intent.getParcelableExtra(PLAYER_INTENT_DATA);
418+
assert dat != null;
419+
final Single<StreamInfo> single =
420+
ExtractorHelper.getStreamInfo(dat.getServiceId(), dat.getUrl(), false);
421+
streamItemDisposable.add(single.subscribeOn(Schedulers.io())
422+
.observeOn(AndroidSchedulers.mainThread())
423+
.subscribe(info -> {
424+
final PlayQueue newPlayQueue = new SinglePlayQueue(info,
425+
dat.getSeconds() * 1000L);
426+
NavigationHelper.playOnPopupPlayer(context, playQueue, false);
427+
}, throwable -> {
428+
final var errorInfo = new ErrorInfo(throwable, UserAction.PLAY_ON_POPUP,
429+
dat.getUrl());
430+
// This will only show a snackbar if the passed context has a root view:
431+
// otherwise it will resort to showing a notification, so we are safe
432+
// here.
433+
ErrorUtil.showSnackbar(context, errorInfo);
434+
}));
435+
return;
436+
}
385437
case AllOthers -> {
386438
// fallthrough; TODO: put other intent data in separate cases
387439
}
388440
}
389441

390-
final boolean playWhenReady = intent.getBooleanExtra(PLAY_WHEN_READY, true);
442+
final PlayQueue newQueue = getPlayQueueFromCache(intent);
443+
if (newQueue == null) {
444+
return;
445+
}
391446

392447
// branching parameters for below
393448
final boolean samePlayQueue = playQueue != null && playQueue.equalStreamsAndIndex(newQueue);
@@ -468,6 +523,10 @@ public void handleIntent(@NonNull final Intent intent) {
468523
initPlayback(samePlayQueue ? playQueue : newQueue, playWhenReady);
469524
}
470525

526+
handleIntentPost(oldPlayerType);
527+
}
528+
529+
private void handleIntentPost(final PlayerType oldPlayerType) {
471530
if (oldPlayerType != playerType && playQueue != null) {
472531
// If playerType changes from one to another we should reload the player
473532
// (to disable/enable video stream or to set quality)
@@ -478,6 +537,19 @@ public void handleIntent(@NonNull final Intent intent) {
478537
NavigationHelper.sendPlayerStartedEvent(context);
479538
}
480539

540+
@Nullable
541+
private static PlayQueue getPlayQueueFromCache(@NonNull final Intent intent) {
542+
final String queueCache = intent.getStringExtra(PLAY_QUEUE_KEY);
543+
if (queueCache == null) {
544+
return null;
545+
}
546+
final PlayQueue newQueue = SerializedCache.getInstance().take(queueCache, PlayQueue.class);
547+
if (newQueue == null) {
548+
return null;
549+
}
550+
return newQueue;
551+
}
552+
481553
private void initUIsForCurrentPlayerType() {
482554
if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN)
483555
|| (UIs.get(PopupPlayerUi.class).isPresent() && playerType == PlayerType.POPUP)) {
@@ -607,6 +679,7 @@ public void destroy() {
607679

608680
databaseUpdateDisposable.clear();
609681
progressUpdateDisposable.set(null);
682+
streamItemDisposable.clear();
610683
cancelLoadingCurrentThumbnail();
611684

612685
UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object

app/src/main/java/org/schabi/newpipe/player/PlayerIntentType.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,16 @@ import kotlinx.parcelize.Parcelize
1111
enum class PlayerIntentType : Parcelable {
1212
Enqueue,
1313
EnqueueNext,
14+
TimestampChange,
1415
AllOthers
1516
}
17+
18+
/**
19+
* A timestamp on the given was clicked and we should switch the playing stream to it.
20+
*/
21+
@Parcelize
22+
data class TimestampChangeData(
23+
val serviceId: Int,
24+
val url: String,
25+
val seconds: Int
26+
) : Parcelable

app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.schabi.newpipe.player.PlayerIntentType;
6262
import org.schabi.newpipe.player.PlayerService;
6363
import org.schabi.newpipe.player.PlayerType;
64+
import org.schabi.newpipe.player.TimestampChangeData;
6465
import org.schabi.newpipe.player.helper.PlayerHelper;
6566
import org.schabi.newpipe.player.helper.PlayerHolder;
6667
import org.schabi.newpipe.player.playqueue.PlayQueue;
@@ -103,6 +104,18 @@ public static <T> Intent getPlayerIntent(@NonNull final Context context,
103104
return intent;
104105
}
105106

107+
@NonNull
108+
public static Intent getPlayerTimestampIntent(@NonNull final Context context,
109+
@NonNull final TimestampChangeData
110+
timestampChangeData) {
111+
final Intent intent = new Intent(context, PlayerService.class);
112+
113+
intent.putExtra(Player.PLAYER_INTENT_TYPE, (Parcelable) PlayerIntentType.TimestampChange);
114+
intent.putExtra(Player.PLAYER_INTENT_DATA, timestampChangeData);
115+
116+
return intent;
117+
}
118+
106119
@NonNull
107120
public static <T> Intent getPlayerEnqueueNextIntent(@NonNull final Context context,
108121
@NonNull final Class<T> targetClazz,

0 commit comments

Comments
 (0)