1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-06-26 07:02:55 +00:00

Player/handleIntent: Don’t delete queue when clicking on timestamp

Fixes https://github.com/TeamNewPipe/NewPipe/issues/11013

We finally are at the point where we can have good logic around
clicking on timestamps.

This is pretty straightforward:

1) if we are already playing the stream (usual case), we skip to the
   correct second directly
2) If we don’t have a queue yet, create a trivial one with the stream
3) If we have a queue, we insert the video as next item and start
  playing it.

The skipping logic in 1) is similar to the one further down in the old
optimization block, but will always correctly fire for timestamps now.
I copied it because it’s not quite the same code, and moving into a
separate method at this stage would complicate the code too much.
This commit is contained in:
Profpatsch 2025-05-05 17:59:34 +02:00
parent 44e840c1a5
commit a9153c6dd9
4 changed files with 79 additions and 14 deletions

View File

@ -90,8 +90,8 @@ import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorPanelHelper;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream;
@ -125,9 +125,9 @@ import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.SerializedCache;
import org.schabi.newpipe.util.StreamTypeUtil;
import org.schabi.newpipe.util.image.PicassoHelper;
import java.util.List;
import java.util.Optional;
@ -407,9 +407,8 @@ public final class Player implements PlaybackListener, Listener {
if (newQueue == null) {
return;
}
final int currentIndex = playQueue.getIndex();
playQueue.append(newQueue.getStreams());
playQueue.move(playQueue.size() - 1, currentIndex + 1);
final PlayQueueItem newItem = newQueue.getStreams().get(0);
newQueue.enqueueNext(newItem, false);
}
return;
}
@ -421,11 +420,43 @@ public final class Player implements PlaybackListener, Listener {
streamItemDisposable.add(single.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(info -> {
final PlayQueue newPlayQueue =
new SinglePlayQueue(info, dat.getSeconds() * 1000L);
// TODO: add back the already playing stream optimization here
initPlayback(newPlayQueue, playWhenReady);
final @Nullable PlayQueue oldPlayQueue = playQueue;
info.setStartPosition(dat.getSeconds());
final PlayQueueItem playQueueItem = new PlayQueueItem(info);
// If the stream is already playing,
// we can just seek to the appropriate timestamp
if (oldPlayQueue != null
&& playQueueItem.isSameItem(oldPlayQueue.getItem())) {
// Player can have state = IDLE when playback is stopped or failed
// and we should retry in this case
if (simpleExoPlayer.getPlaybackState()
== com.google.android.exoplayer2.Player.STATE_IDLE) {
simpleExoPlayer.prepare();
}
simpleExoPlayer.seekTo(oldPlayQueue.getIndex(),
dat.getSeconds() * 1000L);
simpleExoPlayer.setPlayWhenReady(playWhenReady);
} else {
final PlayQueue newPlayQueue;
// If there is no queue yet, just add our item
if (oldPlayQueue == null) {
newPlayQueue = new SinglePlayQueue(playQueueItem);
// else we add the timestamped stream behind the current video
// and start playing it.
} else {
oldPlayQueue.enqueueNext(playQueueItem, true);
oldPlayQueue.offsetIndex(1);
newPlayQueue = oldPlayQueue;
}
initPlayback(newPlayQueue, playWhenReady);
}
handleIntentPost(oldPlayerType);
}, throwable -> {
if (DEBUG) {
Log.e(TAG, "Could not play on popup: " + dat.getUrl(), throwable);
@ -465,7 +496,7 @@ public final class Player implements PlaybackListener, Listener {
if (!exoPlayerIsNull()
&& newQueue.size() == 1 && newQueue.getItem() != null
&& playQueue != null && playQueue.size() == 1 && playQueue.getItem() != null
&& newQueue.getItem().getUrl().equals(playQueue.getItem().getUrl())
&& newQueue.getItem().isSameItem(playQueue.getItem())
&& newQueue.getItem().getRecoveryPosition() != PlayQueueItem.RECOVERY_UNSET) {
// Player can have state = IDLE when playback is stopped or failed
// and we should retry in this case
@ -531,6 +562,7 @@ public final class Player implements PlaybackListener, Listener {
handleIntentPost(oldPlayerType);
}
private void handleIntentPost(final PlayerType oldPlayerType) {
if (oldPlayerType != playerType && playQueue != null) {
// If playerType changes from one to another we should reload the player

View File

@ -291,6 +291,22 @@ public abstract class PlayQueue implements Serializable {
broadcast(new 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.
*/
public void enqueueNext(@NonNull final PlayQueueItem item, final boolean skipIfSame) {
final int currentIndex = getIndex();
// if the next item is the same item as the one we want to enqueue, skip if flag is true
if (skipIfSame && item.isSameItem(getItem(currentIndex + 1))) {
return;
}
append(List.of(item));
move(size() - 1, currentIndex + 1);
}
/**
* Removes the item at the given index from the play queue.
* <p>
@ -529,8 +545,7 @@ public abstract class PlayQueue implements Serializable {
final PlayQueueItem stream = streams.get(i);
final PlayQueueItem otherStream = other.streams.get(i);
// Check is based on serviceId and URL
if (stream.getServiceId() != otherStream.getServiceId()
|| !stream.getUrl().equals(otherStream.getUrl())) {
if (!stream.isSameItem(otherStream)) {
return false;
}
}

View File

@ -38,7 +38,7 @@ public class PlayQueueItem implements Serializable {
private long recoveryPosition;
private Throwable error;
PlayQueueItem(@NonNull final StreamInfo info) {
public PlayQueueItem(@NonNull final StreamInfo info) {
this(info.getName(), info.getUrl(), info.getServiceId(), info.getDuration(),
info.getThumbnails(), info.getUploaderName(),
info.getUploaderUrl(), info.getStreamType());
@ -71,6 +71,22 @@ public class PlayQueueItem implements Serializable {
this.recoveryPosition = RECOVERY_UNSET;
}
/** Whether these two items should be treated as the same stream
* for the sake of keeping the same player running when e.g. jumping between timestamps.
*
* @param other the {@link PlayQueueItem} to compare against.
* @return whether the two items are the same so the stream can be re-used.
*/
public boolean isSameItem(@Nullable final PlayQueueItem other) {
if (other == null) {
return false;
}
// We assume that the same service & URL uniquely determines
// that we can keep the same stream running.
return getServiceId() == other.getServiceId()
&& getUrl().equals(other.getUrl());
}
@NonNull
public String getTitle() {
return title;

View File

@ -16,7 +16,9 @@ public final class SinglePlayQueue extends PlayQueue {
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);