5.6 KiB
Voice Session Domain
Tracks voice session metadata across client-side navigation and manages the voice workspace UI state (expanded, minimized, hidden). This domain does not touch WebRTC directly; actual connections live in voice-connection and infrastructure/realtime.
The actual mixed live-stream workspace UI lives in features/room/voice-workspace and consumes VoiceWorkspaceService from this domain.
Module map
voice-session/
├── application/
│ ├── facades/
│ │ └── voice-session.facade.ts Tracks active voice session, drives floating controls
│ └── services/
│ └── voice-workspace.service.ts Workspace mode (hidden/expanded/minimized), focused stream, mini-window position
│
├── domain/
│ ├── logic/
│ │ └── voice-session.logic.ts isViewingVoiceSessionServer, buildVoiceSessionRoom
│ └── models/
│ └── voice-session.model.ts VoiceSessionInfo interface
│
├── infrastructure/
│ └── util/
│ └── voice-settings-storage.util.ts Persists audio device IDs, volumes, bitrate, latency, noise reduction to localStorage
│
├── feature/
│ ├── voice-controls/ Full voice control panel (mic, camera, deafen, devices, screen share, settings)
│ └── floating-voice-controls/ Minimal overlay when user navigates away from the voice server
│
└── index.ts Barrel exports
How the pieces connect
The facade manages session bookkeeping. The workspace service owns view state. Settings storage provides persistence for user preferences. Neither service opens any WebRTC connections.
graph TD
VSF[VoiceSessionFacade]
VWS[VoiceWorkspaceService]
VSS[voiceSettingsStorage]
Logic[voice-session.logic]
VC[VoiceControlsComponent]
FC[FloatingVoiceControlsComponent]
Store[NgRx Store]
VC --> VSF
VC --> VWS
VC --> VSS
FC --> VSF
FC --> VWS
VSF --> Logic
VSF --> Store
VWS --> VSF
click VSF "application/facades/voice-session.facade.ts" "Tracks active voice session" _blank
click VWS "application/services/voice-workspace.service.ts" "Workspace mode and focused stream" _blank
click VSS "infrastructure/util/voice-settings-storage.util.ts" "localStorage persistence for audio settings" _blank
click Logic "domain/logic/voice-session.logic.ts" "Pure helper functions" _blank
click VC "feature/voice-controls/" "Full voice control panel" _blank
click FC "feature/floating-voice-controls/" "Minimal floating overlay" _blank
Session lifecycle
stateDiagram-v2
[*] --> NoSession
NoSession --> Active: startSession(info)
Active --> Active: checkCurrentRoute(serverId)
Active --> NoSession: endSession()
state Active {
[*] --> ViewingServer
ViewingServer --> AwayFromServer: navigated to different server
AwayFromServer --> ViewingServer: navigated back / navigateToVoiceServer()
}
When a voice session is active and the user navigates away from the voice-connected server, showFloatingControls becomes true and the floating overlay appears. Clicking the overlay dispatches RoomsActions.viewServer to navigate back.
Remote voice playback is scoped to the active voice channel, not the whole server. Users stay connected to the shared peer mesh for text, presence, and screen-share control, but voice transport and playback only stay active for peers whose voiceState.roomId and voiceState.serverId match the local user's current voice session.
Owners and admins can also move connected users between voice channels from the room sidebar by dragging a user onto a different voice channel. The moved client updates its local heartbeat and voice-session metadata to the new channel, so routing, floating controls, and occupancy stay in sync after the move.
Workspace modes
VoiceWorkspaceService controls the voice workspace panel state. The workspace is only visible when the user is viewing the voice-connected server.
stateDiagram-v2
[*] --> Hidden
Hidden --> Expanded: open()
Expanded --> Minimized: minimize()
Expanded --> Hidden: close() / showChat()
Minimized --> Expanded: restore()
Minimized --> Hidden: close()
Expanded --> Hidden: voice session ends
Minimized --> Hidden: voice session ends
The minimized mode renders a draggable mini-window. Its position is tracked in miniWindowPosition and clamped to viewport bounds on resize. focusedStreamId controls which live stream gets the widescreen treatment in expanded mode, using feature-level stream IDs such as screen:<peerKey> or camera:<peerKey>.
Voice settings
Settings are stored in localStorage under a single JSON key. All values are validated and clamped on load to defend against corrupt storage.
| Setting | Default | Range |
|---|---|---|
| inputDevice | "" |
device ID string |
| outputDevice | "" |
device ID string |
| inputVolume | 100 | 0 -- 100 |
| outputVolume | 100 | 0 -- 100 |
| audioBitrate | 96 kbps | 32 -- 256 |
| latencyProfile | "balanced" |
low / balanced / high |
| noiseReduction | true |
boolean |
| screenShareQuality | "balanced" |
low / balanced / high |
| askScreenShareQuality | true |
boolean |
| includeSystemAudio | false |
boolean |
loadVoiceSettingsFromStorage() and saveVoiceSettingsToStorage(patch) are the only entry points. The save function merges the patch with the current stored value so callers only need to pass changed fields.