zebra-report: mute button + peer-force-muted handler
Mirror of zebra-report a3c49f1 + proxy.unturf.com 90a5e96.
This commit is contained in:
parent
d91e908eb3
commit
94b981da29
1 changed files with 50 additions and 2 deletions
|
|
@ -3612,6 +3612,36 @@ async function handleSignal(raw){
|
|||
}
|
||||
}
|
||||
break;
|
||||
case 'peer-force-muted':
|
||||
{
|
||||
const mm = members.get(m.uuid);
|
||||
const by = members.get(m.by);
|
||||
const victPub = pubHexFromMsg(m, 'pubkey')
|
||||
|| (mm && mm.pubkey ? pubHexFromMsg({pubkey:mm.pubkey},'pubkey') : '');
|
||||
const byPub = pubHexFromMsg(m, 'by_pubkey');
|
||||
logLine('', idTag(m.uuid, victPub) + ' was muted by ' + (by||byPub?idTag(m.by, byPub):'a mod'));
|
||||
if (m.uuid === myUUID){
|
||||
/* I was the target — server already dropped my mic publisher
|
||||
* at the SFU; mirror that locally so my own UI shows muted
|
||||
* and the mic track stops sending into mesh PCs too. The
|
||||
* user can hit unmute to resume on their own. */
|
||||
showNotice('You were muted'+(by?' by '+by.handle:'')+'. Click unmute to talk again.', 'warn');
|
||||
if (!muted){
|
||||
muted = true;
|
||||
try { sessionStorage.setItem(MUTE_STATE_KEY, '1'); } catch(_){}
|
||||
if (micStream){
|
||||
for (const t of micStream.getAudioTracks()) t.enabled = false;
|
||||
}
|
||||
try { sendMicState(); } catch(_){}
|
||||
}
|
||||
renderRoom();
|
||||
} else {
|
||||
/* Mark the target visually muted right away — their next
|
||||
* mic-state broadcast will confirm it on the wire. */
|
||||
if (mm){ mm.muted = true; renderRoom(); }
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'peer-booted':
|
||||
{
|
||||
const mm = members.get(m.uuid);
|
||||
|
|
@ -3969,6 +3999,15 @@ async function modDemote(uuid, to){
|
|||
const sig = await signBytes(sigAction(roomID, roomEpoch, 'demote', uuid, to));
|
||||
send({ type:'demote', target: uuid, to, epoch: roomEpoch, sig });
|
||||
}
|
||||
/* mute: drops the speaker's mic publisher at the SFU so their voice
|
||||
* stops immediately. No role change — they keep their seat, keep
|
||||
* sharing screen / camera / game, keep hearing the room — and they
|
||||
* may unmute themselves on their own when they want to talk again. */
|
||||
async function modMute(uuid){
|
||||
if (!confirm('mute this speaker? (they can unmute themselves)')) return;
|
||||
const sig = await signBytes(sigAction(roomID, roomEpoch, 'mute', uuid));
|
||||
send({ type:'mute', target: uuid, epoch: roomEpoch, sig });
|
||||
}
|
||||
/* kick: drop the peer from this session — evicts their SFU PCs (audio
|
||||
* actually stops) but does NOT block their pubkey. They can rejoin
|
||||
* freely. Use when someone's audio is leaking from a closed-tab /
|
||||
|
|
@ -4210,6 +4249,15 @@ function renderRoom(){
|
|||
b.textContent = '→ speaker'; b.onclick = () => modDemote(m.uuid, 'speaker').catch(e=>logLine('err','demote: '+e.message));
|
||||
actsPrimary.appendChild(b);
|
||||
}
|
||||
/* mute: drops their mic publisher at the SFU. Speakers always;
|
||||
* cohosts only when I'm host (cohost can't mute another cohost). */
|
||||
if (canSpeak(m.role) && m.role !== 'host' &&
|
||||
!(m.role === 'cohost' && myRole !== 'host')){
|
||||
const mb = document.createElement('button'); mb.className='small';
|
||||
mb.textContent = 'mute';
|
||||
mb.onclick = () => modMute(m.uuid).catch(e => logLine('err','mute: '+e.message));
|
||||
actsPrimary.appendChild(mb);
|
||||
}
|
||||
/* kick + ban — destructive removal actions, kept on their own
|
||||
* row so the muscle-memory misclick where 'ban' sat next to
|
||||
* '→ speaker' can't fire by accident. */
|
||||
|
|
@ -4709,8 +4757,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">a9cbd8ef7da5690b94b8b76a9e69fd92</span><br>
|
||||
sha256 <span class="stamp-sha">b47ce1db998fae78a00c5f01ecb32f68a853a989babd8831290041776b854a40</span><br>
|
||||
md5 <span class="stamp-md5">14406e1c86dd563732f048cef3cf4cbb</span><br>
|
||||
sha256 <span class="stamp-sha">9b7b9f6b416b367475d3c1bf181acafbb684589ed18188fcb14b29927320c940</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