From 32464c93fb0e94918761fd5de35eea88cf53cd08 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sat, 21 Feb 2026 15:57:45 +0100 Subject: [PATCH] Fix race condition in play() and set_uri MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrige une condition de course où play() pouvait être appelé avant set_uri. Ajoute un flag playPending pour reporter la lecture jusqu'à ce que la source soit définie, et gère correctement les cas où l'élément média n'a pas encore les données nécessaires. --- .../webapp/src/composables/useWebRenderer.ts | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/pmoapp/webapp/src/composables/useWebRenderer.ts b/pmoapp/webapp/src/composables/useWebRenderer.ts index 9d729fd3..b1913b5d 100644 --- a/pmoapp/webapp/src/composables/useWebRenderer.ts +++ b/pmoapp/webapp/src/composables/useWebRenderer.ts @@ -110,6 +110,12 @@ class GaplessEngine { /** Durée de la piste courante (secondes), lue via loadedmetadata */ private _duration = 0; + /** + * Indique qu'un play() a été reçu avant set_uri (race condition). + * setCurrent() le détectera et lancera la lecture automatiquement. + */ + private playPending = false; + onStateChange: (state: PlaybackState) => void = () => {}; onPosition: (pos: number, dur: number) => void = () => {}; onTrackEnded: () => void = () => {}; @@ -161,7 +167,14 @@ class GaplessEngine { el.onloadedmetadata = () => { this._duration = el.duration || 0; }; - // Ne pas émettre STOPPED ici : l'état reste TRANSITIONING jusqu'au play() + + // Si play() est arrivé avant set_uri (race condition), lancer la lecture maintenant + if (this.playPending) { + this.playPending = false; + this.play().catch((e) => + console.error("[GaplessEngine] deferred play() failed:", e), + ); + } } /** Précharge la piste suivante dans l'autre slot. */ @@ -177,8 +190,27 @@ class GaplessEngine { async play(): Promise { const el = this.slots[this.currentSlot]; + // Si pas de source : play() est arrivé avant set_uri (race condition). + // On mémorise et setCurrent() déclenchera la lecture dès qu'il sera appelé. + if (!el.src || el.src === window.location.href) { + this.playPending = true; + return; + } + + this.playPending = false; el.onended = () => this.onCurrentEnded(); + // Si l'élément n'a pas encore de données, attendre canplay. + if (el.readyState < HTMLMediaElement.HAVE_FUTURE_DATA) { + await new Promise((resolve) => { + const onCanPlay = () => { + el.removeEventListener("canplay", onCanPlay); + resolve(); + }; + el.addEventListener("canplay", onCanPlay); + }); + } + try { await el.play(); } catch (e) { @@ -199,6 +231,7 @@ class GaplessEngine { } stop(): void { + this.playPending = false; const el = this.slots[this.currentSlot]; el.onended = null; el.pause();