# 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. ```mermaid 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 ```mermaid 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. ```mermaid 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:` or `camera:`. ## 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.