From 39b74ef622622cac671d25cba56d21819f535907 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Mon, 1 Jun 2026 19:45:30 -0400 Subject: [PATCH] zebra-report: ship local preview tile for screen-share publisher Mirror zebra-spaces fix + refreshed page-integrity stamps from git.unturf.com/engineering/unturf/zebra-report d9852e6. --- zebra-report/host-your-own.html | 6 +++--- zebra-report/how-it-works.html | 6 +++--- zebra-report/index.html | 6 +++--- zebra-report/zebra-audio.html | 6 +++--- zebra-report/zebra-spaces.html | 29 +++++++++++++++++++++-------- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/zebra-report/host-your-own.html b/zebra-report/host-your-own.html index 50abfa0..13b0cc3 100644 --- a/zebra-report/host-your-own.html +++ b/zebra-report/host-your-own.html @@ -440,9 +440,9 @@ handle /zebra-spaces-sfu* { reverse_proxy localhost:8092 { flush_interval -1

diff --git a/zebra-report/how-it-works.html b/zebra-report/how-it-works.html index b9f2a23..aa5d50b 100644 --- a/zebra-report/how-it-works.html +++ b/zebra-report/how-it-works.html @@ -274,9 +274,9 @@

diff --git a/zebra-report/index.html b/zebra-report/index.html index 0354a22..0285ab6 100644 --- a/zebra-report/index.html +++ b/zebra-report/index.html @@ -1652,9 +1652,9 @@ logLine('sys', 'chat content lives in encrypted SRTP audio. no IP packets carry })(); diff --git a/zebra-report/zebra-audio.html b/zebra-report/zebra-audio.html index b7f52fb..a4f2c0e 100644 --- a/zebra-report/zebra-audio.html +++ b/zebra-report/zebra-audio.html @@ -659,9 +659,9 @@ else wirePuppet(); diff --git a/zebra-report/zebra-spaces.html b/zebra-report/zebra-spaces.html index 4c65260..eb5d5da 100644 --- a/zebra-report/zebra-spaces.html +++ b/zebra-report/zebra-spaces.html @@ -608,17 +608,24 @@ function flushSfuStreams(){ /* render one screen-share tile (or update its srcObject if it already * exists). Removed on peer-left / boot / SFU track removal via * removeScreenTile. */ -function renderScreenTile(pubHex, stream){ - /* find the matching member for the title */ +function renderScreenTile(pubHex, stream, opts){ + const local = !!(opts && opts.local); + /* find the matching member for the title; self isn't in members map */ let label = shortHex(pubHex); - for (const [, mm] of members){ - try { if (mm.pubkey && hex(unb64(mm.pubkey)) === pubHex){ label = mm.handle || label; break; } } catch(_){} + if (local){ + label = (myHandle || label) + ' (you)'; + } else { + for (const [, mm] of members){ + try { if (mm.pubkey && hex(unb64(mm.pubkey)) === pubHex){ label = mm.handle || label; break; } } catch(_){} + } } let entry = screenVideos.get(pubHex); if (!entry){ const tile = document.createElement('div'); tile.className = 'screen-tile'; const video = document.createElement('video'); - video.autoplay = true; video.playsInline = true; video.muted = false; + /* local preview must be muted — playing back our own captured audio + * would feed back through the mic */ + video.autoplay = true; video.playsInline = true; video.muted = local; const meta = document.createElement('div'); meta.className = 'screen-meta'; const who = document.createElement('span'); who.textContent = 'screen: '+label; const pop = document.createElement('button'); pop.className = 'small'; @@ -632,7 +639,7 @@ function renderScreenTile(pubHex, stream){ entry.video.srcObject = stream; try { const p = entry.video.play(); if (p && p.catch) p.catch(()=>{}); } catch(_){} $('sec-screens').classList.remove('hidden'); - logLine('', 'screen share: receiving '+label); + logLine('', 'screen share: '+(local?'local preview ':'receiving ')+label); } function removeScreenTile(pubHex){ const entry = screenVideos.get(pubHex); @@ -694,12 +701,16 @@ async function sfuPublishScreen(){ await pc.setRemoteDescription({ type:'answer', sdp: ans.sdp }); sfuScreenPC = pc; sfuScreenPeerID = ans.peer_id; logLine('', 'sfu: sharing screen as '+shortHex(sfuScreenPeerID)); + /* render a muted local preview so the publisher sees what they're + * sharing — SFU does not echo the publisher's own stream back */ + renderScreenTile(myKeys.pubHex, stream, { local: true }); $('btn-screen-share').classList.add('hidden'); $('btn-screen-stop').classList.remove('hidden'); } async function sfuUnpublishScreen(){ if (!sfuScreenPC && !sfuScreenStream) return; const pid = sfuScreenPeerID; + if (myKeys) removeScreenTile(myKeys.pubHex); if (sfuScreenStream){ sfuScreenStream.getTracks().forEach(t=>t.stop()); sfuScreenStream = null; } if (sfuScreenPC){ try { sfuScreenPC.close(); } catch(_){} sfuScreenPC = null; sfuScreenPeerID = null; } if (pid && roomID){ @@ -730,6 +741,8 @@ async function sfuSubscribe(){ * Route screens to the video tile renderer; mics to the audio path. */ if (sid.endsWith(':screen')){ const pubHex = sid.slice(0, -':screen'.length); + /* skip echo of our own screen — we already render a local preview */ + if (myKeys && pubHex === myKeys.pubHex) return; screenStreams.set(pubHex, ev.streams[0]); renderScreenTile(pubHex, ev.streams[0]); return; @@ -1569,8 +1582,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');