Skip to content

Commit 803aba4

Browse files
authored
Merge pull request #12254 from TeamNewPipe/timestamp-keep-current-player
2 parents 8afb00d + 1723bf0 commit 803aba4

File tree

14 files changed

+290
-204
lines changed

14 files changed

+290
-204
lines changed

app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
import org.schabi.newpipe.local.history.HistoryRecordManager;
9494
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
9595
import org.schabi.newpipe.player.Player;
96+
import org.schabi.newpipe.player.PlayerIntentType;
9697
import org.schabi.newpipe.player.PlayerService;
9798
import org.schabi.newpipe.player.PlayerType;
9899
import org.schabi.newpipe.player.event.OnKeyDownListener;
@@ -1166,8 +1167,12 @@ private void openMainPlayer() {
11661167
final PlayQueue queue = setupPlayQueueForIntent(false);
11671168
tryAddVideoPlayerView();
11681169

1169-
final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(),
1170-
PlayerService.class, queue, true, autoPlayEnabled);
1170+
final Context context = requireContext();
1171+
final Intent playerIntent =
1172+
NavigationHelper.getPlayerIntent(context, PlayerService.class, queue,
1173+
PlayerIntentType.AllOthers)
1174+
.putExtra(Player.PLAY_WHEN_READY, autoPlayEnabled)
1175+
.putExtra(Player.RESUME_PLAYBACK, true);
11711176
ContextCompat.startForegroundService(activity, playerIntent);
11721177
}
11731178

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

Lines changed: 161 additions & 56 deletions
Large diffs are not rendered by default.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.schabi.newpipe.player
2+
3+
import android.os.Parcelable
4+
import kotlinx.parcelize.Parcelize
5+
6+
// We model this as an enum class plus one struct for each enum value
7+
// so we can consume it from Java properly. After converting to Kotlin,
8+
// we could switch to a sealed enum class & a proper Kotlin `when` match.
9+
10+
@Parcelize
11+
enum class PlayerIntentType : Parcelable {
12+
Enqueue,
13+
EnqueueNext,
14+
TimestampChange,
15+
AllOthers
16+
}
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/player/PlayerService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,9 @@ public int onStartCommand(final Intent intent, final int flags, final int startI
169169
}
170170

171171
if (player != null) {
172+
final PlayerType oldPlayerType = player.getPlayerType();
172173
player.handleIntent(intent);
174+
player.handleIntentPost(oldPlayerType);
173175
player.UIs().get(MediaSessionPlayerUi.class)
174176
.ifPresent(ui -> ui.handleMediaButtonIntent(intent));
175177
}

app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package org.schabi.newpipe.player.helper;
22

3-
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
4-
import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF;
5-
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
63
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_ALWAYS;
74
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_NEVER;
85
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_WIFI;
@@ -25,7 +22,6 @@
2522
import androidx.preference.PreferenceManager;
2623

2724
import com.google.android.exoplayer2.PlaybackParameters;
28-
import com.google.android.exoplayer2.Player.RepeatMode;
2925
import com.google.android.exoplayer2.SeekParameters;
3026
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
3127
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
@@ -410,23 +406,9 @@ private static SinglePlayQueue getAutoQueuedSinglePlayQueue(
410406
return singlePlayQueue;
411407
}
412408

413-
414409
// endregion
415410
// region Utils used by player
416411

417-
@RepeatMode
418-
public static int nextRepeatMode(@RepeatMode final int repeatMode) {
419-
switch (repeatMode) {
420-
case REPEAT_MODE_OFF:
421-
return REPEAT_MODE_ONE;
422-
case REPEAT_MODE_ONE:
423-
return REPEAT_MODE_ALL;
424-
case REPEAT_MODE_ALL:
425-
default:
426-
return REPEAT_MODE_OFF;
427-
}
428-
}
429-
430412
@ResizeMode
431413
public static int retrieveResizeModeFromPrefs(final Player player) {
432414
return player.getPrefs().getInt(player.getContext().getString(R.string.last_resize_mode),

app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.schabi.newpipe.MainActivity;
2424
import org.schabi.newpipe.R;
2525
import org.schabi.newpipe.player.Player;
26+
import org.schabi.newpipe.player.PlayerIntentType;
2627
import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi;
2728
import org.schabi.newpipe.util.NavigationHelper;
2829

@@ -254,7 +255,9 @@ private Intent getIntentForNotification() {
254255
} else {
255256
// We are playing in fragment. Don't open another activity just show fragment. That's it
256257
final Intent intent = NavigationHelper.getPlayerIntent(
257-
player.getContext(), MainActivity.class, null, true);
258+
player.getContext(), MainActivity.class, null,
259+
PlayerIntentType.AllOthers);
260+
intent.putExtra(Player.RESUME_PLAYBACK, true);
258261
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
259262
intent.setAction(Intent.ACTION_MAIN);
260263
intent.addCategory(Intent.CATEGORY_LAUNCHER);

app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,22 @@ public synchronized void append(@NonNull final List<PlayQueueItem> items) {
291291
broadcast(new AppendEvent(itemList.size()));
292292
}
293293

294+
/**
295+
* Add the given item after the current stream.
296+
*
297+
* @param item item to add.
298+
* @param skipIfSame if set, skip adding if the next stream is the same stream.
299+
*/
300+
public void enqueueNext(@NonNull final PlayQueueItem item, final boolean skipIfSame) {
301+
final int currentIndex = getIndex();
302+
// if the next item is the same item as the one we want to enqueue, skip if flag is true
303+
if (skipIfSame && item.isSameItem(getItem(currentIndex + 1))) {
304+
return;
305+
}
306+
append(List.of(item));
307+
move(size() - 1, currentIndex + 1);
308+
}
309+
294310
/**
295311
* Removes the item at the given index from the play queue.
296312
* <p>
@@ -529,8 +545,7 @@ public boolean equalStreams(@Nullable final PlayQueue other) {
529545
final PlayQueueItem stream = streams.get(i);
530546
final PlayQueueItem otherStream = other.streams.get(i);
531547
// Check is based on serviceId and URL
532-
if (stream.getServiceId() != otherStream.getServiceId()
533-
|| !stream.getUrl().equals(otherStream.getUrl())) {
548+
if (!stream.isSameItem(otherStream)) {
534549
return false;
535550
}
536551
}

app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItem.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public class PlayQueueItem implements Serializable {
3838
private long recoveryPosition;
3939
private Throwable error;
4040

41-
PlayQueueItem(@NonNull final StreamInfo info) {
41+
public PlayQueueItem(@NonNull final StreamInfo info) {
4242
this(info.getName(), info.getUrl(), info.getServiceId(), info.getDuration(),
4343
info.getThumbnails(), info.getUploaderName(),
4444
info.getUploaderUrl(), info.getStreamType());
@@ -71,6 +71,22 @@ private PlayQueueItem(@Nullable final String name, @Nullable final String url,
7171
this.recoveryPosition = RECOVERY_UNSET;
7272
}
7373

74+
/** Whether these two items should be treated as the same stream
75+
* for the sake of keeping the same player running when e.g. jumping between timestamps.
76+
*
77+
* @param other the {@link PlayQueueItem} to compare against.
78+
* @return whether the two items are the same so the stream can be re-used.
79+
*/
80+
public boolean isSameItem(@Nullable final PlayQueueItem other) {
81+
if (other == null) {
82+
return false;
83+
}
84+
// We assume that the same service & URL uniquely determines
85+
// that we can keep the same stream running.
86+
return serviceId == other.serviceId
87+
&& url.equals(other.url);
88+
}
89+
7490
@NonNull
7591
public String getTitle() {
7692
return title;

app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ public SinglePlayQueue(final StreamInfoItem item) {
1616
public SinglePlayQueue(final StreamInfo info) {
1717
super(0, List.of(new PlayQueueItem(info)));
1818
}
19-
19+
public SinglePlayQueue(final PlayQueueItem item) {
20+
super(0, List.of(item));
21+
}
2022
public SinglePlayQueue(final StreamInfo info, final long startPosition) {
2123
super(0, List.of(new PlayQueueItem(info)));
2224
getItem().setRecoveryPosition(startPosition);

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

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import android.content.Intent;
1010
import android.net.Uri;
1111
import android.os.Build;
12+
import android.os.Parcelable;
1213
import android.util.Log;
1314
import android.widget.Toast;
1415

@@ -57,8 +58,10 @@
5758
import org.schabi.newpipe.local.subscription.SubscriptionsImportFragment;
5859
import org.schabi.newpipe.player.PlayQueueActivity;
5960
import org.schabi.newpipe.player.Player;
61+
import org.schabi.newpipe.player.PlayerIntentType;
6062
import org.schabi.newpipe.player.PlayerService;
6163
import org.schabi.newpipe.player.PlayerType;
64+
import org.schabi.newpipe.player.TimestampChangeData;
6265
import org.schabi.newpipe.player.helper.PlayerHelper;
6366
import org.schabi.newpipe.player.helper.PlayerHolder;
6467
import org.schabi.newpipe.player.playqueue.PlayQueue;
@@ -85,7 +88,7 @@ private NavigationHelper() {
8588
public static <T> Intent getPlayerIntent(@NonNull final Context context,
8689
@NonNull final Class<T> targetClazz,
8790
@Nullable final PlayQueue playQueue,
88-
final boolean resumePlayback) {
91+
@NonNull final PlayerIntentType playerIntentType) {
8992
final Intent intent = new Intent(context, targetClazz);
9093

9194
if (playQueue != null) {
@@ -95,44 +98,31 @@ public static <T> Intent getPlayerIntent(@NonNull final Context context,
9598
}
9699
}
97100
intent.putExtra(Player.PLAYER_TYPE, PlayerType.MAIN.valueForIntent());
98-
intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback);
99101
intent.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true);
102+
intent.putExtra(Player.PLAYER_INTENT_TYPE, (Parcelable) playerIntentType);
100103

101104
return intent;
102105
}
103106

104107
@NonNull
105-
public static <T> Intent getPlayerIntent(@NonNull final Context context,
106-
@NonNull final Class<T> targetClazz,
107-
@Nullable final PlayQueue playQueue,
108-
final boolean resumePlayback,
109-
final boolean playWhenReady) {
110-
return getPlayerIntent(context, targetClazz, playQueue, resumePlayback)
111-
.putExtra(Player.PLAY_WHEN_READY, playWhenReady);
112-
}
108+
public static Intent getPlayerTimestampIntent(@NonNull final Context context,
109+
@NonNull final TimestampChangeData
110+
timestampChangeData) {
111+
final Intent intent = new Intent(context, PlayerService.class);
113112

114-
@NonNull
115-
public static <T> Intent getPlayerEnqueueIntent(@NonNull final Context context,
116-
@NonNull final Class<T> targetClazz,
117-
@Nullable final PlayQueue playQueue) {
118-
// when enqueueing `resumePlayback` is always `false` since:
119-
// - if there is a video already playing, the value of `resumePlayback` just doesn't make
120-
// any difference.
121-
// - if there is nothing already playing, it is useful for the enqueue action to have a
122-
// slightly different behaviour than the normal play action: the latter resumes playback,
123-
// the former doesn't. (note that enqueue can be triggered when nothing is playing only
124-
// by long pressing the video detail fragment, playlist or channel controls
125-
return getPlayerIntent(context, targetClazz, playQueue, false)
126-
.putExtra(Player.ENQUEUE, true);
113+
intent.putExtra(Player.PLAYER_INTENT_TYPE, (Parcelable) PlayerIntentType.TimestampChange);
114+
intent.putExtra(Player.PLAYER_INTENT_DATA, timestampChangeData);
115+
116+
return intent;
127117
}
128118

129119
@NonNull
130120
public static <T> Intent getPlayerEnqueueNextIntent(@NonNull final Context context,
131121
@NonNull final Class<T> targetClazz,
132122
@Nullable final PlayQueue playQueue) {
133-
// see comment in `getPlayerEnqueueIntent` as to why `resumePlayback` is false
134-
return getPlayerIntent(context, targetClazz, playQueue, false)
135-
.putExtra(Player.ENQUEUE_NEXT, true);
123+
return getPlayerIntent(context, targetClazz, playQueue, PlayerIntentType.EnqueueNext)
124+
// see comment in `getPlayerEnqueueIntent` as to why `resumePlayback` is false
125+
.putExtra(Player.RESUME_PLAYBACK, false);
136126
}
137127

138128
/* PLAY */
@@ -166,8 +156,10 @@ public static void playOnPopupPlayer(final Context context,
166156

167157
Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
168158

169-
final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback);
170-
intent.putExtra(Player.PLAYER_TYPE, PlayerType.POPUP.valueForIntent());
159+
final Intent intent = getPlayerIntent(context, PlayerService.class, queue,
160+
PlayerIntentType.AllOthers);
161+
intent.putExtra(Player.PLAYER_TYPE, PlayerType.POPUP.valueForIntent())
162+
.putExtra(Player.RESUME_PLAYBACK, resumePlayback);
171163
ContextCompat.startForegroundService(context, intent);
172164
}
173165

@@ -177,8 +169,10 @@ public static void playOnBackgroundPlayer(final Context context,
177169
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT)
178170
.show();
179171

180-
final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback);
172+
final Intent intent = getPlayerIntent(context, PlayerService.class, queue,
173+
PlayerIntentType.AllOthers);
181174
intent.putExtra(Player.PLAYER_TYPE, PlayerType.AUDIO.valueForIntent());
175+
intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback);
182176
ContextCompat.startForegroundService(context, intent);
183177
}
184178

@@ -191,7 +185,17 @@ public static void enqueueOnPlayer(final Context context,
191185
}
192186

193187
Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show();
194-
final Intent intent = getPlayerEnqueueIntent(context, PlayerService.class, queue);
188+
189+
// when enqueueing `resumePlayback` is always `false` since:
190+
// - if there is a video already playing, the value of `resumePlayback` just doesn't make
191+
// any difference.
192+
// - if there is nothing already playing, it is useful for the enqueue action to have a
193+
// slightly different behaviour than the normal play action: the latter resumes playback,
194+
// the former doesn't. (note that enqueue can be triggered when nothing is playing only
195+
// by long pressing the video detail fragment, playlist or channel controls
196+
final Intent intent = getPlayerIntent(context, PlayerService.class, queue,
197+
PlayerIntentType.Enqueue)
198+
.putExtra(Player.RESUME_PLAYBACK, false);
195199

196200
intent.putExtra(Player.PLAYER_TYPE, playerType.valueForIntent());
197201
ContextCompat.startForegroundService(context, intent);

0 commit comments

Comments
 (0)