fix(playback): gate within-track resume on cache, drop slow seek path
Session resume of a Qobuz track no longer forces the streaming start-position path, which buffered the CMAF stream sequentially from byte 0 up to the saved offset (up to ~30s wait, with no buffering indication). v2_play_track is now always called without a start position. The track resumes at the saved offset only on a cache hit (format_id === null), where the audio is a full in-memory buffer and Seek lands like a local file. On a cache miss it starts from 0:00 instead of blocking on a sequential buffer fill. No backend or audio-engine change; the start_position_secs streaming path is now unused. cc
This commit is contained in:
2 files changed
+31
-17
No files matched your search
@@ -116,10 +116,10 @@ export async function cmdPlayTrack(
|
|||||||
// start_position_secs). duration_secs is required on the streaming
|
// start_position_secs). duration_secs is required on the streaming
|
||||||
// path — without it the backend stores duration=0 and
|
// path — without it the backend stores duration=0 and
|
||||||
// current_position() clamps to 0, freezing the seekbar (seen on
|
// current_position() clamps to 0, freezing the seekbar (seen on
|
||||||
// session-restore first play). start_position_secs > 0 turns the
|
// session-restore first play). start_position_secs is still accepted
|
||||||
// call into a session-resume play (#315): backend buffers enough
|
// by the backend but the frontend no longer uses it: session resume
|
||||||
// bytes to cover the offset and pre-skips decoder output before
|
// now seeks to the saved offset only on a cache hit, where the audio
|
||||||
// engaging audio, avoiding the play-then-seek race.
|
// is fully in memory and Seek lands (see playerStore togglePlay).
|
||||||
return invoke('v2_play_track', {
|
return invoke('v2_play_track', {
|
||||||
trackId,
|
trackId,
|
||||||
quality: quality ?? null,
|
quality: quality ?? null,
|
||||||
|
|||||||
@@ -559,25 +559,39 @@ export async function togglePlay(): Promise<void> {
|
|||||||
void flushPendingSeek();
|
void flushPendingSeek();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Qobuz track — use v2_play_track. Pass duration so the
|
// Qobuz track session restore (#315 within-track resume).
|
||||||
|
// Play from the start: v2_play_track serves a cache hit
|
||||||
|
// instantly or streams from byte 0. Pass duration so the
|
||||||
// streaming backend's current_position() doesn't clamp to 0
|
// streaming backend's current_position() doesn't clamp to 0
|
||||||
// (the value flows into thread_state.duration and seekbar
|
// (the value flows into thread_state.duration and seekbar
|
||||||
// progress is capped by .min(duration)). For session resume,
|
// progress is capped by .min(duration)).
|
||||||
// hand the restore offset directly to v2_play_track via
|
//
|
||||||
// startPositionSecs (#315 extension): the streaming path
|
// Resume the saved offset ONLY on a cache hit (format_id ===
|
||||||
// waits for enough buffer to cover the offset and pre-skips
|
// null): the audio is then a full in-memory buffer
|
||||||
// decoder output before engaging audio, instead of the
|
// (current_audio_data) so the Seek handler lands, exactly
|
||||||
// play-then-seek dance that silently dropped the seek
|
// like a local file. On a cache miss the track streams via
|
||||||
// because the audio thread's Seek handler bails when there
|
// CMAF — seeking mid-track would block on the buffer filling
|
||||||
// is no `current_audio_data` (streaming uses
|
// sequentially from byte 0, far too slow to feel like a
|
||||||
// `current_streaming_source` instead).
|
// feature, so we deliberately start fresh from 0:00.
|
||||||
await cmdPlayTrack(
|
const result = (await cmdPlayTrack(
|
||||||
currentTrack.id,
|
currentTrack.id,
|
||||||
getStreamingQuality(),
|
getStreamingQuality(),
|
||||||
currentTrack.duration ? Math.round(currentTrack.duration) : null,
|
currentTrack.duration ? Math.round(currentTrack.duration) : null,
|
||||||
null,
|
null,
|
||||||
restorePosition > 1 ? restorePosition : null,
|
null,
|
||||||
);
|
)) as { format_id?: number | null } | null | undefined;
|
||||||
|
const servedFromCache = result != null && result.format_id === null;
|
||||||
|
if (restorePosition > 1 && servedFromCache) {
|
||||||
|
pendingSeekPosition = restorePosition;
|
||||||
|
void flushPendingSeek();
|
||||||
|
} else if (restorePosition > 1) {
|
||||||
|
// Cache miss — undo the seekbar pre-anchor (setPendingSessionRestore
|
||||||
|
// pre-set currentTime to the saved offset) so the bar matches
|
||||||
|
// the actual start at 0:00 instead of flashing the saved time.
|
||||||
|
currentTime = 0;
|
||||||
|
anchorPosition(0);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in new issue
Block a user