my-sea voice: guard before NVM disconnects voice + harden mute (honor pre-stream mute) — TDD
Phase 4 of the my-sea voice batch (user-spec 2026-05-29). ── Voice-disconnect guard (item 6, the achievable slice) ── Every my-sea navigation is a full page reload, which tears the WebRTC mesh down — so voice can't literally persist across a reload without an SPA-style no-reload nav (a separate, larger refactor, deferred). What ships now: the gear-menu NVM warns before dropping the call. - _my_sea_gear.html: the NVM routes through an inline, dependency-free `mySeaGuardedNav(event, url)`. When voice is LIVE (VoiceRoom.localStream) AND the target leaves my_sea (`url` has no `/my-sea`), it pops the shared guard portal — "Leave the Sea? You'll disconnect from voice." — before navigating; confirm proceeds, dismiss stays. NVMs that stay within my_sea, or any nav with no live mic, go straight through. Covers all NVMs (owner my_sea, the gatekeeper, the spectator) since they all include this partial. ── Bug B: desktop mute (mute robustness) ── - voice-mesh.js: extracted `_applyMute()` and call it from join (post- getUserMedia) as well as toggleMute. On desktop the first join pops a mic- permission prompt; a mute toggled while that prompt is open used to be lost because the stream didn't exist yet — re-applying after it resolves makes the mute stick. Teardown resets `muted=false` so a rejoin starts clean. - voice-glow.js: setVoiceState now syncs the voice btn's own flags (.in-call / .muted / dataset.inCall) to the mesh truth, so a rejoin starts clean and the glow's DOM fallback can't get stuck "live". Tests: +5 VoiceMeshSpec mute specs (incl. the pre-stream-mute Bug-B case); +2 NVM-guard FTs (warn when live / pass through when not); 3 gear-NVM ITs updated for the mySeaGuardedNav markup. 286 gameboard ITs + 433 Jasmine specs green. Note: true cross-view voice persistence (no-reload within-my_sea nav) + the desktop-mute live confirmation remain to verify on staging w. Redis up. Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -75,6 +75,12 @@
|
||||
st = st || {};
|
||||
inCall = !!st.inCall;
|
||||
peerCount = st.peerCount || 0;
|
||||
// Keep the voice btn's own flags in sync with the mesh truth so a
|
||||
// rejoin starts clean (dataset.inCall gates join-vs-mute in
|
||||
// burger-btn.js) and the render fallback can't get stuck "live".
|
||||
voice.classList.toggle('in-call', inCall);
|
||||
voice.classList.toggle('muted', inCall && !!st.muted);
|
||||
if (!inCall) { delete voice.dataset.inCall; }
|
||||
render();
|
||||
}
|
||||
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
});
|
||||
}).then(function (stream) {
|
||||
self.localStream = stream;
|
||||
self._applyMute(); // honor a mute toggled during the permission prompt
|
||||
var scheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
self.ws = new WebSocket(scheme + '://' + window.location.host + '/ws/voice/' + roomId + '/');
|
||||
self.ws.onmessage = function (e) { self._onMessage(JSON.parse(e.data)); };
|
||||
@@ -189,13 +190,23 @@
|
||||
});
|
||||
};
|
||||
|
||||
// Push the current `muted` flag onto the live audio tracks. Extracted so
|
||||
// BOTH toggleMute AND join (post-getUserMedia) call it: on desktop the
|
||||
// first join pops a mic-permission prompt, and a mute toggled while that
|
||||
// prompt is open used to be lost because the stream didn't exist yet —
|
||||
// the fresh tracks then came up enabled regardless. Re-applying after the
|
||||
// stream resolves makes the mute stick (Bug B, 2026-05-29).
|
||||
VoiceRoom.prototype._applyMute = function () {
|
||||
if (!this.localStream) return;
|
||||
var enabled = !this.muted;
|
||||
this.localStream.getAudioTracks().forEach(function (t) {
|
||||
t.enabled = enabled;
|
||||
});
|
||||
};
|
||||
|
||||
VoiceRoom.prototype.toggleMute = function () {
|
||||
this.muted = !this.muted;
|
||||
if (this.localStream) {
|
||||
this.localStream.getAudioTracks().forEach(function (t) {
|
||||
t.enabled = !this.muted;
|
||||
}, this);
|
||||
}
|
||||
this._applyMute();
|
||||
this._notify();
|
||||
return this.muted;
|
||||
};
|
||||
@@ -206,7 +217,8 @@
|
||||
this.localStream.getTracks().forEach(function (t) { t.stop(); });
|
||||
this.localStream = null;
|
||||
}
|
||||
this._notify(); // call ended — back to not-in-call
|
||||
this.muted = false; // a rejoin starts unmuted
|
||||
this._notify(); // call ended — back to not-in-call
|
||||
};
|
||||
|
||||
VoiceRoom.prototype.leave = function () {
|
||||
|
||||
Reference in New Issue
Block a user