web: prefer video tag with gesture audio unlock

This commit is contained in:
every.channel 2026-02-24 23:21:19 -08:00
parent c545b2381d
commit ad81b9791a
No known key found for this signature in database
2 changed files with 63 additions and 7 deletions

View file

@ -169,10 +169,16 @@ function mountPlayer(relayUrl, name) {
watch.connection.websocket = { enabled: false };
}
const canvas = document.createElement("canvas");
canvas.className = "canvas";
watch.appendChild(canvas);
// Prefer a video element for native controls/audio routing.
// Start muted to satisfy autoplay policy, then unlock audio on user gesture.
const video = document.createElement("video");
video.className = "archiveVideo";
video.controls = true;
video.autoplay = true;
video.muted = true;
video.volume = 1;
video.playsInline = true;
watch.appendChild(video);
mount.appendChild(watch);
const forceAudioOn = () => {
try {
@ -182,9 +188,33 @@ function mountPlayer(relayUrl, name) {
// Best effort only.
}
};
forceAudioOn();
window.setTimeout(forceAudioOn, 1000);
window.setTimeout(forceAudioOn, 4000);
const unlockAudio = () => {
forceAudioOn();
watch.backend?.paused?.set?.(true);
watch.backend?.paused?.set?.(false);
video.muted = false;
video.volume = 1;
void video.play().catch(() => {});
setHint(`Live: subscribed to ${name} (audio unlocked)`, "ok");
};
document.addEventListener("pointerdown", unlockAudio, { once: true });
video.addEventListener("pointerdown", unlockAudio, { once: true });
void video.play().catch(() => {});
// If video-element rendering stalls, fall back to canvas rendering.
window.setTimeout(() => {
const stalled = video.readyState < 2 || (video.currentTime === 0 && !video.paused);
if (!stalled) return;
const canvas = document.createElement("canvas");
canvas.className = "canvas";
try {
video.remove();
watch.appendChild(canvas);
setHint(`Live: subscribed to ${name} (canvas fallback)`, "warn");
} catch (_) {
// Ignore fallback errors.
}
}, 9000);
bindPlayerSignals(watch, name);
}

View file

@ -0,0 +1,26 @@
# ECP-0078: Live `<video>`-First Rendering With Gesture Audio Unlock
## Context
Live browser playback currently prioritizes canvas rendering. Audio can fail on first load due to autoplay policy (`AudioContext was not allowed to start`) and we still need a robust `<video>` rendering path for native controls.
## Decision
In the web watcher mount path:
1. Render live playback with a `<video>` child in `<moq-watch>` first.
2. Start muted for autoplay compatibility, then unlock audio on first user gesture by:
- forcing backend `muted=false`, `volume=1`,
- toggling paused state to trigger resume,
- unmuting the `<video>` element.
3. If `<video>` playback stalls during startup, automatically fall back to canvas rendering.
## Rationale
- Preserves the `<video>` UX target while handling browser autoplay constraints explicitly.
- Avoids total playback failure by retaining a tested canvas fallback path.
- Keeps changes local to app wiring without forking upstream MoQ player internals.
## Reversibility
- Remove the fallback timer and unlock wiring to return to a fixed rendering mode.