stream: compute now-playing duration/position off the HTTP thread
All checks were successful
continuous-integration/drone/push Build is passing

The /nowplaying handler called source.duration/source.elapsed on the live
crossfaded source. Since it is polled every second by the web player, those
cross-thread source queries contended with the crossfade's own clock during a
transition and wedged the streaming clock — a few seconds of audio then a
frozen buffer looping on air, only ever with a listener connected.

Capture duration and the track start time in a synchronous on_metadata
callback (which runs in the clock thread, where touching the source is safe)
and have /nowplaying read those refs instead — position is just the wall-clock
time since the track started. No handler touches the source outside its clock
anymore, so the 3 s crossfade is safe again.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nemunaire 2026-07-05 17:06:55 +08:00
commit b5f83ef6c8
2 changed files with 22 additions and 2 deletions

View file

@ -95,11 +95,14 @@ harbor.http.register(
# /scrobble pour prouver qu'il parle bien du titre à l'antenne.
# `duration`/`position` (secondes, arrondies) permettent au player de décider
# d'un scrobble « écouté à 90 %, démarré au début ». duration=0 si inconnue.
# Ces valeurs sont pré-calculées dans le thread d'horloge (voir cur_duration/
# cur_started dans radio.liq) : on ne doit JAMAIS appeler source.duration/
# source.elapsed ici, ça figerait l'antenne pendant un crossfade.
resp.json({
title=m["title"], artist=m["artist"], url=m["url"], origin=m["origin"],
file=path.basename(m["filename"]),
duration=int_of_float(source.duration(radio)),
position=int_of_float(source.elapsed(radio))
duration=int_of_float(cur_duration()),
position=int_of_float(time() - cur_started())
})
end
)