zebra-spaces: parse SFU streamID with short-pubkey + dash — Firefox compat
Pairs with proxy.unturf.com#9575418. Firefox enforces RFC 7941 msid strictly: 1*64 token-chars, no ':'. Our streamID was pubkey + ':kind' = 71 chars with an invalid separator, so Firefox silently dropped every track and the listener saw only the game iframe. Client parse now: - format SHORT16HEX or SHORT16HEX-screen / SHORT16HEX-camera - 16-char prefix resolved back to the full pubhex via member roster - own-publish echo check matches by prefix (myKeys.pubHex.startsWith) - screens / cameras keep using full pubhex as the map key so existing identity-keyed code (renderScreenTile, removeCameraTile, screenStreams map, etc.) doesn't have to change
This commit is contained in:
parent
bd770230f9
commit
6beb138490
1 changed files with 47 additions and 41 deletions
|
|
@ -1423,53 +1423,59 @@ async function sfuSubscribe(){
|
|||
pc.ontrack = (ev) => {
|
||||
const sid = ev.streams[0] ? ev.streams[0].id : '';
|
||||
if (!sid) return;
|
||||
/* streamID format: PUBKEY (mic) | PUBKEY:screen | PUBKEY:camera.
|
||||
* Route video kinds to the tile renderer; mics to the audio path. */
|
||||
const colon = sid.indexOf(':');
|
||||
if (colon > 0){
|
||||
const pubHex = sid.slice(0, colon);
|
||||
const kind = sid.slice(colon + 1);
|
||||
/* skip echo of our own publish — we already render a local preview */
|
||||
if (myKeys && pubHex === myKeys.pubHex) return;
|
||||
/* video kinds: when the publisher unshares, the SFU stops the
|
||||
* transceiver and the corresponding remote track fires 'ended'.
|
||||
* Wire that to the tile remover so the listener's UI matches the
|
||||
* publisher's state instead of holding a frozen last frame. */
|
||||
if (kind === 'screen'){
|
||||
screenStreams.set(pubHex, ev.streams[0]);
|
||||
renderScreenTile(pubHex, ev.streams[0]);
|
||||
ev.track.addEventListener('ended', () => removeScreenTile(pubHex));
|
||||
return;
|
||||
}
|
||||
if (kind === 'camera'){
|
||||
cameraStreams.set(pubHex, ev.streams[0]);
|
||||
renderCameraTile(pubHex, ev.streams[0]);
|
||||
ev.track.addEventListener('ended', () => removeCameraTile(pubHex));
|
||||
return;
|
||||
}
|
||||
/* streamID format (RFC 7941 compliant — Firefox enforces 1*64 token-
|
||||
* chars and rejects ':'): SHORT16HEX (mic) | SHORT16HEX-screen |
|
||||
* SHORT16HEX-camera. Resolve the 16-char prefix back to a member's
|
||||
* full pubkey via lookup so the rest of the code keeps using full
|
||||
* pubhex as identity. */
|
||||
const dash = sid.indexOf('-');
|
||||
let pubHex16, kind;
|
||||
if (dash > 0){
|
||||
pubHex16 = sid.slice(0, dash);
|
||||
kind = sid.slice(dash + 1);
|
||||
} else {
|
||||
pubHex16 = sid;
|
||||
kind = 'mic';
|
||||
}
|
||||
/* skip echo of our own publish — match by prefix */
|
||||
if (myKeys && myKeys.pubHex.startsWith(pubHex16)) return;
|
||||
/* resolve short prefix → full pubhex via member roster */
|
||||
let pubHex = pubHex16;
|
||||
for (const [, mm] of members){
|
||||
try {
|
||||
if (mm.pubkey){
|
||||
const fh = hex(unb64(mm.pubkey));
|
||||
if (fh.startsWith(pubHex16)){ pubHex = fh; break; }
|
||||
}
|
||||
} catch(_){}
|
||||
}
|
||||
if (kind === 'screen'){
|
||||
screenStreams.set(pubHex, ev.streams[0]);
|
||||
renderScreenTile(pubHex, ev.streams[0]);
|
||||
ev.track.addEventListener('ended', () => removeScreenTile(pubHex));
|
||||
return;
|
||||
}
|
||||
if (kind === 'camera'){
|
||||
cameraStreams.set(pubHex, ev.streams[0]);
|
||||
renderCameraTile(pubHex, ev.streams[0]);
|
||||
ev.track.addEventListener('ended', () => removeCameraTile(pubHex));
|
||||
return;
|
||||
}
|
||||
if (kind !== 'mic'){
|
||||
logLine('', 'sfu: unknown kind '+kind+' from '+shortHex(pubHex));
|
||||
return;
|
||||
}
|
||||
/* mic audio — 400ms jitter-buffer target. Mobile/Wi-Fi peak inter-
|
||||
* arrival is 3-5x the smoothed jitter reported by getStats, so a
|
||||
* smoothed 50ms means 150-250ms peaks. 400ms absorbs that and feels
|
||||
* fine for music broadcasts; conversation gains ~quarter-second of
|
||||
* delay which is well below noticeable. */
|
||||
/* mic audio — 400ms jitter-buffer target absorbs Wi-Fi peak jitter */
|
||||
try { ev.receiver.playoutDelayHint = 0.4; } catch(_){}
|
||||
const pubHex = sid;
|
||||
/* never play our own mic back to ourselves — the SFU echoes our
|
||||
* publish to every sub including our own (we always subscribe now) */
|
||||
if (myKeys && pubHex === myKeys.pubHex) return;
|
||||
/* cache stream by publisher pubkey so it survives the member's session
|
||||
* uuid changing across leave/rejoin — see flushSfuStreams */
|
||||
/* cache by full pubkey (already resolved above) so it survives the
|
||||
* member's session uuid changing across leave/rejoin */
|
||||
sfuStreamsByPubHex.set(pubHex, ev.streams[0]);
|
||||
for (const [uuid, mm] of members){
|
||||
try {
|
||||
if (mm.pubkey && hex(unb64(mm.pubkey)) === pubHex){
|
||||
/* if we're a speaker and already have a mesh peer with this
|
||||
* member, mesh carries their audio with lower latency — skip
|
||||
* the duplicate SFU mic. Screens + cameras still come through
|
||||
* because they're handled above. */
|
||||
/* speakers get their peers' audio via mesh (lower latency) —
|
||||
* skip the duplicate SFU mic. screens + cameras still came
|
||||
* through above. */
|
||||
if (canSpeak(myRole) && peers.has(uuid)) return;
|
||||
attachSfuTrack(uuid, ev.streams[0]);
|
||||
return;
|
||||
|
|
@ -2708,8 +2714,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-02</span><br>
|
||||
md5 <span class="stamp-md5">181a0e0d4e4da7c4a40e2e5e576954fd</span><br>
|
||||
sha256 <span class="stamp-sha">29024b972f5dede7d5fd2b924170aaf9aa097afc9bac4402e86b01f9d543a4ed</span><br>
|
||||
md5 <span class="stamp-md5">dee696071acb10104b0a0c68024e9a68</span><br>
|
||||
sha256 <span class="stamp-sha">7fc20fb17b274f1bf5f667101e705530057cf859984a0dd386d83198ee65439e</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