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,48 +352,93 @@ 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 isAudioOnly = audioPlayerSelected ();
361382
362383 if (intent .hasExtra (PLAYBACK_QUALITY )) {
363384 videoResolver .setPlaybackQuality (intent .getStringExtra (PLAYBACK_QUALITY ));
364385 }
365386
366- final PlayerIntentType playerIntentType = intent .getParcelableExtra ( PLAYER_INTENT_TYPE );
387+ final boolean playWhenReady = intent .getBooleanExtra ( PLAY_WHEN_READY , true );
367388
368389 switch (playerIntentType ) {
369390 case Enqueue -> {
370391 if (playQueue != null ) {
392+ final PlayQueue newQueue = getPlayQueueFromCache (intent );
393+ if (newQueue == null ) {
394+ return ;
395+ }
371396 playQueue .append (newQueue .getStreams ());
372397 }
373398 return ;
374399 }
375400 case EnqueueNext -> {
376401 if (playQueue != null ) {
402+ final PlayQueue newQueue = getPlayQueueFromCache (intent );
403+ if (newQueue == null ) {
404+ return ;
405+ }
377406 final int currentIndex = playQueue .getIndex ();
378407 playQueue .append (newQueue .getStreams ());
379408 playQueue .move (playQueue .size () - 1 , currentIndex + 1 );
380409 }
381410 return ;
382411 }
412+ case TimestampChange -> {
413+ final TimestampChangeData dat = intent .getParcelableExtra (PLAYER_INTENT_DATA );
414+ assert dat != null ;
415+ final Single <StreamInfo > single =
416+ ExtractorHelper .getStreamInfo (dat .getServiceId (), dat .getUrl (), false );
417+ streamItemDisposable .add (single .subscribeOn (Schedulers .io ())
418+ .observeOn (AndroidSchedulers .mainThread ())
419+ .subscribe (info -> {
420+ final PlayQueue newPlayQueue = new SinglePlayQueue (info ,
421+ dat .getSeconds () * 1000L );
422+ NavigationHelper .playOnPopupPlayer (context , playQueue , false );
423+ }, throwable -> {
424+ // This will only show a snackbar if the passed context has a root view:
425+ // otherwise it will resort to showing a notification, so we are safe
426+ // here.
427+ ErrorUtil .createNotification (context ,
428+ new ErrorInfo (throwable , UserAction .PLAY_ON_POPUP , dat .getUrl (),
429+ null , dat .getUrl ()));
430+ }));
431+ return ;
432+ }
383433 case AllOthers -> {
384434 // fallthrough; TODO: put other intent data in separate cases
385435 }
386436 }
387437
388- final boolean playWhenReady = intent .getBooleanExtra (PLAY_WHEN_READY , true );
438+ final PlayQueue newQueue = getPlayQueueFromCache (intent );
439+ if (newQueue == null ) {
440+ return ;
441+ }
389442
390443 // branching parameters for below
391444 final boolean samePlayQueue = playQueue != null && playQueue .equalStreamsAndIndex (newQueue );
@@ -466,6 +519,10 @@ public void handleIntent(@NonNull final Intent intent) {
466519 initPlayback (samePlayQueue ? playQueue : newQueue , playWhenReady );
467520 }
468521
522+ handleIntentPost (oldPlayerType );
523+ }
524+
525+ private void handleIntentPost (final PlayerType oldPlayerType ) {
469526 if (oldPlayerType != playerType && playQueue != null ) {
470527 // If playerType changes from one to another we should reload the player
471528 // (to disable/enable video stream or to set quality)
@@ -476,6 +533,19 @@ public void handleIntent(@NonNull final Intent intent) {
476533 NavigationHelper .sendPlayerStartedEvent (context );
477534 }
478535
536+ @ Nullable
537+ private static PlayQueue getPlayQueueFromCache (@ NonNull final Intent intent ) {
538+ final String queueCache = intent .getStringExtra (PLAY_QUEUE_KEY );
539+ if (queueCache == null ) {
540+ return null ;
541+ }
542+ final PlayQueue newQueue = SerializedCache .getInstance ().take (queueCache , PlayQueue .class );
543+ if (newQueue == null ) {
544+ return null ;
545+ }
546+ return newQueue ;
547+ }
548+
479549 private void initUIsForCurrentPlayerType () {
480550 if ((UIs .get (MainPlayerUi .class ).isPresent () && playerType == PlayerType .MAIN )
481551 || (UIs .get (PopupPlayerUi .class ).isPresent () && playerType == PlayerType .POPUP )) {
@@ -605,6 +675,7 @@ public void destroy() {
605675
606676 databaseUpdateDisposable .clear ();
607677 progressUpdateDisposable .set (null );
678+ streamItemDisposable .clear ();
608679 cancelLoadingCurrentThumbnail ();
609680
610681 UIs .destroyAll (Object .class ); // destroy every UI: obviously every UI extends Object
0 commit comments