Compare commits

..
Author SHA1 Message Date
anthorob 363e02f409 Delay autoplay triggering until the current track nears its end. 2026-10-31 17:09:46 -04:00
2 changed files with 31 additions and 13 deletions

No files matched your search

@@ -37,6 +37,7 @@ public class TrackScheduler {
private final StuckTrackRecoveryHandler stuckTrackRecoveryHandler;
private static final long POSITION_THROTTLE_MS = 5000;
private volatile long lastPositionSent = Long.MIN_VALUE;
private static final long AUTOPLAY_REMAINING_MS = 30_000;
private final AutoplayService autoplayService;
private volatile boolean autoplayEnabled;
private final AtomicBoolean autoplayLoading = new AtomicBoolean();
@@ -91,14 +92,11 @@ public class TrackScheduler {
}
/**
* Enables or disables autoplay. Enabling it while the last track of the queue is already
* playing loads its recommendations immediately.
* Enables or disables autoplay. Recommendations are only loaded once the last track of the
* queue is close to its end, so users still get a chance to queue their own tracks.
*/
public void setAutoplayEnabled(boolean enabled) {
this.autoplayEnabled = enabled;
if (enabled) {
maybeAutoplay();
}
}
boolean tryBeginAutoplayLoad() {
@@ -109,9 +107,13 @@ public class TrackScheduler {
autoplayLoading.set(false);
}
private void maybeAutoplay() {
private void maybeAutoplay(long position) {
Track track = currentTrack;
if (autoplayEnabled && track != null && queueManager.getQueueSize() <= 1) {
if (!autoplayEnabled || track == null || queueManager.getQueueSize() > 1) {
return;
}
long length = track.getInfo().getLength();
if (length > 0 && length - position <= AUTOPLAY_REMAINING_MS) {
autoplayService.loadFor(this, track);
}
}
@@ -146,7 +148,6 @@ public class TrackScheduler {
currentTrack = track;
stuckTrackRecoveryHandler.reset();
publish(MusicEvent.Type.TRACK_START, track, null);
maybeAutoplay();
}
public void onTrackEnd(Track track, TrackEndEvent.AudioTrackEndReason endReason) {
@@ -236,6 +237,7 @@ public class TrackScheduler {
}
void onPlayerUpdate(long timestamp, long position, boolean connected, long ping, long now) {
maybeAutoplay(position);
if (manager.getEvents() == null || currentTrack == null) {
return;
}
@@ -147,13 +147,29 @@ public class TrackSchedulerTest {
}
@Test
public void onTrackStartLoadsAutoplayWhenEnabledAndLastTrack() {
public void onTrackStartDoesNotLoadAutoplayImmediately() {
AutoplayService service = mock(AutoplayService.class);
TrackScheduler autoplay = new TrackScheduler(musicManager, service);
Track track = QueueManagerTest.mockTrack("t1");
when(track.getInfo().getLength()).thenReturn(200_000L);
autoplay.setAutoplayEnabled(true);
autoplay.onTrackStart(track);
autoplay.onPlayerUpdate(1L, 1_000L, true, 20L, 1000L);
verify(service, never()).loadFor(any(), any());
}
@Test
public void autoplayLoadsWhenLastTrackNearsItsEnd() {
AutoplayService service = mock(AutoplayService.class);
TrackScheduler autoplay = new TrackScheduler(musicManager, service);
Track track = QueueManagerTest.mockTrack("t1");
when(track.getInfo().getLength()).thenReturn(200_000L);
autoplay.setAutoplayEnabled(true);
autoplay.onTrackStart(track);
autoplay.onPlayerUpdate(1L, 175_000L, true, 20L, 1000L);
verify(service).loadFor(autoplay, track);
}
@@ -180,20 +196,20 @@ public class TrackSchedulerTest {
clearInvocations(service);
autoplay.onTrackStart(t1);
autoplay.onPlayerUpdate(1L, 999_999L, true, 20L, 1000L);
verify(service, never()).loadFor(any(), any());
}
@Test
public void enablingAutoplayWhileLastTrackPlayingTriggersLoad() {
public void enablingAutoplayDoesNotLoadImmediately() {
AutoplayService service = mock(AutoplayService.class);
TrackScheduler autoplay = new TrackScheduler(musicManager, service);
Track track = QueueManagerTest.mockTrack("t1");
autoplay.onTrackStart(track);
autoplay.onTrackStart(QueueManagerTest.mockTrack("t1"));
autoplay.setAutoplayEnabled(true);
verify(service).loadFor(autoplay, track);
verify(service, never()).loadFor(any(), any());
}
@Test