Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
35c7f2f
Player: Remove unused IS_MUTED intent key
Profpatsch May 4, 2025
32eb3af
Player/handleIntent: a few comments
Profpatsch May 4, 2025
90e1ac5
NavigationHelper: inline getPlayerEnqueueIntent
Profpatsch May 4, 2025
b592403
NavigationHelper: push out resumePlayback one layer
Profpatsch May 4, 2025
e14ec3a
NavigationHelper: inline trivial getPlayerIntent use
Profpatsch May 4, 2025
fd24c08
Player/handleIntent: de morgan samePlayQueue
Profpatsch May 4, 2025
ab7d137
Player/handleIntent: always early return on ENQUEUE an ENQUEUE_NEXT
Profpatsch May 4, 2025
5750ef6
Player/handleIntent: start converting intent data to enum
Profpatsch May 4, 2025
8fb3e90
Player: remove unused REPEAT_MODE intent key
Profpatsch May 5, 2025
d534946
Player: inline repeat mode cycling
Profpatsch May 5, 2025
25a4a9a
Player/handleIntent: move prefs parameters into initPlayback
Profpatsch May 5, 2025
3803d49
Player/handleIntent: separate out the timestamp request into enum
Profpatsch May 5, 2025
150649a
Player/handleIntent: Don’t delete queue when clicking on timestamp
Profpatsch May 5, 2025
01f9a3d
Fix Checkstyle & remove unused fields
Profpatsch May 6, 2025
d77771a
Player/handleIntent: fix enqueue if player not running
Profpatsch Sep 5, 2025
eb277fe
Player/handleIntent: call handleIntentPost unconditionally
Profpatsch Sep 6, 2025
1723bf0
Player/handleIntent: keep current player when clicking timestamp
Profpatsch May 5, 2025
803aba4
Merge pull request #12254 from TeamNewPipe/timestamp-keep-current-player
Stypox Sep 6, 2025
89c4eb5
Refactor player intent logic
Isira-Seneviratne Sep 8, 2025
45c22c0
Merge pull request #12615 from Isira-Seneviratne/Player-intent-refactor
Profpatsch Sep 9, 2025
c98f56b
Merge branch 'dev' into Merge-dev-to-refactor
Isira-Seneviratne Sep 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ import org.schabi.newpipe.local.dialog.PlaylistDialog
import org.schabi.newpipe.local.history.HistoryRecordManager
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment
import org.schabi.newpipe.player.Player
import org.schabi.newpipe.player.PlayerIntentType
import org.schabi.newpipe.player.PlayerService
import org.schabi.newpipe.player.PlayerType
import org.schabi.newpipe.player.event.OnKeyDownListener
Expand Down Expand Up @@ -1044,8 +1045,10 @@ class VideoDetailFragment :
tryAddVideoPlayerView()

val playerIntent = NavigationHelper.getPlayerIntent(
requireContext(), PlayerService::class.java, queue, true, autoPlayEnabled
requireContext(), PlayerService::class.java, queue, PlayerIntentType.AllOthers
)
.putExtra(Player.PLAY_WHEN_READY, autoPlayEnabled)
.putExtra(Player.RESUME_PLAYBACK, true)
ContextCompat.startForegroundService(activity, playerIntent)
}

Expand Down
212 changes: 155 additions & 57 deletions app/src/main/java/org/schabi/newpipe/player/Player.java

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions app/src/main/java/org/schabi/newpipe/player/PlayerIntentType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.schabi.newpipe.player

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

// We model this as an enum class plus one struct for each enum value
// so we can consume it from Java properly. After converting to Kotlin,
// we could switch to a sealed enum class & a proper Kotlin `when` match.
enum class PlayerIntentType {
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
2 changes: 2 additions & 0 deletions app/src/main/java/org/schabi/newpipe/player/PlayerService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ class PlayerService : MediaBrowserServiceCompat() {
}

if (p != null) {
val oldPlayerType = p.playerType
p.handleIntent(intent)
p.handleIntentPost(oldPlayerType)
p.UIs().get(MediaSessionPlayerUi::class)
?.handleMediaButtonIntent(intent)
}
Expand Down
25 changes: 0 additions & 25 deletions app/src/main/java/org/schabi/newpipe/player/PlayerType.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,7 @@
package org.schabi.newpipe.player;

import static org.schabi.newpipe.player.Player.PLAYER_TYPE;

import android.content.Intent;

public enum PlayerType {
MAIN,
AUDIO,
POPUP;

/**
* @return an integer representing this {@link PlayerType}, to be used to save it in intents
* @see #retrieveFromIntent(Intent) Use retrieveFromIntent() to retrieve and convert player type
* integers from an intent
*/
public int valueForIntent() {
return ordinal();
}

/**
* @param intent the intent to retrieve a player type from
* @return the player type integer retrieved from the intent, converted back into a {@link
* PlayerType}, or {@link PlayerType#MAIN} if there is no player type extra in the
* intent
* @throws ArrayIndexOutOfBoundsException if the intent contains an invalid player type integer
* @see #valueForIntent() Use valueForIntent() to obtain valid player type integers
*/
public static PlayerType retrieveFromIntent(final Intent intent) {
return values()[intent.getIntExtra(PLAYER_TYPE, MAIN.valueForIntent())];
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package org.schabi.newpipe.player.helper;

import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF;
import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_ALWAYS;
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_NEVER;
import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_WIFI;
Expand All @@ -25,7 +22,6 @@
import androidx.preference.PreferenceManager;

import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player.RepeatMode;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
Expand Down Expand Up @@ -410,23 +406,9 @@ private static SinglePlayQueue getAutoQueuedSinglePlayQueue(
return singlePlayQueue;
}


// endregion
// region Utils used by player

@RepeatMode
public static int nextRepeatMode(@RepeatMode final int repeatMode) {
switch (repeatMode) {
case REPEAT_MODE_OFF:
return REPEAT_MODE_ONE;
case REPEAT_MODE_ONE:
return REPEAT_MODE_ALL;
case REPEAT_MODE_ALL:
default:
return REPEAT_MODE_OFF;
}
}

@ResizeMode
public static int retrieveResizeModeFromPrefs(final Player player) {
return player.getPrefs().getInt(player.getContext().getString(R.string.last_resize_mode),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.PlayerIntentType;
import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi;
import org.schabi.newpipe.util.NavigationHelper;

Expand Down Expand Up @@ -254,7 +255,9 @@ private Intent getIntentForNotification() {
} else {
// We are playing in fragment. Don't open another activity just show fragment. That's it
final Intent intent = NavigationHelper.getPlayerIntent(
player.getContext(), MainActivity.class, null, true);
player.getContext(), MainActivity.class, null,
PlayerIntentType.AllOthers);
intent.putExtra(Player.RESUME_PLAYBACK, true);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
Expand Down
16 changes: 16 additions & 0 deletions app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.kt
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,22 @@ abstract class PlayQueue internal constructor(
broadcast(AppendEvent(itemList.size))
}

/**
* Add the given item after the current stream.
*
* @param item item to add.
* @param skipIfSame if set, skip adding if the next stream is the same stream.
*/
fun enqueueNext(item: PlayQueueItem, skipIfSame: Boolean) {
val currentIndex = index
// if the next item is the same item as the one we want to enqueue, skip if flag is true
if (skipIfSame && item == getItem(currentIndex + 1)) {
return
}
append(listOf(item))
move(size() - 1, currentIndex + 1)
}

/**
* Removes the item at the given index from the play queue.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ public SinglePlayQueue(final StreamInfoItem item) {
public SinglePlayQueue(final StreamInfo info) {
super(0, List.of(new PlayQueueItem(info)));
}

public SinglePlayQueue(final PlayQueueItem item) {
super(0, List.of(item));
}
public SinglePlayQueue(final StreamInfo info, final long startPosition) {
super(0, List.of(new PlayQueueItem(info)));
getItem().setRecoveryPosition(startPosition);
Expand Down
89 changes: 41 additions & 48 deletions app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@
import org.schabi.newpipe.local.subscription.SubscriptionsImportFragment;
import org.schabi.newpipe.player.PlayQueueActivity;
import org.schabi.newpipe.player.Player;
import org.schabi.newpipe.player.PlayerIntentType;
import org.schabi.newpipe.player.PlayerService;
import org.schabi.newpipe.player.PlayerType;
import org.schabi.newpipe.player.TimestampChangeData;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
Expand All @@ -69,6 +71,7 @@
import org.schabi.newpipe.util.external_communication.ShareUtils;

import java.util.List;
import java.util.Optional;

public final class NavigationHelper {
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
Expand All @@ -87,54 +90,32 @@ private NavigationHelper() {
public static <T> Intent getPlayerIntent(@NonNull final Context context,
@NonNull final Class<T> targetClazz,
@Nullable final PlayQueue playQueue,
final boolean resumePlayback) {
final Intent intent = new Intent(context, targetClazz);

if (playQueue != null) {
final String cacheKey = SerializedCache.getInstance().put(playQueue, PlayQueue.class);
if (cacheKey != null) {
intent.putExtra(Player.PLAY_QUEUE_KEY, cacheKey);
}
}
intent.putExtra(Player.PLAYER_TYPE, PlayerType.MAIN.valueForIntent());
intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback);
intent.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true);

return intent;
}

@NonNull
public static <T> Intent getPlayerIntent(@NonNull final Context context,
@NonNull final Class<T> targetClazz,
@Nullable final PlayQueue playQueue,
final boolean resumePlayback,
final boolean playWhenReady) {
return getPlayerIntent(context, targetClazz, playQueue, resumePlayback)
.putExtra(Player.PLAY_WHEN_READY, playWhenReady);
@NonNull final PlayerIntentType playerIntentType) {
final String cacheKey = Optional.ofNullable(playQueue)
.map(queue -> SerializedCache.getInstance().put(queue, PlayQueue.class))
.orElse(null);
return new Intent(context, targetClazz)
.putExtra(Player.PLAY_QUEUE_KEY, cacheKey)
.putExtra(Player.PLAYER_TYPE, PlayerType.MAIN)
.putExtra(PlayerService.SHOULD_START_FOREGROUND_EXTRA, true)
.putExtra(Player.PLAYER_INTENT_TYPE, playerIntentType);
}

@NonNull
public static <T> Intent getPlayerEnqueueIntent(@NonNull final Context context,
@NonNull final Class<T> targetClazz,
@Nullable final PlayQueue playQueue) {
// when enqueueing `resumePlayback` is always `false` since:
// - if there is a video already playing, the value of `resumePlayback` just doesn't make
// any difference.
// - if there is nothing already playing, it is useful for the enqueue action to have a
// slightly different behaviour than the normal play action: the latter resumes playback,
// the former doesn't. (note that enqueue can be triggered when nothing is playing only
// by long pressing the video detail fragment, playlist or channel controls
return getPlayerIntent(context, targetClazz, playQueue, false)
.putExtra(Player.ENQUEUE, true);
public static Intent getPlayerTimestampIntent(@NonNull final Context context,
@NonNull final TimestampChangeData data) {
return new Intent(context, PlayerService.class)
.putExtra(Player.PLAYER_INTENT_TYPE, PlayerIntentType.TimestampChange)
.putExtra(Player.PLAYER_INTENT_DATA, data);
}

@NonNull
public static <T> Intent getPlayerEnqueueNextIntent(@NonNull final Context context,
@NonNull final Class<T> targetClazz,
@Nullable final PlayQueue playQueue) {
// see comment in `getPlayerEnqueueIntent` as to why `resumePlayback` is false
return getPlayerIntent(context, targetClazz, playQueue, false)
.putExtra(Player.ENQUEUE_NEXT, true);
return getPlayerIntent(context, targetClazz, playQueue, PlayerIntentType.EnqueueNext)
// see comment in `getPlayerEnqueueIntent` as to why `resumePlayback` is false
.putExtra(Player.RESUME_PLAYBACK, false);
}

/* PLAY */
Expand Down Expand Up @@ -168,8 +149,10 @@ public static void playOnPopupPlayer(final Context context,

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

final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback);
intent.putExtra(Player.PLAYER_TYPE, PlayerType.POPUP.valueForIntent());
final var intent = getPlayerIntent(context, PlayerService.class, queue,
PlayerIntentType.AllOthers)
.putExtra(Player.PLAYER_TYPE, PlayerType.POPUP)
.putExtra(Player.RESUME_PLAYBACK, resumePlayback);
ContextCompat.startForegroundService(context, intent);
}

Expand All @@ -179,8 +162,10 @@ public static void playOnBackgroundPlayer(final Context context,
Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT)
.show();

final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback);
intent.putExtra(Player.PLAYER_TYPE, PlayerType.AUDIO.valueForIntent());
final Intent intent = getPlayerIntent(context, PlayerService.class, queue,
PlayerIntentType.AllOthers)
.putExtra(Player.PLAYER_TYPE, PlayerType.AUDIO)
.putExtra(Player.RESUME_PLAYBACK, resumePlayback);
ContextCompat.startForegroundService(context, intent);
}

Expand All @@ -193,9 +178,18 @@ public static void enqueueOnPlayer(final Context context,
}

Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show();
final Intent intent = getPlayerEnqueueIntent(context, PlayerService.class, queue);

intent.putExtra(Player.PLAYER_TYPE, playerType.valueForIntent());
// when enqueueing `resumePlayback` is always `false` since:
// - if there is a video already playing, the value of `resumePlayback` just doesn't make
// any difference.
// - if there is nothing already playing, it is useful for the enqueue action to have a
// slightly different behaviour than the normal play action: the latter resumes playback,
// the former doesn't. (note that enqueue can be triggered when nothing is playing only
// by long pressing the video detail fragment, playlist or channel controls
final Intent intent = getPlayerIntent(context, PlayerService.class, queue,
PlayerIntentType.Enqueue)
.putExtra(Player.RESUME_PLAYBACK, false)
.putExtra(Player.PLAYER_TYPE, playerType);
ContextCompat.startForegroundService(context, intent);
}

Expand All @@ -217,9 +211,8 @@ public static void enqueueNextOnPlayer(final Context context, final PlayQueue qu
playerType = PlayerType.AUDIO;
}
Toast.makeText(context, R.string.enqueued_next, Toast.LENGTH_SHORT).show();
final Intent intent = getPlayerEnqueueNextIntent(context, PlayerService.class, queue);

intent.putExtra(Player.PLAYER_TYPE, playerType.valueForIntent());
final Intent intent = getPlayerEnqueueNextIntent(context, PlayerService.class, queue)
.putExtra(Player.PLAYER_TYPE, playerType);
ContextCompat.startForegroundService(context, intent);
}

Expand Down
Loading