109109import org .schabi .newpipe .player .playback .PlaybackListener ;
110110import org .schabi .newpipe .player .playqueue .PlayQueue ;
111111import org .schabi .newpipe .player .playqueue .PlayQueueItem ;
112+ import org .schabi .newpipe .player .playqueue .SinglePlayQueue ;
112113import org .schabi .newpipe .player .resolver .AudioPlaybackResolver ;
113114import org .schabi .newpipe .player .resolver .VideoPlaybackResolver ;
114115import org .schabi .newpipe .player .resolver .VideoPlaybackResolver .SourceType ;
118119import org .schabi .newpipe .player .ui .PopupPlayerUi ;
119120import org .schabi .newpipe .player .ui .VideoPlayerUi ;
120121import org .schabi .newpipe .util .DependentPreferenceHelper ;
122+ import org .schabi .newpipe .util .ExtractorHelper ;
121123import org .schabi .newpipe .util .ListHelper ;
122124import org .schabi .newpipe .util .NavigationHelper ;
125+ import org .schabi .newpipe .util .PermissionHelper ;
123126import org .schabi .newpipe .util .SerializedCache ;
124127import org .schabi .newpipe .util .StreamTypeUtil ;
125128import org .schabi .newpipe .util .image .PicassoHelper ;
130133
131134import io .reactivex .rxjava3 .android .schedulers .AndroidSchedulers ;
132135import io .reactivex .rxjava3 .core .Observable ;
136+ import io .reactivex .rxjava3 .core .Single ;
133137import io .reactivex .rxjava3 .disposables .CompositeDisposable ;
134138import io .reactivex .rxjava3 .disposables .Disposable ;
135139import io .reactivex .rxjava3 .disposables .SerialDisposable ;
140+ import io .reactivex .rxjava3 .schedulers .Schedulers ;
136141
137142public final class Player implements PlaybackListener , Listener {
138143 public static final boolean DEBUG = MainActivity .DEBUG ;
@@ -160,6 +165,7 @@ public final class Player implements PlaybackListener, Listener {
160165 public static final String PLAY_WHEN_READY = "play_when_ready" ;
161166 public static final String PLAYER_TYPE = "player_type" ;
162167 public static final String PLAYER_INTENT_TYPE = "player_intent_type" ;
168+ public static final String PLAYER_INTENT_DATA = "player_intent_data" ;
163169
164170 /*//////////////////////////////////////////////////////////////////////////
165171 // Time constants
@@ -244,6 +250,8 @@ public final class Player implements PlaybackListener, Listener {
244250 private final SerialDisposable progressUpdateDisposable = new SerialDisposable ();
245251 @ NonNull
246252 private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable ();
253+ @ NonNull
254+ private final CompositeDisposable streamItemDisposable = new CompositeDisposable ();
247255
248256 // This is the only listener we need for thumbnail loading, since there is always at most only
249257 // one thumbnail being loaded at a time. This field is also here to maintain a strong reference,
@@ -344,18 +352,31 @@ public int getOverrideResolutionIndex(final List<VideoStream> sortedVideos,
344352
345353 @ SuppressWarnings ("MethodLength" )
346354 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 ) {
355+
356+ final PlayerIntentType playerIntentType = intent .getParcelableExtra ( PLAYER_INTENT_TYPE );
357+ if (playerIntentType == null ) {
350358 return ;
351359 }
352- final PlayQueue newQueue = SerializedCache .getInstance ().take (queueCache , PlayQueue .class );
353- if (newQueue == null ) {
354- return ;
360+ final PlayerType newPlayerType ;
361+ // TODO: this should be in the second switch below, but I’m not sure whether I
362+ // can move the initUIs stuff without breaking the setup for edge cases somehow.
363+ switch (playerIntentType ) {
364+ case TimestampChange -> {
365+ // TODO: this breaks out of the pattern of asking for the permission before
366+ // sending the PlayerIntent, but I’m not sure yet how to combine the permissions
367+ // with the new enum approach. Maybe it’s better that the player asks anyway?
368+ if (!PermissionHelper .isPopupEnabledElseAsk (context )) {
369+ return ;
370+ }
371+ newPlayerType = PlayerType .POPUP ;
372+ }
373+ default -> {
374+ newPlayerType = PlayerType .retrieveFromIntent (intent );
375+ }
355376 }
356377
357378 final PlayerType oldPlayerType = playerType ;
358- playerType = PlayerType . retrieveFromIntent ( intent ) ;
379+ playerType = newPlayerType ;
359380 initUIsForCurrentPlayerType ();
360381 // TODO: what does the following comment mean? Is that a relict?
361382 // We need to setup audioOnly before super(), see "sourceOf"
@@ -365,29 +386,63 @@ public void handleIntent(@NonNull final Intent intent) {
365386 videoResolver .setPlaybackQuality (intent .getStringExtra (PLAYBACK_QUALITY ));
366387 }
367388
368- final PlayerIntentType playerIntentType = intent .getParcelableExtra ( PLAYER_INTENT_TYPE );
389+ final boolean playWhenReady = intent .getBooleanExtra ( PLAY_WHEN_READY , true );
369390
370391 switch (playerIntentType ) {
371392 case Enqueue -> {
372393 if (playQueue != null ) {
394+ final PlayQueue newQueue = getPlayQueueFromCache (intent );
395+ if (newQueue == null ) {
396+ return ;
397+ }
373398 playQueue .append (newQueue .getStreams ());
374399 }
375400 return ;
376401 }
377402 case EnqueueNext -> {
378403 if (playQueue != null ) {
404+ final PlayQueue newQueue = getPlayQueueFromCache (intent );
405+ if (newQueue == null ) {
406+ return ;
407+ }
379408 final int currentIndex = playQueue .getIndex ();
380409 playQueue .append (newQueue .getStreams ());
381410 playQueue .move (playQueue .size () - 1 , currentIndex + 1 );
382411 }
383412 return ;
384413 }
414+ case TimestampChange -> {
415+ final TimestampChangeData dat = intent .getParcelableExtra (PLAYER_INTENT_DATA );
416+ assert dat != null ;
417+ final Single <StreamInfo > single =
418+ ExtractorHelper .getStreamInfo (dat .getServiceId (), dat .getUrl (), false );
419+ streamItemDisposable .add (single .subscribeOn (Schedulers .io ())
420+ .observeOn (AndroidSchedulers .mainThread ())
421+ .subscribe (info -> {
422+ final PlayQueue newPlayQueue = new SinglePlayQueue (info ,
423+ dat .getSeconds () * 1000L );
424+ NavigationHelper .playOnPopupPlayer (context , playQueue , false );
425+ }, throwable -> {
426+ final var errorInfo = new ErrorInfo (throwable , UserAction .PLAY_ON_POPUP ,
427+ dat .getUrl ());
428+ // This will only show a snackbar if the passed context has a root view:
429+ // otherwise it will resort to showing a notification, so we are safe
430+ // here.
431+ ErrorUtil .showSnackbar (context ,
432+ new ErrorInfo (throwable , UserAction .PLAY_ON_POPUP , dat .getUrl (),
433+ null , dat .getUrl ()));
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
0 commit comments