perf(player): bump gapless lead time from 5s to 10s

Offline-cache v2 bundles need runway for the decrypt pipeline. At 10
MB/s (software AES fallback) a 58 MB track needs 6 seconds, which
blew past the old 5s pre-emptive window and missed the gapless handoff
entirely. With AES-NI active the decrypt is ~500ms, so 10s is
harmless overkill — the decrypted bytes just sit in L1 a few seconds
earlier.

Design note: this is complementary to the AES-NI target-feature fix,
not a substitute. A too-small lead time makes gapless brittle even on
fast hardware (any hiccup blows the window); a too-large one is
wasted memory but otherwise fine. 10s is comfortable for every
realistic hardware/track-size combo we'll see.

If we ever want user-visible control, plumb a `gapless_lead_secs`
through AudioSettings and read it at the hot site.

cc
This commit is contained in:
vicrodh committed 2026-04-17 13:12:08 -06:00
1 parent d5ef97b84f
commit 82781d3e55
1 file changed
+21 -3
+21 -3
View File
@@ -2497,8 +2497,26 @@ impl Player {
}
}
// Gapless readiness: signal frontend when ~5s remain
// Only when: playing, not streaming, no gapless already pending, gapless enabled
// Gapless readiness: signal frontend that it's
// time to prepare the next track.
//
// Lead time used to be 5s but that's too tight
// for offline-cache v2 bundles: the AES-CTR
// decrypt of a HiRes track on CPUs WITHOUT
// AES-NI runs at ~10 MB/s — a 58 MB track
// needs ~6s just to decrypt, which blows past
// a 5s window and misses the gapless handoff.
//
// 10s covers most HiRes tracks even on the
// software-AES fallback path, and is
// harmless when decrypt is fast (the bytes
// just land in L1 a few seconds earlier and
// sit there until the engine picks them up).
//
// If the frontend ever exposes a user setting
// for this, just plumb it through
// AudioSettings and read here.
const GAPLESS_LEAD_SECS: u64 = 10;
let gapless_enabled = thread_settings
.lock()
.ok()
@@ -2506,7 +2524,7 @@ impl Player {
.unwrap_or(false);
if gapless_enabled
&& dur > 0
&& pos + 5 >= dur
&& pos + GAPLESS_LEAD_SECS >= dur
&& gapless_pending.is_none()
&& !gapless_request_armed
&& !thread_state.is_gapless_ready()