Outgoing voice was gated on the observer's roster copy of the remote user's voice state, which is signaling gossip. The signal server broadcasts `user_left` for any socket it declares dead, so a suspended laptop or a flaky hop wiped that copy and the observer detached its microphone from a peer that never left the channel - a silent member with no way back through the UI. `decideVoicePathRouting` now closes a path only on positive evidence: we left voice, the peer itself reported another channel or none, or the connection is gone. Missing gossip holds an established path instead. Opening still needs confirmation, so a guess never starts sending; the same rule gates playback, camera video, and the microphone a new connection puts in its first offer. Peers are also asked for their voice state when a connection or data channel comes up, so a rebuilt path re-confirms itself. Alongside it, the microphone can be switched mid-call: capture moves to a device service and rules, the live track is swapped with `replaceTrack` so the session is never renegotiated, and the speaking indicator follows the new stream.
92 lines
3.9 KiB
JSON
92 lines
3.9 KiB
JSON
{
|
|
"voice": {
|
|
"controls": {
|
|
"connectionError": "Connection error",
|
|
"connectionErrorStatus": "Connection Error",
|
|
"connected": "Connected",
|
|
"retry": "Retry",
|
|
"failedConnect": "Failed to connect voice session."
|
|
},
|
|
"devices": {
|
|
"inputFellBack": "Your microphone was disconnected. Switched to the system default.",
|
|
"outputFellBack": "Your speaker was disconnected. Switched to the system default."
|
|
},
|
|
"floating": {
|
|
"backToServer": "Back to {{server}}",
|
|
"voiceFallback": "Voice",
|
|
"toggleMute": "Toggle Mute",
|
|
"toggleDeafen": "Toggle Deafen",
|
|
"toggleScreenShare": "Toggle Screen Share",
|
|
"disconnect": "Disconnect"
|
|
},
|
|
"workspace": {
|
|
"streams": "Streams",
|
|
"inVoice": "{{count}} in voice",
|
|
"liveStreams": "{{count}} live streams",
|
|
"liveStream": "{{count}} live stream",
|
|
"localPreview": "Local preview",
|
|
"focusedStream": "Focused stream",
|
|
"yourCamera": "Your camera",
|
|
"yourScreen": "Your screen",
|
|
"focusedStreamFallback": "Focused stream",
|
|
"unmuteStreamAudio": "Unmute stream audio",
|
|
"muteStreamAudio": "Mute stream audio",
|
|
"showAllStreams": "Show all streams",
|
|
"allStreams": "All streams",
|
|
"minimizeWorkspace": "Minimize stream workspace",
|
|
"returnToChat": "Return to chat",
|
|
"otherLiveStreams": "Other live streams",
|
|
"noLiveStreams": "No live streams yet",
|
|
"noLiveStreamsDescription": "Turn on your camera, click Screen Share below, or wait for someone in {{channel}} to go live.",
|
|
"participantsReady": "{{count}} participants ready",
|
|
"startScreenShare": "Start screen sharing",
|
|
"expand": "Expand",
|
|
"close": "Close",
|
|
"waitingForStream": "Waiting for a live stream",
|
|
"connectedTo": "Connected to {{server}}",
|
|
"miniWindowHint": "{{count}} live streams · double-click to expand",
|
|
"miniWindowHintSingle": "{{count}} live stream · double-click to expand",
|
|
"voiceLounge": "Voice Lounge",
|
|
"voiceServer": "Voice server",
|
|
"voiceWorkspace": "Voice workspace"
|
|
},
|
|
"streamTile": {
|
|
"cameraLive": "Camera live",
|
|
"screenShareLive": "Screen share live",
|
|
"focusAria": "Focus {{name}} {{badge}}",
|
|
"openAria": "Open {{name}} {{badge}} in widescreen mode",
|
|
"exitFullscreenTitle": "Double-click to exit fullscreen",
|
|
"enterFullscreenTitle": "Double-click for fullscreen",
|
|
"localCameraFullscreen": "Local camera preview in fullscreen",
|
|
"localPreviewFullscreen": "Local preview in fullscreen",
|
|
"cameraFullscreen": "Fullscreen camera view",
|
|
"streamFullscreen": "Fullscreen stream view",
|
|
"localCameraPreview": "Your camera preview never captures audio.",
|
|
"localPreviewMuted": "Your preview stays muted locally to avoid audio feedback.",
|
|
"streamVolume": "Stream volume",
|
|
"screenShareVolume": "Screen share volume",
|
|
"rotateLandscape": "Rotate to landscape",
|
|
"exitFullscreen": "Exit fullscreen",
|
|
"fullscreen": "Fullscreen",
|
|
"noScreenAudio": "No screen audio",
|
|
"viewingWidescreen": "Viewing in widescreen",
|
|
"viewInWidescreen": "View in widescreen",
|
|
"streamAudio": "Stream audio",
|
|
"audioPercent": "{{volume}}% audio"
|
|
}
|
|
},
|
|
"network": {
|
|
"signaling": {
|
|
"connectTimeout": "Timed out connecting to signaling server",
|
|
"connectionFailed": "Connection to signaling server failed",
|
|
"disconnected": "Disconnected from signaling server",
|
|
"healthCheckFailed": "Signaling server health check failed",
|
|
"instanceChanged": "Signaling server instance changed; refreshing websocket",
|
|
"recovered": "Signaling server recovered; refreshing websocket",
|
|
"keepaliveTimeout": "Signaling keepalive acknowledgement timed out",
|
|
"keepaliveSendFailed": "Failed to send signaling keepalive",
|
|
"payloadSendFailed": "Failed to send signaling payload"
|
|
}
|
|
}
|
|
}
|