zebra-spaces: rebuild <video> on MSID supplant — autoplay grant resets
Live-room defect (2026-06-03): Will refreshed his browser; the supplant fired a SECOND ontrack for kind=camera pub=Will at +16s. The page swapped srcObject on the existing <video>, called play(), got 'fetching process for the media resource was aborted by the user agent at the user's request' — browser refused to start a new playback session on the same element after its autoplay grant had already been consumed. Tile sat black on the moderator's screen. Fix: swapFreshVideoElement() — on supplant, replace the <video> with a freshly-built one carrying the same attrs. A brand-new <video> is eligible for muted-autoplay even when the prior one had its play() rejected, so the supplant lands cleanly without needing a tap. Applied to both the thumbnail and the spotlight tile. Mesh test harness extracts the helper so renderVideoTile still runs end-to-end under the sandbox.
This commit is contained in:
parent
3ca9042e54
commit
fb7228b289
2 changed files with 44 additions and 3 deletions
|
|
@ -51,6 +51,7 @@ function extractConst(name){
|
|||
|
||||
const watchSrc = extractFn(/function watchVideoTrackForRemoval\(/);
|
||||
const firstFrameSrc = extractFn(/function watchFirstFrame\(/);
|
||||
const swapVideoSrc = extractFn(/function swapFreshVideoElement\(/);
|
||||
const renderTileSrc = extractFn(/function renderVideoTile\(/);
|
||||
const removeTileSrc = extractFn(/function removeVideoTile\(/);
|
||||
const renderScreenShim = extractFn(/function renderScreenTile\(/);
|
||||
|
|
@ -231,6 +232,7 @@ function makeBrowser(name, pubKeyHex){
|
|||
tileKindsSrc + '\n' +
|
||||
watchSrc + '\n' +
|
||||
firstFrameSrc + '\n' +
|
||||
swapVideoSrc + '\n' +
|
||||
renderTileSrc + '\n' +
|
||||
removeTileSrc + '\n' +
|
||||
renderScreenShim + '\n' +
|
||||
|
|
|
|||
|
|
@ -1869,6 +1869,26 @@ function updateContainerVisibility(){
|
|||
$('tiles-h2').classList.toggle('hidden', !anyThumb);
|
||||
}
|
||||
|
||||
/* Replace a <video> with a freshly-built one carrying the same
|
||||
* autoplay/playsinline/muted attributes, in the same DOM slot. Returns
|
||||
* the new element so the caller can update its reference. Browsers
|
||||
* treat a brand-new <video> as eligible for muted-autoplay even when
|
||||
* the old element had its play() rejected — so use this on any
|
||||
* supplant where srcObject would otherwise be swapped mid-life. */
|
||||
function swapFreshVideoElement(oldVideo){
|
||||
if (!oldVideo) return oldVideo;
|
||||
const fresh = document.createElement('video');
|
||||
fresh.setAttribute('autoplay', '');
|
||||
fresh.setAttribute('playsinline', '');
|
||||
fresh.setAttribute('muted', '');
|
||||
fresh.autoplay = true; fresh.playsInline = true; fresh.muted = true;
|
||||
if (oldVideo.parentNode){
|
||||
try { oldVideo.srcObject = null; } catch(_){}
|
||||
oldVideo.parentNode.replaceChild(fresh, oldVideo);
|
||||
}
|
||||
return fresh;
|
||||
}
|
||||
|
||||
/* build a tile DOM. opts.thumb = true for thumb-style (no fullscreen
|
||||
* button, gets .tile-thumb class). Returns { tile, video, fsBtn }. */
|
||||
function buildTile(kind, pubHex, label, opts){
|
||||
|
|
@ -2036,6 +2056,19 @@ function renderVideoTile(kind, pubHex, stream, opts){
|
|||
$(k.thumbContainer).appendChild(built.tile);
|
||||
entry = { tile: built.tile, video: built.video, label };
|
||||
k.store.set(pubHex, entry);
|
||||
} else {
|
||||
/* MSID-supplant retarget: when the same pubHex+kind republishes
|
||||
* (publisher refreshed / sfu PC rebuilt), reassigning srcObject on
|
||||
* the existing <video> hits the browser's autoplay policy as a
|
||||
* mid-life srcObject swap and a fresh play() rejects with
|
||||
* "fetching process for the media resource was aborted by the
|
||||
* user agent at the user's request" — the tile stays paused on a
|
||||
* black frame. Building a fresh <video> element resets that
|
||||
* decision: muted-video autoplay applies again because the
|
||||
* element has no prior gesture-consumed play() to defend against.
|
||||
* Fox 2026-06-03 ("still black share camera from will" after a
|
||||
* 16s supplant). */
|
||||
entry.video = swapFreshVideoElement(entry.video);
|
||||
}
|
||||
entry.video.srcObject = stream;
|
||||
try {
|
||||
|
|
@ -2051,9 +2084,15 @@ function renderVideoTile(kind, pubHex, stream, opts){
|
|||
logLine('err','play threw: '+e.message);
|
||||
entry.tile.classList.add('needs-tap');
|
||||
}
|
||||
/* if the same tile is currently spotlit, mirror the stream into the big tile */
|
||||
/* if the same tile is currently spotlit, mirror the stream into the big
|
||||
* tile — same supplant-vs-autoplay trap, same fix. */
|
||||
if (spotlight && spotlight.kind === kind && spotlight.pubHex === pubHex && spotlight.video){
|
||||
spotlight.video = swapFreshVideoElement(spotlight.video);
|
||||
spotlight.video.srcObject = stream;
|
||||
try {
|
||||
const p = spotlight.video.play();
|
||||
if (p && p.catch) p.catch(()=>{ spotlight.tile.classList.add('needs-tap'); });
|
||||
} catch(_){}
|
||||
}
|
||||
/* spotlight promotion rules:
|
||||
* - no spotlight yet → first tile auto-promotes (anyone)
|
||||
|
|
@ -4207,8 +4246,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');
|
|||
|
||||
<footer style="margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> · built <span class="stamp-date">2026-06-03</span><br>
|
||||
md5 <span class="stamp-md5">c874915d93ed4b044f8a9fd30e542d7c</span><br>
|
||||
sha256 <span class="stamp-sha">e13bfc8b82cd58097e12ac00591b4f2a324f589aaae10f9b488bbe78e6cce82d</span><br>
|
||||
md5 <span class="stamp-md5">86b88dea62be004921fe4e116bba90eb</span><br>
|
||||
sha256 <span class="stamp-sha">3686823398c477768b1af4f1dbb86f19cf69923e58d1ad94d8ecb7669e613e00</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
|
||||
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify against these hashes; point at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
|
||||
</footer>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue