fix(qconnect): anchor play next to local current track

cx
This commit is contained in:
vicrodh committed 2026-03-12 20:17:02 -06:00
1 parent 1d28730456
commit a46c5a016f
3 files changed
+61 -2

No files matched your search

@@ -38,6 +38,27 @@ describe('resolveQconnectPlayNextInsertAfter', () => {
});
});
it('prefers the authoritative current track when renderer current is stale but still present in queue', () => {
const queueSnapshot = buildQueueSnapshot([123452387, 123452388, 123452389], [123452387, 1, 2]);
const rendererSnapshot: QconnectRendererSnapshot = {
current_track: { track_id: 123452387, queue_item_id: 123452387 },
next_track: { track_id: 123452388, queue_item_id: 1 }
};
expect(
resolveQconnectPlayNextInsertAfter(queueSnapshot, rendererSnapshot, {
authoritativeCurrentTrackId: 123452388
})
).toEqual({
insertAfter: 1,
strategy: 'authoritative_track_id_match',
queueIndex: 1,
nextQueueIndex: 1,
matchedTrackId: 123452388,
matchedQueueItemId: 1
});
});
it('falls back to queue track matching when renderer queue_item_id is stale', () => {
const queueSnapshot = buildQueueSnapshot([201, 202, 203], [7001, 7002, 7003]);
const rendererSnapshot: QconnectRendererSnapshot = {
+35 -1
View File
@@ -64,6 +64,10 @@ export type QconnectPlayNextAnchorResolution = {
matchedQueueItemId: number | null;
};
type QconnectPlayNextResolutionOptions = {
authoritativeCurrentTrackId?: number | null;
};
function isPositiveNumber(value: unknown): value is number {
return typeof value === 'number' && Number.isFinite(value) && value > 0;
}
@@ -102,11 +106,13 @@ function findTrackIndex(
export function resolveQconnectPlayNextInsertAfter(
queueSnapshot: QconnectQueueSnapshot | null | undefined,
rendererSnapshot: QconnectRendererSnapshot | null | undefined
rendererSnapshot: QconnectRendererSnapshot | null | undefined,
options: QconnectPlayNextResolutionOptions = {}
): QconnectPlayNextAnchorResolution {
const queueItems = queueSnapshot?.queue_items ?? [];
const currentTrack = rendererSnapshot?.current_track ?? null;
const nextTrack = rendererSnapshot?.next_track ?? null;
const authoritativeCurrentTrackId = options.authoritativeCurrentTrackId;
if (queueItems.length === 0) {
return {
@@ -121,6 +127,34 @@ export function resolveQconnectPlayNextInsertAfter(
const currentQueueIndex = findQueueIndexByQueueItemId(queueItems, currentTrack?.queue_item_id);
const nextQueueIndex = findQueueIndexByQueueItemId(queueItems, nextTrack?.queue_item_id);
const authoritativeTrackIndexBeforeNext = nextQueueIndex !== null
? findTrackIndexBefore(queueItems, authoritativeCurrentTrackId, nextQueueIndex)
: null;
const authoritativeCurrentTrackIndex = findTrackIndex(queueItems, authoritativeCurrentTrackId);
const authoritativeTrackDisagreesWithRenderer = isPositiveNumber(authoritativeCurrentTrackId)
&& authoritativeCurrentTrackId !== currentTrack?.track_id;
if (authoritativeTrackDisagreesWithRenderer && authoritativeTrackIndexBeforeNext !== null) {
return {
insertAfter: queueItems[authoritativeTrackIndexBeforeNext].queue_item_id,
strategy: 'authoritative_track_id_before_renderer_next',
queueIndex: authoritativeTrackIndexBeforeNext,
nextQueueIndex,
matchedTrackId: queueItems[authoritativeTrackIndexBeforeNext].track_id,
matchedQueueItemId: queueItems[authoritativeTrackIndexBeforeNext].queue_item_id
};
}
if (authoritativeTrackDisagreesWithRenderer && authoritativeCurrentTrackIndex !== null) {
return {
insertAfter: queueItems[authoritativeCurrentTrackIndex].queue_item_id,
strategy: 'authoritative_track_id_match',
queueIndex: authoritativeCurrentTrackIndex,
nextQueueIndex,
matchedTrackId: queueItems[authoritativeCurrentTrackIndex].track_id,
matchedQueueItemId: queueItems[authoritativeCurrentTrackIndex].queue_item_id
};
}
if (currentQueueIndex !== null) {
return {
+5 -1
View File
@@ -14,6 +14,7 @@ import {
addToQueue,
type BackendQueueTrack
} from '$lib/stores/queueStore';
import { getPlayerState } from '$lib/stores/playerStore';
import { addTrackToFavorites } from '$lib/services/playbackService';
import { openPlaylistModal } from '$lib/stores/uiStore';
import { showToast as storeShowToast, type ToastType } from '$lib/stores/toastStore';
@@ -123,7 +124,10 @@ async function resolveQconnectPlayNextInsertAfterFromSnapshots(): Promise<Qconne
const queueSnapshot = queueSnapshotResult.status === 'fulfilled' ? queueSnapshotResult.value : null;
const rendererSnapshot = rendererSnapshotResult.status === 'fulfilled' ? rendererSnapshotResult.value : null;
const resolution = resolveQconnectPlayNextInsertAfter(queueSnapshot, rendererSnapshot);
const localCurrentTrackId = getPlayerState().currentTrack?.id ?? null;
const resolution = resolveQconnectPlayNextInsertAfter(queueSnapshot, rendererSnapshot, {
authoritativeCurrentTrackId: localCurrentTrackId
});
const snapshotError = [
queueSnapshotResult.status === 'rejected'