Add native self-hosted instance connection to fluxer_desktop

Trimmed monorepo checkout (fluxer_desktop + packages/voice_engine_v2 +
tools/ci) with a "Connect to a Different Server" menu item and popout
that lets the desktop app switch to any self-hosted Fluxer instance,
plus fixes for well-known discovery on single-domain self-hosted
deployments and a false-positive ERR_ABORTED on same-origin client
redirects during the switch. Defaults to chat.fluxr.chat and uses an
isolated userData directory from the official build.
This commit is contained in:
2026-07-01 18:22:43 -04:00
commit 682afacd30
1763 changed files with 613720 additions and 0 deletions
@@ -0,0 +1,73 @@
{
"measured_at": "85e057a273fd",
"host": "darwin-arm64-apple-silicon",
"regression_budget_percent": 5.0,
"note": "Vitest bench medians captured as `mean_ms` (mean over samples). check-regression.sh treats `mean_ms` like `median_ns` after scaling. p75 captured as a secondary stability hint.",
"benches": {
"src/runtime/VoiceEngineV2Runtime.bench.ts > dispatch inboundVideo.frameReceived": {
"mean_ms": 0.3517,
"p75_ms": 0.4266,
"hz": 2843.2
},
"src/runtime/VoiceEngineV2Runtime.bench.ts > dispatch microphone.publishRequested": {
"mean_ms": 0.4786,
"p75_ms": 0.534,
"hz": 2089.33
},
"src/runtime/VoiceEngineV2Runtime.bench.ts > dispatch screen.publishRequested": {
"mean_ms": 0.474,
"p75_ms": 0.5327,
"hz": 2109.75
},
"src/runtime/VoiceEngineV2Runtime.bench.ts > dispatch room.participantJoined": {
"mean_ms": 0.344,
"p75_ms": 0.4121,
"hz": 2906.83
},
"src/runtime/VoiceEngineV2Runtime.bench.ts > dispatch stats.collected": {
"mean_ms": 0.3451,
"p75_ms": 0.4295,
"hz": 2897.74
},
"src/core/reducer.bench.ts > inboundVideo.frameReceived": {
"mean_ms": 0.0001,
"p75_ms": 0.0001,
"hz": 7877275.4
},
"src/core/reducer.bench.ts > microphone.publishRequested": {
"mean_ms": 0.0002,
"p75_ms": 0.0002,
"hz": 5768974.56
},
"src/core/reducer.bench.ts > screen.publishRequested": {
"mean_ms": 0.0002,
"p75_ms": 0.0002,
"hz": 6149233.82
},
"src/core/reducer.bench.ts > room.participantJoined": {
"mean_ms": 0.0001,
"p75_ms": 0.0001,
"hz": 11101158.62
},
"src/core/reducer.bench.ts > stats.collected": {
"mean_ms": 0.0,
"p75_ms": 0.0,
"hz": 28019021.94
},
"src/runtime/snapshot.bench.ts > JSON.stringify snapshot (8 participants × 4 tracks)": {
"mean_ms": 0.0174,
"p75_ms": 0.018,
"hz": 57366.32
},
"src/runtime/snapshot.bench.ts > JSON.parse snapshot (8 participants × 4 tracks)": {
"mean_ms": 0.0168,
"p75_ms": 0.0167,
"hz": 59607.57
},
"src/runtime/snapshot.bench.ts > JSON.stringify + JSON.parse round-trip": {
"mean_ms": 0.0358,
"p75_ms": 0.0356,
"hz": 27897.14
}
}
}
@@ -0,0 +1,49 @@
{
"name": "basic_session",
"events": [
{
"type": "microphone.publishRequested",
"options": {
"deviceId": "default"
}
},
{
"type": "connection.connectRequested",
"options": {
"url": "wss://voice.example.test",
"token": "token-1"
}
},
{
"type": "connection.connectSucceeded",
"operationId": 1
},
{
"type": "microphone.publishSucceeded",
"operationId": 2
},
{
"type": "gateway.voiceStateUpdated",
"voiceState": {
"guildId": "guild-1",
"channelId": "channel-1",
"userId": "user-1",
"sessionId": "session-1",
"selfMute": false,
"selfDeaf": false,
"selfVideo": false,
"selfStream": false,
"suppress": false,
"requestToSpeakTimestamp": null
}
}
],
"expected": {
"nextOperationId": 3,
"connectionStatus": "connected",
"activeUrl": "wss://voice.example.test",
"microphoneStatus": "published",
"microphoneDeviceId": "default",
"gatewayChannelId": "channel-1"
}
}
@@ -0,0 +1,93 @@
{
"version": 18,
"methods": [
"isSupported",
"getCapabilities",
"prewarm",
"getHardwareEncoderCapabilities",
"connect",
"disconnect",
"isConnected",
"publishMicrophone",
"pushPcm",
"publishScreen",
"updateScreenShareEncoding",
"unpublishScreen",
"publishScreenAudio",
"pushScreenAudioPcm",
"pushScreenAudioFloat",
"unpublishScreenAudio",
"setMicEnabled",
"setSpeakingDetection",
"listAudioInputDevices",
"listAudioOutputDevices",
"setAudioOutputDevice",
"setParticipantVolume",
"setRemoteTrackSubscription",
"publishData",
"listCameraDevices",
"publishCamera",
"updateCameraCapture",
"publishNativeCameraSink",
"publishProcessedCamera",
"pushProcessedCameraFrame",
"pushCameraBackgroundFrame",
"clearCameraBackgroundFrame",
"publishDeviceScreenShare",
"unpublishCamera",
"isPublishingCamera",
"startCameraPreview",
"stopCameraPreview",
"getConnectionStats",
"getVoiceEngineReadiness",
"getAudioDeviceModuleState",
"onEvent",
"onVideoFrame"
],
"ipcChannels": {
"isSupported": "voice-engine-v2:is-supported",
"getCapabilities": "voice-engine-v2:get-capabilities",
"prewarm": "voice-engine-v2:prewarm",
"getHardwareEncoderCapabilities": "voice-engine-v2:get-hardware-encoder-capabilities",
"connect": "voice-engine-v2:connect",
"disconnect": "voice-engine-v2:disconnect",
"isConnected": "voice-engine-v2:is-connected",
"publishMicrophone": "voice-engine-v2:publish-microphone",
"pushPcm": "voice-engine-v2:push-pcm",
"publishScreen": "voice-engine-v2:publish-screen",
"updateScreenShareEncoding": "voice-engine-v2:update-screen-share-encoding",
"unpublishScreen": "voice-engine-v2:unpublish-screen",
"publishScreenAudio": "voice-engine-v2:publish-screen-audio",
"pushScreenAudioPcm": "voice-engine-v2:push-screen-audio-pcm",
"pushScreenAudioFloat": "voice-engine-v2:push-screen-audio-float",
"unpublishScreenAudio": "voice-engine-v2:unpublish-screen-audio",
"setMicEnabled": "voice-engine-v2:set-mic-enabled",
"setSpeakingDetection": "voice-engine-v2:set-speaking-detection",
"listAudioInputDevices": "voice-engine-v2:list-audio-input-devices",
"listAudioOutputDevices": "voice-engine-v2:list-audio-output-devices",
"setAudioOutputDevice": "voice-engine-v2:set-audio-output-device",
"setParticipantVolume": "voice-engine-v2:set-participant-volume",
"setRemoteTrackSubscription": "voice-engine-v2:set-remote-track-subscription",
"publishData": "voice-engine-v2:publish-data",
"listCameraDevices": "voice-engine-v2:list-camera-devices",
"publishCamera": "voice-engine-v2:publish-camera",
"updateCameraCapture": "voice-engine-v2:update-camera-capture",
"publishNativeCameraSink": "voice-engine-v2:publish-native-camera-sink",
"publishProcessedCamera": "voice-engine-v2:publish-processed-camera",
"pushProcessedCameraFrame": "voice-engine-v2:push-processed-camera-frame",
"pushCameraBackgroundFrame": "voice-engine-v2:push-camera-background-frame",
"clearCameraBackgroundFrame": "voice-engine-v2:clear-camera-background-frame",
"publishDeviceScreenShare": "voice-engine-v2:publish-device-screen-share",
"unpublishCamera": "voice-engine-v2:unpublish-camera",
"isPublishingCamera": "voice-engine-v2:is-publishing-camera",
"startCameraPreview": "voice-engine-v2:start-camera-preview",
"stopCameraPreview": "voice-engine-v2:stop-camera-preview",
"getConnectionStats": "voice-engine-v2:get-connection-stats",
"getVoiceEngineReadiness": "voice-engine-v2:get-readiness",
"getAudioDeviceModuleState": "voice-engine-v2:get-audio-device-module-state"
},
"eventChannels": {
"event": "voice-engine-v2:event",
"videoFrame": "voice-engine-v2:video-frame"
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,61 @@
{
"name": "camera_in_place_update",
"events": [
{
"type": "connection.connectRequested",
"options": {"url": "wss://voice.example.test", "token": "token-camera"}
},
{"type": "connection.connectSucceeded", "operationId": 1},
{
"type": "camera.publishRequested",
"options": {"deviceId": "camera-1", "width": 1280, "height": 720, "mirror": false}
},
{"type": "camera.publishSucceeded", "operationId": 2},
{
"type": "camera.updateEncodingRequested",
"options": {
"width": 1920,
"height": 1080,
"frameRate": 60,
"mirror": true,
"backgroundMode": "blur",
"backgroundBlurStrength": 60
}
},
{"type": "camera.updateEncodingSucceeded", "operationId": 3}
],
"expectedCommands": [
{
"type": "connection.connect",
"operationId": 1,
"options": {"url": "wss://voice.example.test", "token": "token-camera"}
},
{
"type": "camera.publish",
"operationId": 2,
"options": {"deviceId": "camera-1", "width": 1280, "height": 720, "mirror": false}
},
{
"type": "camera.updateEncoding",
"operationId": 3,
"options": {
"width": 1920,
"height": 1080,
"frameRate": 60,
"mirror": true,
"backgroundMode": "blur",
"backgroundBlurStrength": 60
}
}
],
"expected": {
"nextOperationId": 4,
"cameraStatus": "published",
"cameraDeviceId": "camera-1",
"cameraWidth": 1920,
"cameraFrameRate": 60,
"cameraMirror": true,
"cameraBackgroundMode": "blur",
"lastFailureCode": null
}
}
@@ -0,0 +1,48 @@
{
"name": "capabilities_lifecycle",
"events": [
{
"type": "capabilities.changed",
"capabilities": {
"connect": true,
"microphone": true,
"camera": true,
"screen": true,
"screenAudio": true,
"outputDevice": true,
"participantVolume": true,
"remoteTrackSubscription": true,
"dataChannel": true,
"stats": true,
"nativeVideoFrames": true,
"hardwareEncoding": true,
"zeroCopyScreenTransport": true,
"nativeAudioTaps": true
}
},
{"type": "capabilities.hardwareEncoderQueryRequested"},
{
"type": "capabilities.hardwareEncoderChanged",
"operationId": 1,
"capabilities": {
"available": true,
"backend": "nvenc",
"compiled": true,
"runtime": true,
"codecs": ["h264", "hevc"],
"zeroCopy": true,
"nativeInputs": ["d3d11-texture"]
}
}
],
"expectedCommands": [[], [{"type": "capabilities.queryHardwareEncoder", "operationId": 1}], []],
"expected": {
"nextOperationId": 2,
"hardwareEncoderOperationId": null,
"hardwareEncoderFailureCode": null,
"hardwareEncoderBackend": "nvenc",
"hasZeroCopyNativeInput": true,
"hasNativeNvencH264": true,
"hasNativeNvencH265": true
}
}
@@ -0,0 +1,93 @@
{
"name": "codec_republish",
"events": [
{
"type": "connection.connectRequested",
"options": {
"url": "wss://voice.example.test",
"token": "token-codec"
}
},
{
"type": "connection.connectSucceeded",
"operationId": 1
},
{
"type": "screen.publishRequested",
"options": {
"captureId": "capture-screen-1",
"width": 1920,
"height": 1080,
"codec": "h264",
"hardwareEncoding": true,
"zeroCopyRequired": true,
"maxFramerate": 60
}
},
{
"type": "screen.publishSucceeded",
"operationId": 2
},
{
"type": "screen.updateEncodingRequested",
"options": {
"captureId": "capture-screen-1",
"width": 1920,
"height": 1080,
"codec": "h265",
"hardwareEncoding": true,
"zeroCopyRequired": true,
"frameRate": 60
}
},
{
"type": "screen.publishSucceeded",
"operationId": 3
}
],
"expectedCommands": [
{
"type": "connection.connect",
"operationId": 1,
"options": {
"url": "wss://voice.example.test",
"token": "token-codec"
}
},
{
"type": "screen.publish",
"operationId": 2,
"options": {
"captureId": "capture-screen-1",
"width": 1920,
"height": 1080,
"codec": "h264",
"hardwareEncoding": true,
"zeroCopyRequired": true,
"maxFramerate": 60
}
},
{
"type": "screen.publish",
"operationId": 3,
"options": {
"captureId": "capture-screen-1",
"width": 1920,
"height": 1080,
"codec": "h265",
"hardwareEncoding": true,
"zeroCopyRequired": true,
"maxFramerate": 60
}
}
],
"expected": {
"nextOperationId": 4,
"screenStatus": "published",
"screenCaptureId": "capture-screen-1",
"screenCodec": "h265",
"screenHardwareEncoding": true,
"screenZeroCopyRequired": true,
"lastFailureCode": null
}
}
@@ -0,0 +1,61 @@
{
"name": "audio input selection and hotplug republish microphone when connected",
"events": [
{"type": "connection.connectRequested", "options": {"url": "wss://voice", "token": "token"}},
{"type": "connection.connectSucceeded", "operationId": 1},
{"type": "microphone.publishRequested", "options": {"deviceId": "mic-a"}},
{"type": "microphone.publishSucceeded", "operationId": 2},
{"type": "devices.selectAudioInputRequested", "deviceId": "mic-b"},
{"type": "command.succeeded", "operationId": 3, "commandType": "devices.selectAudioInput"},
{"type": "microphone.publishSucceeded", "operationId": 4},
{
"type": "devices.changed",
"operationId": null,
"reason": "hotplug",
"devices": {
"audioInputs": [
{"deviceId": "mic-b", "label": "Desk mic", "isDefault": false},
{"deviceId": "mic-c", "label": "Headset mic", "isDefault": true}
],
"audioOutputs": [],
"cameras": [],
"selectedAudioInputId": "mic-c",
"selectedAudioOutputId": null,
"selectedCameraId": null
}
},
{"type": "microphone.publishSucceeded", "operationId": 5}
],
"expectedCommands": [
{"type": "connection.connect", "operationId": 1, "options": {"url": "wss://voice", "token": "token"}},
{"type": "microphone.publish", "operationId": 2, "options": {"deviceId": "mic-a"}},
{"type": "devices.selectAudioInput", "operationId": 3, "deviceId": "mic-b"},
{"type": "microphone.publish", "operationId": 4, "options": {"deviceId": "mic-b"}},
{"type": "microphone.publish", "operationId": 5, "options": {"deviceId": "mic-c"}}
],
"checkpoints": [
{
"afterEvent": 5,
"microphoneStatus": "publishing",
"desiredDeviceId": "mic-b",
"selectedAudioInputId": "mic-b"
},
{
"afterEvent": 8,
"microphoneStatus": "publishing",
"desiredDeviceId": "mic-c",
"selectedAudioInputId": "mic-c"
}
],
"expectedSnapshot": {
"nextOperationId": 6,
"connectionStatus": "connected",
"microphoneStatus": "published",
"microphoneEnabled": true,
"localSpeakingOverride": null,
"publishedDeviceId": "mic-c",
"desiredDeviceId": "mic-c",
"selectedAudioInputId": "mic-c",
"lastFailureCode": null
}
}
@@ -0,0 +1,77 @@
{
"name": "device_inventory",
"events": [
{"type": "devices.enumerateRequested"},
{
"type": "devices.changed",
"operationId": 1,
"reason": "initial",
"devices": {
"audioInputs": [
{"deviceId": "mic-1", "label": "Built-in Microphone", "isDefault": true, "role": "default"},
{
"deviceId": "mic-2",
"label": "USB Microphone",
"isDefault": false,
"role": "communications",
"endpointLabel": "USB Audio",
"isDefaultRoute": true
}
],
"audioOutputs": [{"deviceId": "speaker-1", "label": "Built-in Speakers", "isDefault": true, "role": "default"}],
"cameras": [{"deviceId": "camera-1", "label": "FaceTime Camera", "description": "Integrated camera"}],
"selectedAudioInputId": "mic-1",
"selectedAudioOutputId": "speaker-1",
"selectedCameraId": "camera-1"
}
},
{"type": "devices.selectAudioInputRequested", "deviceId": "mic-2"},
{"type": "command.succeeded", "operationId": 2, "commandType": "devices.selectAudioInput"},
{
"type": "devices.changed",
"operationId": null,
"reason": "hotplug",
"devices": {
"audioInputs": [
{
"deviceId": "mic-2",
"label": "USB Microphone",
"isDefault": true,
"role": "communications",
"endpointLabel": "USB Audio",
"isDefaultRoute": true
}
],
"audioOutputs": [
{"deviceId": "speaker-1", "label": "Built-in Speakers", "isDefault": true, "role": "default"},
{"deviceId": "headset-1", "label": "USB Headset", "isDefault": false, "role": "endpoint"}
],
"cameras": [
{"deviceId": "camera-1", "label": "FaceTime Camera", "description": "Integrated camera"},
{"deviceId": "camera-2", "label": "Capture Card", "description": "USB capture"}
],
"selectedAudioInputId": "mic-2",
"selectedAudioOutputId": "speaker-1",
"selectedCameraId": "camera-1"
}
}
],
"expectedCommands": [
[{"type": "devices.enumerate", "operationId": 1}],
[],
[{"type": "devices.selectAudioInput", "operationId": 2, "deviceId": "mic-2"}],
[],
[]
],
"expected": {
"nextOperationId": 3,
"deviceOperationId": null,
"failureCode": null,
"selectedAudioInputId": "mic-2",
"selectedAudioInputLabel": "USB Microphone",
"selectedAudioInputRole": "communications",
"audioOutputCount": 2,
"cameraCount": 2,
"operationStatuses": {"1": "succeeded", "2": "succeeded"}
}
}
@@ -0,0 +1,51 @@
{
"name": "diagnostics_lifecycle",
"events": [
{
"type": "diagnostics.logRequested",
"entry": {
"id": "diag-1",
"atMs": 1000,
"level": "info",
"code": "voice.session.started",
"message": "Voice session started"
}
},
{"type": "command.succeeded", "operationId": 1, "commandType": "diagnostics.log"},
{
"type": "diagnostics.logged",
"operationId": null,
"entry": {
"id": "diag-2",
"atMs": 1010,
"level": "warn",
"code": "voice.stats.delayed",
"message": "Stats collection was delayed",
"detail": {"delayMs": 250}
}
}
],
"expectedCommands": [
[
{
"type": "diagnostics.log",
"operationId": 1,
"entry": {
"id": "diag-1",
"atMs": 1000,
"level": "info",
"code": "voice.session.started",
"message": "Voice session started"
}
}
],
[],
[]
],
"expected": {
"nextOperationId": 2,
"entryIds": ["diag-1", "diag-2"],
"lastCode": "voice.stats.delayed",
"operationStatuses": {"1": "succeeded"}
}
}
@@ -0,0 +1,34 @@
{
"name": "e2ee_lifecycle",
"events": [
{"type": "e2ee.setEnabledRequested", "enabled": true, "keyId": "key-a"},
{"type": "e2ee.setEnabledRequested", "enabled": false},
{"type": "e2ee.enabled", "operationId": 1, "keyId": "key-a"},
{"type": "e2ee.disabled", "operationId": 2},
{"type": "e2ee.enabled", "operationId": null, "keyId": "key-b"},
{
"type": "e2ee.failed",
"operationId": null,
"error": {
"code": "implementationError",
"message": "Remote participant key material could not be applied",
"capability": "e2ee"
}
}
],
"expectedCommands": [
[{"type": "e2ee.setEnabled", "operationId": 1, "enabled": true, "keyId": "key-a"}],
[{"type": "e2ee.setEnabled", "operationId": 2, "enabled": false}],
[],
[],
[],
[]
],
"expected": {
"nextOperationId": 3,
"status": "failed",
"keyId": "key-b",
"operationId": null,
"failureCode": "implementationError"
}
}
@@ -0,0 +1,97 @@
{
"name": "gateway_connection",
"description": "Join writes gateway voice state, observes gateway state, then connects the media session.",
"steps": [
{
"name": "request gateway join",
"event": {
"type": "gateway.voiceStateWriteRequested",
"options": {
"guildId": "guild-1",
"channelId": "channel-1",
"selfMute": false,
"selfDeaf": false
}
},
"commands": [
{
"type": "gateway.voiceState.write",
"operationId": 1,
"options": {
"guildId": "guild-1",
"channelId": "channel-1",
"selfMute": false,
"selfDeaf": false
}
}
]
},
{
"name": "gateway join write succeeds",
"event": {
"type": "gateway.voiceStateWriteSucceeded",
"operationId": 1
},
"commands": []
},
{
"name": "gateway voice state arrives",
"event": {
"type": "gateway.voiceStateUpdated",
"voiceState": {
"guildId": "guild-1",
"channelId": "channel-1",
"userId": "user-1",
"sessionId": "session-1",
"selfMute": false,
"selfDeaf": false,
"selfVideo": false,
"selfStream": false,
"suppress": false,
"requestToSpeakTimestamp": null
}
},
"commands": []
},
{
"name": "request media connection",
"event": {
"type": "connection.connectRequested",
"options": {
"url": "wss://voice-one.example.test",
"token": "token-1"
}
},
"commands": [
{
"type": "connection.connect",
"operationId": 2,
"options": {
"url": "wss://voice-one.example.test",
"token": "token-1"
}
}
]
},
{
"name": "media connection succeeds",
"event": {
"type": "connection.connectSucceeded",
"operationId": 2
},
"commands": []
}
],
"expected": {
"nextOperationId": 3,
"connectionStatus": "connected",
"activeUrl": "wss://voice-one.example.test",
"desiredUrl": "wss://voice-one.example.test",
"gatewayChannelId": "channel-1",
"lifecycleTearingDown": false,
"operationStatuses": {
"1": "succeeded",
"2": "succeeded"
}
}
}
@@ -0,0 +1,81 @@
{
"name": "inbound video frame stats projection",
"events": [
{
"type": "inboundVideo.trackSubscribed",
"track": {
"participantSid": "PA_alice",
"participantIdentity": "alice",
"trackSid": "TR_screen",
"source": "screen"
}
},
{
"type": "inboundVideo.frameReceived",
"frame": {
"participantSid": "PA_alice",
"participantIdentity": "alice",
"trackSid": "TR_screen",
"width": 640,
"height": 360,
"timestampUs": 1000,
"byteLength": 345600
}
},
{
"type": "inboundVideo.frameStats",
"stats": {
"participantSid": "PA_alice",
"participantIdentity": "alice",
"trackSid": "TR_screen",
"width": 1280,
"height": 720,
"frameCount": 31,
"lastFrameTimestampUs": 1033000,
"lastFrameByteLength": 1382400
}
},
{
"type": "inboundVideo.frameStats",
"stats": {
"participantSid": "PA_alice",
"participantIdentity": "alice",
"trackSid": "TR_screen",
"width": 1280,
"height": 720,
"frameCount": 61,
"lastFrameTimestampUs": 2033000,
"lastFrameByteLength": 1382400
}
},
{
"type": "inboundVideo.frameStats",
"stats": {
"participantSid": "PA_bob",
"trackSid": "TR_unknown",
"width": 320,
"height": 180,
"frameCount": 5,
"lastFrameTimestampUs": 3000000,
"lastFrameByteLength": 86400
}
}
],
"expected": {
"inboundVideoTracks": [
{
"participantSid": "PA_alice",
"participantIdentity": "alice",
"trackSid": "TR_screen",
"source": "screen",
"width": 1280,
"height": 720,
"frameCount": 61,
"lastFrameTimestampUs": 2033000,
"lastFrameByteLength": 1382400
}
],
"droppedFrameCount": 1,
"commands": []
}
}
@@ -0,0 +1,66 @@
{
"name": "inbound native video frame projection",
"events": [
{
"type": "inboundVideo.trackSubscribed",
"track": {
"participantSid": "PA_alice",
"participantIdentity": "alice",
"trackSid": "TR_screen",
"source": "screen"
}
},
{
"type": "inboundVideo.frameReceived",
"frame": {
"participantSid": "PA_alice",
"participantIdentity": "alice",
"trackSid": "TR_screen",
"width": 640,
"height": 360,
"timestampUs": 1000,
"byteLength": 345600
}
},
{
"type": "inboundVideo.frameReceived",
"frame": {
"participantSid": "PA_alice",
"participantIdentity": "alice",
"trackSid": "TR_screen",
"width": 1280,
"height": 720,
"timestampUs": 2000,
"byteLength": 1382400
}
},
{
"type": "inboundVideo.frameReceived",
"frame": {
"participantSid": "PA_alice",
"trackSid": "TR_unknown",
"width": 320,
"height": 180,
"timestampUs": 3000,
"byteLength": 86400
}
}
],
"expected": {
"inboundVideoTracks": [
{
"participantSid": "PA_alice",
"participantIdentity": "alice",
"trackSid": "TR_screen",
"source": "screen",
"width": 1280,
"height": 720,
"frameCount": 2,
"lastFrameTimestampUs": 2000,
"lastFrameByteLength": 1382400
}
],
"droppedFrameCount": 1,
"commands": []
}
}
@@ -0,0 +1,82 @@
{
"name": "local media mute unmute deafen undeafen and microphone publish lifecycle",
"events": [
{"type": "connection.connectRequested", "options": {"url": "wss://voice", "token": "token"}},
{"type": "connection.connectSucceeded", "operationId": 1},
{"type": "microphone.publishRequested", "options": {"deviceId": "mic-a"}},
{"type": "microphone.publishSucceeded", "operationId": 2},
{"type": "connection.reconnectRequested"},
{"type": "connection.disconnectSucceeded", "operationId": 3},
{"type": "connection.connectSucceeded", "operationId": 4},
{"type": "microphone.publishSucceeded", "operationId": 5},
{"type": "localAudio.muteRequested", "muted": true},
{"type": "microphone.setEnabledSucceeded", "operationId": 6},
{"type": "localAudio.muteRequested", "muted": false},
{"type": "microphone.setEnabledSucceeded", "operationId": 7},
{"type": "localAudio.deafenRequested", "deafened": true},
{"type": "microphone.setEnabledSucceeded", "operationId": 8},
{"type": "localAudio.deafenRequested", "deafened": false},
{"type": "microphone.setEnabledSucceeded", "operationId": 9},
{"type": "microphone.unpublishRequested"},
{"type": "microphone.unpublishSucceeded", "operationId": 10}
],
"expectedCommands": [
{"type": "connection.connect", "operationId": 1, "options": {"url": "wss://voice", "token": "token"}},
{"type": "microphone.publish", "operationId": 2, "options": {"deviceId": "mic-a"}},
{"type": "connection.disconnect", "operationId": 3, "reason": "network"},
{"type": "connection.connect", "operationId": 4, "options": {"url": "wss://voice", "token": "token"}},
{"type": "microphone.publish", "operationId": 5, "options": {"deviceId": "mic-a"}},
{"type": "microphone.setEnabled", "operationId": 6, "enabled": false},
{"type": "microphone.setEnabled", "operationId": 7, "enabled": true},
{"type": "microphone.setEnabled", "operationId": 8, "enabled": false},
{"type": "microphone.setEnabled", "operationId": 9, "enabled": true},
{"type": "microphone.unpublish", "operationId": 10}
],
"checkpoints": [
{
"afterEvent": 5,
"connectionStatus": "reconnecting",
"microphoneStatus": "idle",
"desiredDeviceId": "mic-a",
"publishedDeviceId": null
},
{
"afterEvent": 9,
"microphoneStatus": "published",
"microphoneEnabled": false,
"localSpeakingOverride": false,
"locallyMuted": true,
"locallyDeafened": false
},
{
"afterEvent": 13,
"microphoneStatus": "published",
"microphoneEnabled": false,
"localSpeakingOverride": false,
"locallyMuted": true,
"locallyDeafened": true,
"shouldUnmuteOnUndeafen": true
},
{
"afterEvent": 15,
"microphoneStatus": "published",
"microphoneEnabled": true,
"localSpeakingOverride": null,
"locallyMuted": false,
"locallyDeafened": false
}
],
"expectedSnapshot": {
"nextOperationId": 11,
"connectionStatus": "connected",
"microphoneStatus": "idle",
"microphoneEnabled": true,
"localSpeakingOverride": false,
"locallyMuted": false,
"locallyDeafened": false,
"mutedByPermission": false,
"publishedDeviceId": null,
"desiredDeviceId": null,
"lastFailureCode": null
}
}
@@ -0,0 +1,58 @@
{
"name": "microphone device failure mutes locally then device hotswap and unmute recover",
"events": [
{"type": "connection.connectRequested", "options": {"url": "wss://voice", "token": "token"}},
{"type": "connection.connectSucceeded", "operationId": 1},
{"type": "microphone.publishRequested", "options": {"deviceId": "mic-a"}},
{
"type": "microphone.publishFailed",
"operationId": 2,
"error": {"code": "deviceUnavailable", "message": "device unavailable", "capability": "microphoneCapture"}
},
{"type": "devices.selectAudioInputRequested", "deviceId": "mic-b"},
{"type": "command.succeeded", "operationId": 3, "commandType": "devices.selectAudioInput"},
{"type": "microphone.publishSucceeded", "operationId": 4},
{"type": "microphone.setEnabledSucceeded", "operationId": 5},
{"type": "localAudio.muteRequested", "muted": false},
{"type": "microphone.setEnabledSucceeded", "operationId": 6}
],
"expectedCommands": [
{"type": "connection.connect", "operationId": 1, "options": {"url": "wss://voice", "token": "token"}},
{"type": "microphone.publish", "operationId": 2, "options": {"deviceId": "mic-a"}},
{"type": "devices.selectAudioInput", "operationId": 3, "deviceId": "mic-b"},
{"type": "microphone.publish", "operationId": 4, "options": {"deviceId": "mic-b"}},
{"type": "microphone.setEnabled", "operationId": 5, "enabled": false},
{"type": "microphone.setEnabled", "operationId": 6, "enabled": true}
],
"checkpoints": [
{
"afterEvent": 4,
"microphoneStatus": "failed",
"microphoneEnabled": false,
"locallyMuted": true,
"preferredLocallyMuted": true,
"lastFailureCode": "deviceUnavailable"
},
{
"afterEvent": 7,
"microphoneStatus": "published",
"microphoneEnabled": false,
"localSpeakingOverride": false,
"publishedDeviceId": "mic-b",
"desiredDeviceId": "mic-b"
}
],
"expectedSnapshot": {
"nextOperationId": 7,
"connectionStatus": "connected",
"microphoneStatus": "published",
"microphoneEnabled": true,
"localSpeakingOverride": null,
"locallyMuted": false,
"preferredLocallyMuted": false,
"publishedDeviceId": "mic-b",
"desiredDeviceId": "mic-b",
"selectedAudioInputId": "mic-b",
"lastFailureCode": "deviceUnavailable"
}
}
@@ -0,0 +1,40 @@
{
"name": "native_frame_sink",
"events": [
{
"type": "nativeFrameSink.attachRequested",
"options": {
"sinkId": "sink-screen-1",
"captureId": "capture-native-1",
"trackSid": "TR_screen_1",
"zeroCopyRequired": true
}
},
{
"type": "nativeFrameSink.detachRequested",
"sinkId": "sink-screen-1"
}
],
"expectedCommands": [
{
"type": "nativeFrameSink.attach",
"operationId": 1,
"options": {
"sinkId": "sink-screen-1",
"captureId": "capture-native-1",
"trackSid": "TR_screen_1",
"zeroCopyRequired": true
}
},
{
"type": "nativeFrameSink.detach",
"operationId": 2,
"sinkId": "sink-screen-1"
}
],
"expected": {
"nextOperationId": 3,
"nativeFrameSinkCount": 0,
"lastFailureCode": null
}
}
@@ -0,0 +1,118 @@
{
"name": "native_zero_copy_screen",
"events": [
{
"type": "nativeCapture.startRequested",
"options": {
"captureId": "capture-native-1",
"source": {
"id": "display-1",
"kind": "screen",
"title": "Display 1",
"width": 2560,
"height": 1440,
"displayId": "display-1"
},
"width": 2560,
"height": 1440,
"frameRate": 60,
"includeCursor": true,
"includeAudio": false,
"zeroCopyRequired": true
}
},
{
"type": "nativeCapture.started",
"operationId": 1,
"captureId": "capture-native-1"
},
{
"type": "nativeCapture.updateRequested",
"options": {
"captureId": "capture-native-1",
"source": {
"id": "display-1",
"kind": "screen",
"title": "Display 1",
"width": 2560,
"height": 1440,
"displayId": "display-1"
},
"width": 1920,
"height": 1080,
"frameRate": 30,
"includeCursor": false,
"includeAudio": false,
"zeroCopyRequired": true
}
},
{
"type": "nativeCapture.started",
"operationId": 2,
"captureId": "capture-native-1"
},
{
"type": "nativeCapture.stopRequested",
"captureId": "capture-native-1"
},
{
"type": "nativeCapture.stopped",
"operationId": 3,
"captureId": "capture-native-1"
}
],
"expectedCommands": [
{
"type": "nativeCapture.start",
"operationId": 1,
"options": {
"captureId": "capture-native-1",
"source": {
"id": "display-1",
"kind": "screen",
"title": "Display 1",
"width": 2560,
"height": 1440,
"displayId": "display-1"
},
"width": 2560,
"height": 1440,
"frameRate": 60,
"includeCursor": true,
"includeAudio": false,
"zeroCopyRequired": true
}
},
{
"type": "nativeCapture.update",
"operationId": 2,
"options": {
"captureId": "capture-native-1",
"source": {
"id": "display-1",
"kind": "screen",
"title": "Display 1",
"width": 2560,
"height": 1440,
"displayId": "display-1"
},
"width": 1920,
"height": 1080,
"frameRate": 30,
"includeCursor": false,
"includeAudio": false,
"zeroCopyRequired": true
}
},
{
"type": "nativeCapture.stop",
"operationId": 3,
"captureId": "capture-native-1"
}
],
"expected": {
"nextOperationId": 4,
"nativeCaptureCount": 0,
"lastFailureCode": null
}
}
@@ -0,0 +1,46 @@
{
"name": "participants and tracks projection",
"events": [
{
"type": "room.participantJoined",
"participant": {"sid": "PA_alice", "identity": "alice", "name": "Alice"}
},
{
"type": "room.trackPublished",
"track": {
"participantIdentity": "alice",
"participantSid": "PA_alice",
"trackSid": "TR_camera",
"trackName": "camera",
"kind": "video",
"source": "camera",
"muted": false
}
},
{"type": "room.trackMuted", "trackSid": "TR_camera"},
{
"type": "room.trackPublished",
"track": {
"participantIdentity": "alice",
"participantSid": "PA_alice",
"trackSid": "TR_mic",
"trackName": "microphone",
"kind": "audio",
"source": "microphone",
"muted": false
}
},
{"type": "room.trackUnpublished", "trackSid": "TR_mic"},
{
"type": "room.participantJoined",
"participant": {"sid": "PA_bob", "identity": "bob", "name": "Bob"}
},
{"type": "room.participantLeft", "participantIdentity": "alice", "participantSid": "PA_alice"}
],
"expected": {
"participants": [{"sid": "PA_bob", "identity": "bob", "name": "Bob"}],
"tracks": [],
"inboundVideoTracks": [],
"commands": []
}
}
@@ -0,0 +1,61 @@
{
"name": "microphone permission denial mutes and grant restores requested unmute",
"events": [
{"type": "connection.connectRequested", "options": {"url": "wss://voice", "token": "token"}},
{"type": "connection.connectSucceeded", "operationId": 1},
{"type": "microphone.publishRequested", "options": {"deviceId": "mic-a"}},
{"type": "microphone.publishSucceeded", "operationId": 2},
{
"type": "permissions.result",
"operationId": null,
"result": {"name": "microphone", "status": "denied", "canPrompt": false}
},
{"type": "microphone.setEnabledSucceeded", "operationId": 3},
{"type": "localAudio.muteRequested", "muted": false},
{
"type": "permissions.result",
"operationId": null,
"result": {"name": "microphone", "status": "granted", "canPrompt": false}
},
{"type": "microphone.setEnabledSucceeded", "operationId": 4}
],
"expectedCommands": [
{"type": "connection.connect", "operationId": 1, "options": {"url": "wss://voice", "token": "token"}},
{"type": "microphone.publish", "operationId": 2, "options": {"deviceId": "mic-a"}},
{"type": "microphone.setEnabled", "operationId": 3, "enabled": false},
{"type": "microphone.setEnabled", "operationId": 4, "enabled": true}
],
"checkpoints": [
{
"afterEvent": 5,
"microphoneStatus": "published",
"microphoneEnabled": false,
"localSpeakingOverride": false,
"locallyMuted": true,
"mutedByPermission": true,
"permissionStatus": "denied"
},
{
"afterEvent": 7,
"microphoneStatus": "published",
"microphoneEnabled": false,
"locallyMuted": true,
"preferredLocallyMuted": false,
"mutedByPermission": true
}
],
"expectedSnapshot": {
"nextOperationId": 5,
"connectionStatus": "connected",
"microphoneStatus": "published",
"microphoneEnabled": true,
"localSpeakingOverride": null,
"locallyMuted": false,
"preferredLocallyMuted": false,
"mutedByPermission": false,
"permissionStatus": "granted",
"publishedDeviceId": "mic-a",
"desiredDeviceId": "mic-a",
"lastFailureCode": null
}
}
@@ -0,0 +1,62 @@
{
"name": "push to talk and push to mute gate microphone enabled state",
"events": [
{"type": "connection.connectRequested", "options": {"url": "wss://voice", "token": "token"}},
{"type": "connection.connectSucceeded", "operationId": 1},
{"type": "microphone.publishRequested", "options": {"deviceId": "mic-a"}},
{"type": "microphone.publishSucceeded", "operationId": 2},
{"type": "audioControls.changed", "controls": {"mode": "pushToTalk", "pushToTalkActive": false}},
{"type": "microphone.setEnabledSucceeded", "operationId": 3},
{"type": "audioControls.changed", "controls": {"pushToTalkActive": true}},
{"type": "microphone.setEnabledSucceeded", "operationId": 4},
{
"type": "audioControls.changed",
"controls": {"mode": "pushToMute", "pushToTalkActive": false, "pushToMuteActive": true}
},
{"type": "microphone.setEnabledSucceeded", "operationId": 5},
{"type": "audioControls.changed", "controls": {"pushToMuteActive": false}},
{"type": "microphone.setEnabledSucceeded", "operationId": 6}
],
"expectedCommands": [
{"type": "connection.connect", "operationId": 1, "options": {"url": "wss://voice", "token": "token"}},
{"type": "microphone.publish", "operationId": 2, "options": {"deviceId": "mic-a"}},
{"type": "microphone.setEnabled", "operationId": 3, "enabled": false},
{"type": "microphone.setEnabled", "operationId": 4, "enabled": true},
{"type": "microphone.setEnabled", "operationId": 5, "enabled": false},
{"type": "microphone.setEnabled", "operationId": 6, "enabled": true}
],
"checkpoints": [
{
"afterEvent": 5,
"microphoneStatus": "published",
"microphoneEnabled": false,
"localSpeakingOverride": false,
"audioMode": "pushToTalk"
},
{
"afterEvent": 7,
"microphoneStatus": "published",
"microphoneEnabled": true,
"localSpeakingOverride": true,
"audioMode": "pushToTalk"
},
{
"afterEvent": 9,
"microphoneStatus": "published",
"microphoneEnabled": false,
"localSpeakingOverride": false,
"audioMode": "pushToMute"
}
],
"expectedSnapshot": {
"nextOperationId": 7,
"connectionStatus": "connected",
"microphoneStatus": "published",
"microphoneEnabled": true,
"localSpeakingOverride": null,
"audioMode": "pushToMute",
"publishedDeviceId": "mic-a",
"desiredDeviceId": "mic-a",
"lastFailureCode": null
}
}
@@ -0,0 +1,160 @@
{
"name": "reconnect_channel_move",
"description": "A channel move writes the new gateway voice state, disconnects the old media session, then reconnects to the new room.",
"steps": [
{
"name": "connect first room",
"event": {
"type": "connection.connectRequested",
"options": {
"url": "wss://voice-one.example.test",
"token": "token-1"
}
},
"commands": [
{
"type": "connection.connect",
"operationId": 1,
"options": {
"url": "wss://voice-one.example.test",
"token": "token-1"
}
}
]
},
{
"name": "first room connected",
"event": {
"type": "connection.connectSucceeded",
"operationId": 1
},
"commands": []
},
{
"name": "old gateway channel observed",
"event": {
"type": "gateway.voiceStateUpdated",
"voiceState": {
"guildId": "guild-1",
"channelId": "channel-1",
"userId": "user-1",
"sessionId": "session-1",
"selfMute": false,
"selfDeaf": false,
"selfVideo": false,
"selfStream": false,
"suppress": false,
"requestToSpeakTimestamp": null
}
},
"commands": []
},
{
"name": "write moved gateway channel",
"event": {
"type": "gateway.voiceStateWriteRequested",
"options": {
"guildId": "guild-1",
"channelId": "channel-2",
"selfMute": false,
"selfDeaf": false
}
},
"commands": [
{
"type": "gateway.voiceState.write",
"operationId": 2,
"options": {
"guildId": "guild-1",
"channelId": "channel-2",
"selfMute": false,
"selfDeaf": false
}
}
]
},
{
"name": "moved gateway write succeeds",
"event": {
"type": "gateway.voiceStateWriteSucceeded",
"operationId": 2
},
"commands": []
},
{
"name": "new gateway channel observed",
"event": {
"type": "gateway.voiceStateUpdated",
"voiceState": {
"guildId": "guild-1",
"channelId": "channel-2",
"userId": "user-1",
"sessionId": "session-2",
"selfMute": false,
"selfDeaf": false,
"selfVideo": false,
"selfStream": false,
"suppress": false,
"requestToSpeakTimestamp": null
}
},
"commands": []
},
{
"name": "request second media connection",
"event": {
"type": "connection.connectRequested",
"options": {
"url": "wss://voice-two.example.test",
"token": "token-2"
}
},
"commands": [
{
"type": "connection.disconnect",
"operationId": 3,
"reason": "replaced"
}
]
},
{
"name": "old media connection disconnects",
"event": {
"type": "connection.disconnectSucceeded",
"operationId": 3
},
"commands": [
{
"type": "connection.connect",
"operationId": 4,
"options": {
"url": "wss://voice-two.example.test",
"token": "token-2"
}
}
]
},
{
"name": "second media connection succeeds",
"event": {
"type": "connection.connectSucceeded",
"operationId": 4
},
"commands": []
}
],
"expected": {
"nextOperationId": 5,
"connectionStatus": "connected",
"activeUrl": "wss://voice-two.example.test",
"desiredUrl": "wss://voice-two.example.test",
"gatewayChannelId": "channel-2",
"lifecycleTearingDown": false,
"operationStatuses": {
"1": "succeeded",
"2": "succeeded",
"3": "succeeded",
"4": "succeeded"
}
}
}
@@ -0,0 +1,48 @@
{
"name": "resume_preserves_media",
"steps": [
{
"name": "external establishment",
"event": {"type": "connection.externallyEstablished", "options": {"url": "wss://voice.fixture", "token": ""}},
"commands": []
},
{
"name": "camera publish requested",
"event": {"type": "camera.publishRequested", "options": {"deviceId": "camera-1"}},
"commands": [{"type": "camera.publish", "operationId": 1, "options": {"deviceId": "camera-1"}}]
},
{
"name": "camera publish succeeded",
"event": {"type": "camera.publishSucceeded", "operationId": 1},
"commands": []
},
{
"name": "resume re-establishes the same endpoint without replaying media",
"event": {"type": "connection.externallyEstablished", "options": {"url": "wss://voice.fixture", "token": ""}},
"commands": []
},
{
"name": "camera unpublish after resume plans a real command",
"event": {"type": "camera.unpublishRequested"},
"commands": [{"type": "camera.unpublish", "operationId": 2}]
},
{
"name": "camera unpublish succeeded",
"event": {"type": "camera.unpublishSucceeded", "operationId": 2},
"commands": []
},
{
"name": "true remote disconnect resets the session",
"event": {"type": "connection.remoteDisconnected", "reason": "network"},
"commands": []
}
],
"expected": {
"nextOperationId": 3,
"connectionStatus": "idle",
"activeUrl": null,
"desiredUrl": null,
"gatewayChannelId": null,
"lifecycleTearingDown": false
}
}
@@ -0,0 +1,53 @@
{
"name": "screen_audio_inclusion_toggle",
"events": [
{
"type": "connection.connectRequested",
"options": {"url": "wss://voice.example.test", "token": "token-screen-audio"}
},
{"type": "connection.connectSucceeded", "operationId": 1},
{
"type": "screen.publishRequested",
"options": {"captureId": "capture-1", "width": 1920, "height": 1080, "codec": "h264", "maxFramerate": 30}
},
{"type": "screen.publishSucceeded", "operationId": 2},
{"type": "screenAudio.publishRequested", "options": {"sampleRate": 48000, "numChannels": 2}},
{"type": "screenAudio.publishSucceeded", "operationId": 3},
{
"type": "screen.updateEncodingRequested",
"options": {"captureId": "capture-1", "width": 1920, "height": 1080, "frameRate": 60}
},
{"type": "screen.updateEncodingSucceeded", "operationId": 4},
{"type": "screenAudio.unpublishRequested"},
{"type": "screenAudio.unpublishSucceeded", "operationId": 5},
{"type": "screenAudio.publishRequested", "options": {"sampleRate": 48000, "numChannels": 2}},
{"type": "screenAudio.publishSucceeded", "operationId": 6}
],
"expectedCommands": [
{
"type": "connection.connect",
"operationId": 1,
"options": {"url": "wss://voice.example.test", "token": "token-screen-audio"}
},
{
"type": "screen.publish",
"operationId": 2,
"options": {"captureId": "capture-1", "width": 1920, "height": 1080, "codec": "h264", "maxFramerate": 30}
},
{"type": "screenAudio.publish", "operationId": 3, "options": {"sampleRate": 48000, "numChannels": 2}},
{
"type": "screen.updateEncoding",
"operationId": 4,
"options": {"captureId": "capture-1", "width": 1920, "height": 1080, "frameRate": 60}
},
{"type": "screenAudio.unpublish", "operationId": 5},
{"type": "screenAudio.publish", "operationId": 6, "options": {"sampleRate": 48000, "numChannels": 2}}
],
"expected": {
"nextOperationId": 7,
"screenStatus": "published",
"screenCaptureId": "capture-1",
"screenAudioStatus": "published",
"lastFailureCode": null
}
}
@@ -0,0 +1,58 @@
{
"name": "screen_audio_routing",
"events": [
{
"type": "connection.connectRequested",
"options": {
"url": "wss://voice.example.test",
"token": "token-audio"
}
},
{
"type": "connection.connectSucceeded",
"operationId": 1
},
{
"type": "screenAudio.publishRequested",
"options": {
"sampleRate": 48000,
"numChannels": 2,
"route": "native",
"captureId": "capture-screen-1",
"tapId": "tap-screen-1"
}
},
{
"type": "screenAudio.publishSucceeded",
"operationId": 2
}
],
"expectedCommands": [
{
"type": "connection.connect",
"operationId": 1,
"options": {
"url": "wss://voice.example.test",
"token": "token-audio"
}
},
{
"type": "screenAudio.publish",
"operationId": 2,
"options": {
"sampleRate": 48000,
"numChannels": 2,
"route": "native",
"captureId": "capture-screen-1",
"tapId": "tap-screen-1"
}
}
],
"expected": {
"nextOperationId": 3,
"screenAudioStatus": "published",
"screenAudioRoute": "native",
"screenAudioTapId": "tap-screen-1",
"lastFailureCode": null
}
}
@@ -0,0 +1,95 @@
{
"name": "screen_share_start_update_stop",
"events": [
{
"type": "connection.connectRequested",
"options": {
"url": "wss://voice.example.test",
"token": "token-screen"
}
},
{
"type": "connection.connectSucceeded",
"operationId": 1
},
{
"type": "screen.publishRequested",
"options": {
"captureId": "capture-screen-1",
"width": 2560,
"height": 1440,
"codec": "h264",
"maxFramerate": 60,
"maxBitrateBps": 8000000
}
},
{
"type": "screen.publishSucceeded",
"operationId": 2
},
{
"type": "screen.updateEncodingRequested",
"options": {
"captureId": "capture-screen-1",
"width": 1280,
"height": 720,
"frameRate": 30,
"maxBitrateBps": 3000000
}
},
{
"type": "screen.updateEncodingSucceeded",
"operationId": 3
},
{
"type": "screen.unpublishRequested"
},
{
"type": "screen.unpublishSucceeded",
"operationId": 4
}
],
"expectedCommands": [
{
"type": "connection.connect",
"operationId": 1,
"options": {
"url": "wss://voice.example.test",
"token": "token-screen"
}
},
{
"type": "screen.publish",
"operationId": 2,
"options": {
"captureId": "capture-screen-1",
"width": 2560,
"height": 1440,
"codec": "h264",
"maxFramerate": 60,
"maxBitrateBps": 8000000
}
},
{
"type": "screen.updateEncoding",
"operationId": 3,
"options": {
"captureId": "capture-screen-1",
"width": 1280,
"height": 720,
"frameRate": 30,
"maxBitrateBps": 3000000
}
},
{
"type": "screen.unpublish",
"operationId": 4
}
],
"expected": {
"nextOperationId": 5,
"screenStatus": "idle",
"screenCaptureId": null,
"lastFailureCode": null
}
}
@@ -0,0 +1,84 @@
{
"name": "stale_connection",
"description": "A stale connect completion is rejected after a newer connect operation supersedes it.",
"steps": [
{
"name": "request first connection",
"event": {
"type": "connection.connectRequested",
"options": {
"url": "wss://voice-one.example.test",
"token": "token-1"
}
},
"commands": [
{
"type": "connection.connect",
"operationId": 1,
"options": {
"url": "wss://voice-one.example.test",
"token": "token-1"
}
}
]
},
{
"name": "request replacement connection",
"event": {
"type": "connection.connectRequested",
"options": {
"url": "wss://voice-two.example.test",
"token": "token-2"
}
},
"commands": [
{
"type": "connection.connect",
"operationId": 2,
"options": {
"url": "wss://voice-two.example.test",
"token": "token-2"
}
}
]
},
{
"name": "stale first completion arrives",
"event": {
"type": "connection.connectSucceeded",
"operationId": 1
},
"commands": []
},
{
"name": "runtime rejects stale first completion",
"event": {
"type": "command.staleCompletionRejected",
"operationId": 1,
"commandType": "connection.connect",
"resourceKey": "connection"
},
"commands": []
},
{
"name": "replacement completion succeeds",
"event": {
"type": "connection.connectSucceeded",
"operationId": 2
},
"commands": []
}
],
"expected": {
"nextOperationId": 3,
"connectionStatus": "connected",
"activeUrl": "wss://voice-two.example.test",
"desiredUrl": "wss://voice-two.example.test",
"gatewayChannelId": null,
"lifecycleTearingDown": false,
"operationStatuses": {
"1": "stale",
"2": "succeeded"
}
}
}
@@ -0,0 +1,99 @@
{
"name": "stats_lifecycle",
"events": [
{
"type": "connection.connectRequested",
"options": {
"url": "wss://voice.example.test",
"token": "token-1"
}
},
{"type": "connection.connectSucceeded", "operationId": 1},
{"type": "stats.collectRequested"},
{
"type": "stats.collected",
"operationId": 2,
"stats": {
"rttMs": 24,
"outbound": [
{
"trackSid": "mic-track",
"source": "microphone",
"kind": "audio",
"bitrateKbps": 42,
"packetsLost": 1,
"packetsSent": 100
},
{
"trackSid": "screen-track",
"source": "screenshare",
"kind": "video",
"codec": "video/H265",
"bitrateKbps": 3500,
"packetsLost": 0,
"effectiveFps": 58.5,
"configuredFps": 60,
"targetFps": 60,
"width": 1920,
"height": 1080,
"zeroCopy": true
}
],
"inbound": [
{
"participantIdentity": "remote-user",
"participantSid": "participant-1",
"trackSid": "remote-audio",
"kind": "audio",
"bitrateKbps": 36,
"packetsLost": 3,
"packetsReceived": 97,
"jitterMs": 7
}
],
"droppedNativeVideoFrames": 2,
"droppedVideoFrameCallbacks": 1
}
}
],
"expectedCommands": [
[
{
"type": "connection.connect",
"operationId": 1,
"options": {"url": "wss://voice.example.test", "token": "token-1"}
}
],
[],
[{"type": "stats.collect", "operationId": 2}],
[]
],
"expected": {
"nextOperationId": 3,
"statsOperationId": null,
"statsFailureCode": null,
"summary": {
"network": {
"audioSendBitrateKbps": 42,
"audioRecvBitrateKbps": 36,
"videoSendBitrateKbps": 3500,
"videoRecvBitrateKbps": 0,
"audioPacketLossPercent": 3,
"videoPacketLossPercent": 0,
"jitterMs": 7,
"rttMs": 24
},
"localAudio": {"trackIdentifier": "mic-track"},
"localScreenShare": {
"trackIdentifier": "screen-track",
"codec": "video/H265",
"effectiveFramesPerSecond": 58.5,
"configuredFramesPerSecond": 60,
"targetFramesPerSecond": 60,
"frameWidth": 1920,
"frameHeight": 1080
},
"remoteAudio": {"trackIdentifier": "remote-audio"}
}
}
}
@@ -0,0 +1,81 @@
{
"name": "remote subscription quality and participant volume commands",
"events": [
{"type": "connection.connectRequested", "options": {"url": "wss://voice", "token": "token"}},
{"type": "connection.connectSucceeded", "operationId": 1},
{
"type": "remoteTrackSubscription.setRequested",
"options": {
"participantIdentity": "alice",
"source": "screen",
"subscribed": true,
"enabled": true,
"quality": "high"
}
},
{
"type": "remoteTrackSubscription.setRequested",
"options": {
"participantIdentity": "alice",
"source": "screen",
"subscribed": true,
"enabled": true,
"quality": "high"
}
},
{
"type": "remoteTrackSubscription.setRequested",
"options": {
"participantIdentity": "alice",
"source": "screen",
"subscribed": true,
"enabled": true,
"quality": "low"
}
},
{"type": "participantVolume.setRequested", "options": {"participantIdentity": "alice", "volume": 0.35}},
{"type": "participantVolume.setRequested", "options": {"participantIdentity": "alice", "volume": 0.35}}
],
"expected": {
"remoteTrackSubscriptions": {
"alice:screen": {
"participantIdentity": "alice",
"source": "screen",
"subscribed": true,
"enabled": true,
"quality": "low"
}
},
"participantVolumes": {"alice": 0.35},
"commands": [
{"type": "connection.connect", "operationId": 1, "options": {"url": "wss://voice", "token": "token"}},
{
"type": "remoteTrackSubscription.set",
"operationId": 2,
"options": {
"participantIdentity": "alice",
"source": "screen",
"subscribed": true,
"enabled": true,
"quality": "high"
}
},
{
"type": "remoteTrackSubscription.set",
"operationId": 3,
"options": {
"participantIdentity": "alice",
"source": "screen",
"subscribed": true,
"enabled": true,
"quality": "low"
}
},
{
"type": "participantVolume.set",
"operationId": 4,
"options": {"participantIdentity": "alice", "volume": 0.35}
}
]
}
}
@@ -0,0 +1,111 @@
{
"name": "teardown_renderer_window",
"description": "Renderer teardown clears gateway state, disconnects media, then runs host lifecycle teardown.",
"steps": [
{
"name": "gateway channel observed",
"event": {
"type": "gateway.voiceStateUpdated",
"voiceState": {
"guildId": "guild-1",
"channelId": "channel-1",
"userId": "user-1",
"sessionId": "session-1",
"selfMute": false,
"selfDeaf": false,
"selfVideo": false,
"selfStream": false,
"suppress": false,
"requestToSpeakTimestamp": null
}
},
"commands": []
},
{
"name": "media connection requested",
"event": {
"type": "connection.connectRequested",
"options": {
"url": "wss://voice-one.example.test",
"token": "token-1"
}
},
"commands": [
{
"type": "connection.connect",
"operationId": 1,
"options": {
"url": "wss://voice-one.example.test",
"token": "token-1"
}
}
]
},
{
"name": "media connection succeeds",
"event": {
"type": "connection.connectSucceeded",
"operationId": 1
},
"commands": []
},
{
"name": "renderer teardown requested",
"event": {
"type": "lifecycle.teardownRequested",
"reason": "rendererDisposed"
},
"commands": [
{
"type": "gateway.voiceState.clear",
"operationId": 2,
"guildId": "guild-1"
}
]
},
{
"name": "gateway clear succeeds",
"event": {
"type": "gateway.voiceStateClearSucceeded",
"operationId": 2
},
"commands": [
{
"type": "connection.disconnect",
"operationId": 3,
"reason": "shutdown"
}
]
},
{
"name": "media disconnect succeeds",
"event": {
"type": "connection.disconnectSucceeded",
"operationId": 3
},
"commands": [
{
"type": "lifecycle.teardown",
"operationId": 4,
"reason": "rendererDisposed"
}
]
},
{
"name": "host teardown succeeds",
"event": {
"type": "lifecycle.teardownSucceeded",
"operationId": 4
},
"commands": []
}
],
"expected": {
"nextOperationId": 5,
"connectionStatus": "idle",
"activeUrl": null,
"desiredUrl": null,
"gatewayChannelId": null,
"lifecycleTearingDown": false
}
}
@@ -0,0 +1,88 @@
{
"name": "watched streams drive subscription commands",
"events": [
{"type": "connection.connectRequested", "options": {"url": "wss://voice", "token": "token"}},
{"type": "connection.connectSucceeded", "operationId": 1},
{
"type": "watchedStream.watchRequested",
"stream": {
"participantIdentity": "alice",
"source": "screen",
"trackSid": null,
"quality": "high",
"enabled": true
}
},
{
"type": "room.trackPublished",
"track": {
"participantIdentity": "alice",
"participantSid": "PA_alice",
"trackSid": "TR_screen",
"trackName": "screen",
"kind": "video",
"source": "screen",
"muted": false
}
},
{
"type": "watchedStream.watchRequested",
"stream": {
"participantIdentity": "alice",
"source": "screen",
"trackSid": "TR_screen",
"quality": "low",
"enabled": true
}
},
{"type": "watchedStream.unwatchRequested", "stream": {"participantIdentity": "alice", "source": "screen"}}
],
"expected": {
"watchedStreams": [],
"remoteTrackSubscriptions": {
"alice:screen": {
"participantIdentity": "alice",
"source": "screen",
"subscribed": false,
"enabled": false,
"quality": "low"
}
},
"commands": [
{"type": "connection.connect", "operationId": 1, "options": {"url": "wss://voice", "token": "token"}},
{
"type": "remoteTrackSubscription.set",
"operationId": 2,
"options": {
"participantIdentity": "alice",
"source": "screen",
"subscribed": true,
"enabled": true,
"quality": "high"
}
},
{
"type": "remoteTrackSubscription.set",
"operationId": 3,
"options": {
"participantIdentity": "alice",
"source": "screen",
"subscribed": true,
"enabled": true,
"quality": "low"
}
},
{
"type": "remoteTrackSubscription.set",
"operationId": 4,
"options": {
"participantIdentity": "alice",
"source": "screen",
"subscribed": false,
"enabled": false,
"quality": "low"
}
}
]
}
}
@@ -0,0 +1,66 @@
{
"name": "externally_established_session",
"events": [
{
"type": "microphone.publishRequested",
"options": {
"deviceId": "default"
}
},
{
"type": "connection.externallyEstablished",
"options": {
"url": "wss://voice.example.test",
"token": ""
}
},
{
"type": "microphone.publishSucceeded",
"operationId": 1
},
{
"type": "connection.remoteDisconnected",
"reason": "network"
},
{
"type": "microphone.publishRequested",
"options": {
"deviceId": "default"
}
},
{
"type": "connection.externallyEstablished",
"options": {
"url": "wss://voice-reconnect.example.test",
"token": ""
}
},
{
"type": "microphone.publishSucceeded",
"operationId": 2
},
{
"type": "gateway.voiceStateUpdated",
"voiceState": {
"guildId": "guild-1",
"channelId": "channel-1",
"userId": "user-1",
"sessionId": "session-1",
"selfMute": false,
"selfDeaf": false,
"selfVideo": false,
"selfStream": false,
"suppress": false,
"requestToSpeakTimestamp": null
}
}
],
"expected": {
"nextOperationId": 3,
"connectionStatus": "connected",
"activeUrl": "wss://voice-reconnect.example.test",
"microphoneStatus": "published",
"microphoneDeviceId": "default",
"gatewayChannelId": "channel-1"
}
}
@@ -0,0 +1,66 @@
[
{
"name": "valid zero-copy native capture start",
"command": {
"type": "nativeCapture.start",
"operationId": 1,
"options": {
"captureId": "capture-1",
"source": {
"id": "display-1",
"kind": "screen",
"title": "Display 1",
"width": 1920,
"height": 1080,
"displayId": "display-1"
},
"width": 1920,
"height": 1080,
"frameRate": 60,
"includeCursor": true,
"includeAudio": false,
"zeroCopyRequired": true
}
},
"expectedOk": true
},
{
"name": "reject native capture start without zero-copy requirement",
"command": {
"type": "nativeCapture.start",
"operationId": 2,
"options": {
"captureId": "capture-2",
"source": {
"id": "window-1",
"kind": "window",
"title": "Window 1",
"windowId": "window-1"
},
"width": 1280,
"height": 720,
"frameRate": 60,
"includeCursor": true,
"includeAudio": false,
"zeroCopyRequired": false
}
},
"expectedOk": false,
"expectedError": "zeroCopyRequired"
},
{
"name": "reject native frame sink without zero-copy requirement",
"command": {
"type": "nativeFrameSink.attach",
"operationId": 3,
"options": {
"sinkId": "sink-1",
"captureId": "capture-1",
"trackSid": "TR_screen",
"zeroCopyRequired": false
}
},
"expectedOk": false,
"expectedError": "zeroCopyRequired"
}
]
@@ -0,0 +1,78 @@
[
{
"name": "mirror toggle updates in place",
"input": {
"published": {"deviceId": "camera-1", "width": 1280, "height": 720, "mirror": false},
"desired": null,
"update": {"mirror": true}
},
"expectedAction": "updateEncoding",
"expectedReason": "effectsOrEncodingChanged",
"expectedMirror": true
},
{
"name": "background mode and blur strength update in place",
"input": {
"published": {"deviceId": "camera-1", "width": 1280, "height": 720, "backgroundMode": "none"},
"desired": null,
"update": {"backgroundMode": "blur", "backgroundBlurStrength": 70}
},
"expectedAction": "updateEncoding",
"expectedReason": "effectsOrEncodingChanged",
"expectedBackgroundMode": "blur"
},
{
"name": "resolution and frame rate update in place",
"input": {
"published": {"deviceId": "camera-1", "width": 1280, "height": 720, "frameRate": 30},
"desired": null,
"update": {"width": 1920, "height": 1080, "frameRate": 60}
},
"expectedAction": "updateEncoding",
"expectedReason": "effectsOrEncodingChanged",
"expectedWidth": 1920,
"expectedFrameRate": 60
},
{
"name": "device change republishes",
"input": {
"published": {"deviceId": "camera-1", "width": 1280, "height": 720},
"desired": null,
"update": {"deviceId": "camera-2"}
},
"expectedAction": "republish",
"expectedReason": "deviceChanged"
},
{
"name": "codec change republishes",
"input": {
"published": {"deviceId": "camera-1", "width": 1280, "height": 720, "codec": "vp8"},
"desired": null,
"update": {"codec": "vp9"}
},
"expectedAction": "republish",
"expectedReason": "codecChanged",
"expectedCodec": "vp9"
},
{
"name": "identical options is a no-op",
"input": {
"published": {"deviceId": "camera-1", "width": 1280, "height": 720, "mirror": true},
"desired": null,
"update": {"width": 1280, "height": 720, "mirror": true}
},
"expectedAction": "noop",
"expectedReason": "unchanged"
},
{
"name": "missing published camera rejects",
"input": {
"published": null,
"desired": null,
"update": {"mirror": true}
},
"expectedAction": "reject",
"expectedReason": "missingPublishedCamera",
"expectedErrorCode": "invalidArgument"
}
]
@@ -0,0 +1,14 @@
[
{"raw": "encrypted", "expected": "encrypted"},
{"raw": "ok", "expected": "encrypted"},
{"raw": "decryption_failed", "expected": "decryption_failed"},
{"raw": "decryptionFailed", "expected": "decryption_failed"},
{"raw": "error", "expected": "decryption_failed"},
{"raw": "broken", "expected": "decryption_failed"},
{"raw": "unencrypted", "expected": "unencrypted"},
{"raw": "missing_key", "expected": "unencrypted"},
{"raw": "missingKey", "expected": "unencrypted"},
{"raw": "internal_error", "expected": "unencrypted"},
{"raw": "unexpected", "expected": "unknown"},
{"raw": null, "expected": "unknown"}
]
@@ -0,0 +1,134 @@
[
{
"name": "invalid native addon result is unavailable",
"input": null,
"codec": "h265",
"expectedCapabilities": {
"available": false,
"backend": "none",
"compiled": false,
"runtime": false,
"codecs": [],
"zeroCopy": false,
"nativeInputs": [],
"reason": "query-failed",
"detail": "Native addon returned an invalid result"
},
"expectedNativeNvenc": false
},
{
"name": "zero-copy native NVENC H265 is usable",
"input": {
"available": true,
"backend": "nvenc",
"compiled": true,
"runtime": true,
"codecs": ["h264", "h265"],
"zeroCopy": true,
"nativeInputs": ["dmabuf"]
},
"codec": "h265",
"expectedCapabilities": {
"available": true,
"backend": "nvenc",
"compiled": true,
"runtime": true,
"codecs": ["h264", "h265"],
"zeroCopy": true,
"nativeInputs": ["dmabuf"]
},
"expectedNativeNvenc": true
},
{
"name": "HEVC codec alias enables H265 when zero-copy NVENC is available",
"input": {
"available": true,
"backend": "nvenc",
"compiled": true,
"runtime": true,
"codecs": ["hevc"],
"zeroCopy": true,
"nativeInputs": ["d3d11-texture"]
},
"codec": "h265",
"expectedCapabilities": {
"available": true,
"backend": "nvenc",
"compiled": true,
"runtime": true,
"codecs": ["hevc"],
"zeroCopy": true,
"nativeInputs": ["d3d11-texture"]
},
"expectedNativeNvenc": true
},
{
"name": "native NVENC without zero-copy is not supported",
"input": {
"available": true,
"backend": "nvenc",
"compiled": true,
"runtime": true,
"codecs": ["h264", "h265"],
"zeroCopy": false,
"nativeInputs": ["dmabuf"]
},
"codec": "h265",
"expectedCapabilities": {
"available": true,
"backend": "nvenc",
"compiled": true,
"runtime": true,
"codecs": ["h264", "h265"],
"zeroCopy": false,
"nativeInputs": ["dmabuf"]
},
"expectedNativeNvenc": false
},
{
"name": "native NVENC without native zero-copy input handles is not supported",
"input": {
"available": true,
"backend": "nvenc",
"compiled": true,
"runtime": true,
"codecs": ["h264", "h265"],
"zeroCopy": true,
"nativeInputs": []
},
"codec": "h264",
"expectedCapabilities": {
"available": true,
"backend": "nvenc",
"compiled": true,
"runtime": true,
"codecs": ["h264", "h265"],
"zeroCopy": true,
"nativeInputs": []
},
"expectedNativeNvenc": false
},
{
"name": "non-NVENC backends are not selected by NVENC policy",
"input": {
"available": true,
"backend": "videotoolbox",
"compiled": true,
"runtime": true,
"codecs": ["h264", "h265"],
"zeroCopy": true,
"nativeInputs": ["cvPixelBuffer"]
},
"codec": "h265",
"expectedCapabilities": {
"available": true,
"backend": "videotoolbox",
"compiled": true,
"runtime": true,
"codecs": ["h264", "h265"],
"zeroCopy": true,
"nativeInputs": ["cvPixelBuffer"]
},
"expectedNativeNvenc": false
}
]
@@ -0,0 +1,56 @@
[
{
"name": "camera unpublish suppressed during reconnect when video restore is pending",
"input": {"source": "camera", "enabled": false, "reconnecting": true, "restoreVideo": true, "restoreStream": false},
"expected": true
},
{
"name": "screen share unpublish suppressed during reconnect when stream restore is pending",
"input": {
"source": "screen_share",
"enabled": false,
"reconnecting": true,
"restoreVideo": false,
"restoreStream": true
},
"expected": true
},
{
"name": "publish events are not suppressed",
"input": {
"source": "screen_share",
"enabled": true,
"reconnecting": true,
"restoreVideo": false,
"restoreStream": true
},
"expected": false
},
{
"name": "inactive reconnect is not suppressed",
"input": {
"source": "screen_share",
"enabled": false,
"reconnecting": false,
"restoreVideo": false,
"restoreStream": true
},
"expected": false
},
{
"name": "camera without video restore is not suppressed",
"input": {"source": "camera", "enabled": false, "reconnecting": true, "restoreVideo": false, "restoreStream": true},
"expected": false
},
{
"name": "microphone is not suppressed",
"input": {
"source": "microphone",
"enabled": false,
"reconnecting": true,
"restoreVideo": true,
"restoreStream": true
},
"expected": false
}
]
@@ -0,0 +1,175 @@
[
{
"name": "successful operation",
"result": {"ok": true},
"requestedEnabled": true,
"expected": "none"
},
{
"name": "disabled microphone not connected",
"result": {
"ok": false,
"error": {
"code": "not-connected",
"message": "Native voice engine is not connected"
}
},
"requestedEnabled": false,
"expected": "none"
},
{
"name": "unsupported microphone capture",
"result": {
"ok": false,
"error": {
"code": "unsupported-capability",
"capability": "microphoneCapture",
"message": "Native voice engine addon does not support device microphone capture"
}
},
"requestedEnabled": true,
"expected": "crash"
},
{
"name": "invalid args",
"result": {
"ok": false,
"error": {
"code": "invalid-args",
"message": "Invalid voice-engine publish-microphone args"
}
},
"requestedEnabled": true,
"expected": "crash"
},
{
"name": "modern not connected",
"result": {
"ok": false,
"error": {
"code": "not-connected",
"capability": "microphoneCapture",
"message": "Native voice engine is not connected"
}
},
"requestedEnabled": true,
"expected": "defer"
},
{
"name": "legacy not connected",
"result": {
"ok": false,
"error": {
"code": "native-error",
"capability": "microphoneCapture",
"message": "not connected"
}
},
"requestedEnabled": true,
"expected": "defer"
},
{
"name": "device unavailable",
"result": {
"ok": false,
"error": {
"code": "native-error",
"capability": "microphoneCapture",
"message": "device unavailable"
}
},
"requestedEnabled": true,
"expected": "mute"
},
{
"name": "timeout failure defers without muting",
"result": {
"ok": false,
"error": {
"code": "timeout",
"message": "Voice engine v2 microphone enable timed out"
}
},
"requestedEnabled": true,
"expected": "defer"
},
{
"name": "cancelled failure defers without muting",
"result": {
"ok": false,
"error": {
"code": "cancelled",
"message": "Voice engine v2 microphone enable cancelled"
}
},
"requestedEnabled": true,
"expected": "defer"
},
{
"name": "livekit transport failure defers without muting",
"result": {
"ok": false,
"error": {
"code": "liveKitError",
"message": "could not establish pc connection"
}
},
"requestedEnabled": true,
"expected": "defer"
},
{
"name": "opaque failure during reconnect defers without muting",
"result": {
"ok": false,
"error": {
"code": "native-error",
"capability": "microphoneCapture",
"message": "engine resume in progress"
}
},
"requestedEnabled": true,
"context": {"reconnecting": true},
"expected": "defer"
},
{
"name": "opaque failure while stable still mutes",
"result": {
"ok": false,
"error": {
"code": "native-error",
"capability": "microphoneCapture",
"message": "engine resume in progress"
}
},
"requestedEnabled": true,
"context": {"reconnecting": false},
"expected": "mute"
},
{
"name": "invalid args during reconnect still crashes",
"result": {
"ok": false,
"error": {
"code": "invalid-args",
"message": "Invalid voice-engine publish-microphone args"
}
},
"requestedEnabled": true,
"context": {"reconnecting": true},
"expected": "crash"
},
{
"name": "disable failure during reconnect stays none",
"result": {
"ok": false,
"error": {
"code": "native-error",
"capability": "microphoneCapture",
"message": "engine resume in progress"
}
},
"requestedEnabled": false,
"context": {"reconnecting": true},
"expected": "none"
}
]
@@ -0,0 +1,98 @@
[
{
"name": "retry while accepted connection is still connecting before gateway echo",
"input": {
"connectionId": "connection-a",
"guildId": "guild-a",
"channelId": "channel-a",
"voiceState": null,
"connectionState": {
"connectionId": "connection-a",
"guildId": "guild-a",
"channelId": "channel-a",
"connecting": true,
"connected": false,
"reconnecting": false
}
},
"expected": true
},
{
"name": "retry when gateway voice state confirms channel",
"input": {
"connectionId": "connection-a",
"guildId": "guild-a",
"channelId": "channel-a",
"voiceState": {
"guild_id": "guild-a",
"channel_id": "channel-a"
},
"connectionState": {
"connectionId": "stale-connection",
"guildId": "guild-a",
"channelId": "channel-a",
"connecting": false,
"connected": false,
"reconnecting": false
}
},
"expected": true
},
{
"name": "reject stale local connection",
"input": {
"connectionId": "connection-a",
"guildId": "guild-a",
"channelId": "channel-a",
"voiceState": null,
"connectionState": {
"connectionId": "connection-b",
"guildId": "guild-a",
"channelId": "channel-a",
"connecting": true,
"connected": false,
"reconnecting": false
}
},
"expected": false
},
{
"name": "reject inactive accepted connection",
"input": {
"connectionId": "connection-a",
"guildId": "guild-a",
"channelId": "channel-a",
"voiceState": null,
"connectionState": {
"connectionId": "connection-a",
"guildId": "guild-a",
"channelId": "channel-a",
"connecting": false,
"connected": false,
"reconnecting": false
}
},
"expected": false
},
{
"name": "treat null guild ids as equivalent private voice scope",
"input": {
"connectionId": "connection-a",
"guildId": null,
"channelId": "dm-channel-a",
"voiceState": {
"guild_id": null,
"channel_id": "dm-channel-a"
},
"connectionState": {
"connectionId": "connection-b",
"guildId": null,
"channelId": "dm-channel-a",
"connecting": false,
"connected": false,
"reconnecting": false
}
},
"expected": true
}
]
@@ -0,0 +1,37 @@
[
{
"name": "local mute wins",
"input": {"userVolumePercent": 200, "outputVolumePercent": 100, "locallyMuted": true},
"expected": 0
},
{
"name": "unity gain",
"input": {"userVolumePercent": 100, "outputVolumePercent": 100, "locallyMuted": false},
"expected": 1
},
{
"name": "max user volume reaches ceiling",
"input": {"userVolumePercent": 200, "outputVolumePercent": 100, "locallyMuted": false},
"expected": 2
},
{
"name": "quiet participant below unity",
"input": {"userVolumePercent": 50, "outputVolumePercent": 100, "locallyMuted": false},
"expectedRange": {"minExclusive": 0, "maxExclusive": 1}
},
{
"name": "zero composed volume",
"input": {"userVolumePercent": 0, "outputVolumePercent": 100, "locallyMuted": false},
"expected": 0
},
{
"name": "non finite values fall back to unity inputs",
"input": {"userVolumePercent": null, "outputVolumePercent": null, "locallyMuted": false},
"expected": 1
},
{
"name": "oversized values clamp to native range",
"input": {"userVolumePercent": 999, "outputVolumePercent": 999, "locallyMuted": false},
"expectedRange": {"minInclusive": 0, "maxInclusive": 2}
}
]
@@ -0,0 +1,112 @@
[
{
"name": "dimensions and bitrate update in place",
"input": {
"published": {
"captureId": "capture-1",
"width": 1920,
"height": 1080,
"codec": "h264",
"maxFramerate": 60,
"maxBitrateBps": 8000000
},
"desired": null,
"update": {
"captureId": "capture-1",
"width": 1280,
"height": 720,
"frameRate": 30,
"maxBitrateBps": 3000000
}
},
"expectedAction": "updateEncoding",
"expectedReason": "dimensionsOrBitrateChanged",
"expectedCodec": "h264"
},
{
"name": "codec change republishes",
"input": {
"published": {
"captureId": "capture-1",
"width": 1920,
"height": 1080,
"codec": "h264"
},
"desired": null,
"update": {
"captureId": "capture-1",
"width": 1920,
"height": 1080,
"codec": "h265"
}
},
"expectedAction": "republish",
"expectedReason": "codecChanged",
"expectedCodec": "h265"
},
{
"name": "hardware encoder change republishes",
"input": {
"published": {
"captureId": "capture-1",
"width": 1920,
"height": 1080,
"codec": "h264",
"hardwareEncoding": false
},
"desired": null,
"update": {
"captureId": "capture-1",
"width": 1920,
"height": 1080,
"hardwareEncoding": true,
"zeroCopyRequired": true
}
},
"expectedAction": "republish",
"expectedReason": "hardwareEncoderChanged",
"expectedHardwareEncoding": true
},
{
"name": "zero-copy requirement change republishes",
"input": {
"published": {
"captureId": "capture-1",
"width": 1920,
"height": 1080,
"codec": "h264",
"zeroCopyRequired": false
},
"desired": null,
"update": {
"captureId": "capture-1",
"width": 1920,
"height": 1080,
"zeroCopyRequired": true
}
},
"expectedAction": "republish",
"expectedReason": "zeroCopyRequirementChanged",
"expectedZeroCopyRequired": true
},
{
"name": "capture mismatch rejects",
"input": {
"published": {
"captureId": "capture-1",
"width": 1920,
"height": 1080,
"codec": "h264"
},
"desired": null,
"update": {
"captureId": "capture-2",
"width": 1920,
"height": 1080
}
},
"expectedAction": "reject",
"expectedReason": "captureMismatch",
"expectedErrorCode": "invalidArgument"
}
]
@@ -0,0 +1,188 @@
[
{
"name": "coerces malformed sparse payload to safe defaults",
"input": {
"rttMs": "bad",
"droppedVideoFrameCallbacks": -2,
"droppedNativeVideoFrames": 3.8,
"outbound": [
null,
{
"trackSid": "",
"source": 42,
"kind": "data",
"bitrateKbps": "bad",
"packetsLost": null,
"width": 1920.9,
"framesDropped": -4.2,
"zeroCopy": false
}
],
"inbound": [
null,
{
"participantSid": "PA_1",
"trackSid": "TR_audio",
"kind": "audio",
"bitrateKbps": 42.5,
"packetsLost": 2,
"jitterMs": "bad",
"audioLevel": 0.5
}
],
"send": null
},
"expected": {
"rttMs": null,
"droppedVideoFrameCallbacks": 0,
"droppedNativeVideoFrames": 3,
"outbound": [
{
"trackSid": "",
"source": "",
"kind": "audio",
"bitrateKbps": 0,
"packetsLost": 0,
"width": 1920,
"framesDropped": 0
}
],
"inbound": [
{
"participantSid": "PA_1",
"trackSid": "TR_audio",
"kind": "audio",
"bitrateKbps": 42.5,
"packetsLost": 2,
"audioLevel": 0.5
}
],
"send": null
}
},
{
"name": "preserves native video diagnostics and clamps send stats",
"input": {
"rttMs": 27,
"droppedVideoFrameCallbacks": 2,
"outbound": [
{
"trackSid": "TR_screen",
"source": "screen",
"kind": "video",
"codec": "video/H265",
"bitrateKbps": 2500.25,
"packetsLost": 1,
"fps": 59.5,
"audioLevel": 0.1,
"width": 2176,
"height": 1200,
"sourceWidth": 2176,
"sourceHeight": 1200,
"targetBitrateKbps": 50000,
"configuredFps": 60,
"targetFps": 60,
"effectiveFps": 58.7,
"framesProduced": 120.8,
"framesAccepted": 119.4,
"framesDropped": 1.9,
"framesCoalesced": 0,
"framesCaptured": 118.2,
"captureFailures": 0,
"maxQueueAgeMs": 12.5,
"maxPushLatencyMs": 8.25,
"adaptiveSendTier": "full",
"adaptiveSendReason": "disabled",
"zeroCopy": true
}
],
"inbound": [],
"send": {
"outgoingVideoQueueDepth": 4,
"outgoingVideoQueueCapacity": 8,
"outgoingVideoMaxQueueDepth": 6,
"outgoingVideoFramesProduced": 120,
"outgoingVideoFramesAccepted": 119,
"outgoingVideoFramesDropped": 1,
"outgoingVideoFramesCoalesced": 0,
"outgoingVideoFramesCaptured": 118,
"outgoingVideoCaptureFailures": -1,
"outgoingVideoEffectiveFps": 58.7,
"outgoingVideoTargetFps": 60,
"outgoingVideoPacingTargetFps": 60,
"outgoingVideoMaxQueueAgeMs": 12.5,
"outgoingVideoMaxPushLatencyMs": 8.25,
"outgoingVideoPacingMode": "source",
"outgoingVideoBusActive": true,
"outgoingAudioBufferTargetMs": 20,
"outgoingAudioBufferMaxMs": 80,
"outgoingAudioUnderruns": 0,
"outgoingAudioRebuffers": 1,
"outgoingAudioMaxFrameGapMs": 24,
"adaptiveSendTier": "full",
"adaptiveSendReason": "disabled"
}
},
"expected": {
"rttMs": 27,
"droppedVideoFrameCallbacks": 2,
"outbound": [
{
"trackSid": "TR_screen",
"source": "screen",
"kind": "video",
"codec": "video/H265",
"bitrateKbps": 2500.25,
"packetsLost": 1,
"fps": 59.5,
"audioLevel": 0.1,
"width": 2176,
"height": 1200,
"sourceWidth": 2176,
"sourceHeight": 1200,
"targetBitrateKbps": 50000,
"configuredFps": 60,
"targetFps": 60,
"effectiveFps": 58.7,
"framesProduced": 120,
"framesAccepted": 119,
"framesDropped": 1,
"framesCoalesced": 0,
"framesCaptured": 118,
"captureFailures": 0,
"maxQueueAgeMs": 12.5,
"maxPushLatencyMs": 8.25,
"adaptiveSendTier": "full",
"adaptiveSendReason": "disabled",
"zeroCopy": true
}
],
"inbound": [],
"send": {
"outgoingVideoQueueDepth": 4,
"outgoingVideoQueueCapacity": 8,
"outgoingVideoMaxQueueDepth": 6,
"outgoingVideoFramesProduced": 120,
"outgoingVideoFramesAccepted": 119,
"outgoingVideoFramesDropped": 1,
"outgoingVideoFramesCoalesced": 0,
"outgoingVideoFramesCaptured": 118,
"outgoingVideoCaptureFailures": 0,
"outgoingVideoEffectiveFps": 58.7,
"outgoingVideoTargetFps": 60,
"outgoingVideoPacingTargetFps": 60,
"outgoingVideoMaxQueueAgeMs": 12.5,
"outgoingVideoMaxPushLatencyMs": 8.25,
"outgoingVideoPacingMode": "source",
"outgoingVideoBusActive": true,
"outgoingAudioBufferTargetMs": 20,
"outgoingAudioBufferMaxMs": 80,
"outgoingAudioUnderruns": 0,
"outgoingAudioRebuffers": 1,
"outgoingAudioMaxFrameGapMs": 24,
"adaptiveSendTier": "full",
"adaptiveSendReason": "disabled"
}
}
}
]
@@ -0,0 +1,382 @@
[
{
"name": "splits outbound camera and screen-share video by source",
"input": {
"rttMs": null,
"outbound": [
{
"trackSid": "cam",
"source": "camera",
"kind": "video",
"bitrateKbps": 500,
"packetsLost": 0,
"packetsSent": 500,
"fps": 30
},
{
"trackSid": "ss",
"source": "screen_share",
"kind": "video",
"bitrateKbps": 3000,
"packetsLost": 1,
"packetsSent": 100,
"fps": 60
}
],
"inbound": []
},
"expectedSummary": {
"network": {
"audioSendBitrateKbps": 0,
"audioRecvBitrateKbps": 0,
"videoSendBitrateKbps": 3500,
"videoRecvBitrateKbps": 0,
"audioPacketLossPercent": 0,
"videoPacketLossPercent": 1,
"jitterMs": 0,
"rttMs": null
},
"localVideo": {"trackIdentifier": "cam", "framesPerSecond": 30},
"localScreenShare": {"trackIdentifier": "ss", "framesPerSecond": 60}
}
},
{
"name": "normalizes screenshare alias without underscore",
"input": {
"rttMs": null,
"outbound": [{"trackSid": "ss", "source": "screenshare", "kind": "video", "bitrateKbps": 1000, "packetsLost": 0}],
"inbound": []
},
"expectedSummary": {
"network": {
"audioSendBitrateKbps": 0,
"audioRecvBitrateKbps": 0,
"videoSendBitrateKbps": 1000,
"videoRecvBitrateKbps": 0,
"audioPacketLossPercent": 0,
"videoPacketLossPercent": 0,
"jitterMs": 0,
"rttMs": null
},
"localVideo": null,
"localScreenShare": {"trackIdentifier": "ss"}
}
},
{
"name": "preserves native screen-share codec dimensions fps and diagnostics",
"input": {
"rttMs": 1,
"outbound": [
{
"trackSid": "TR_screen",
"source": "screenshare",
"kind": "video",
"codec": "video/H265",
"bitrateKbps": 2500,
"packetsLost": 0,
"fps": 14,
"width": 1280,
"height": 720,
"sourceWidth": 2176,
"sourceHeight": 1200,
"targetBitrateKbps": 50000,
"configuredFps": 60,
"targetFps": 60,
"effectiveFps": 58.7,
"framesProduced": 120,
"framesAccepted": 119,
"framesDropped": 1,
"framesCoalesced": 0,
"framesCaptured": 118,
"captureFailures": 0,
"maxQueueAgeMs": 12.5,
"maxPushLatencyMs": 8.25,
"adaptiveSendTier": "full",
"adaptiveSendReason": "disabled"
}
],
"inbound": []
},
"expectedSummary": {
"network": {
"audioSendBitrateKbps": 0,
"audioRecvBitrateKbps": 0,
"videoSendBitrateKbps": 2500,
"videoRecvBitrateKbps": 0,
"audioPacketLossPercent": 0,
"videoPacketLossPercent": 0,
"jitterMs": 0,
"rttMs": 1
},
"localScreenShare": {
"trackIdentifier": "TR_screen",
"codec": "video/H265",
"framesPerSecond": 58.7,
"configuredFramesPerSecond": 60,
"targetFramesPerSecond": 60,
"effectiveFramesPerSecond": 58.7,
"frameWidth": 1280,
"frameHeight": 720,
"sourceFrameWidth": 2176,
"sourceFrameHeight": 1200,
"targetBitrateKbps": 50000,
"framesProduced": 120,
"framesAccepted": 119,
"framesDropped": 1,
"framesCaptured": 118,
"maxQueueAgeMs": 12.5,
"maxPushLatencyMs": 8.25,
"adaptiveSendTier": "full",
"adaptiveSendReason": "disabled"
}
}
},
{
"name": "coalesces duplicate native outbound rows for one screen-share publication",
"input": {
"rttMs": null,
"outbound": [
{
"trackSid": "TR_screen",
"source": "screen_share",
"kind": "video",
"codec": "video/H265",
"bitrateKbps": 269.9,
"packetsLost": 0,
"fps": 3,
"effectiveFps": 53.9,
"configuredFps": 60,
"targetFps": 60,
"width": 2176,
"height": 1200,
"sourceWidth": 2176,
"sourceHeight": 1200,
"targetBitrateKbps": 50000,
"framesProduced": 1904,
"framesAccepted": 1904,
"framesDropped": 64,
"framesCoalesced": 64,
"framesCaptured": 1839,
"captureFailures": 0,
"maxQueueAgeMs": 18,
"maxPushLatencyMs": 18,
"adaptiveSendTier": "full",
"adaptiveSendReason": "adaptiveDisabled"
},
{
"trackSid": "TR_screen",
"source": "screenshare",
"kind": "video",
"codec": "video/H265",
"bitrateKbps": 4614.1,
"packetsLost": 0,
"fps": 30
}
],
"inbound": []
},
"expectedSummary": {
"network": {
"audioSendBitrateKbps": 0,
"audioRecvBitrateKbps": 0,
"videoSendBitrateKbps": 4884,
"videoRecvBitrateKbps": 0,
"audioPacketLossPercent": 0,
"videoPacketLossPercent": 0,
"jitterMs": 0,
"rttMs": null
},
"localScreenShare": {
"trackIdentifier": "TR_screen",
"codec": "video/H265",
"bitrateKbps": 4884,
"framesPerSecond": 53.9,
"configuredFramesPerSecond": 60,
"targetFramesPerSecond": 60,
"frameWidth": 2176,
"frameHeight": 1200,
"targetBitrateKbps": 50000,
"adaptiveSendTier": "full",
"adaptiveSendReason": "adaptiveDisabled"
}
}
},
{
"name": "splits screen-share audio from microphone audio",
"input": {
"rttMs": null,
"outbound": [
{"trackSid": "mic", "source": "microphone", "kind": "audio", "bitrateKbps": 48, "packetsLost": 0},
{"trackSid": "ss-audio", "source": "screen_share_audio", "kind": "audio", "bitrateKbps": 96, "packetsLost": 0}
],
"inbound": []
},
"expectedSummary": {
"network": {
"audioSendBitrateKbps": 144,
"audioRecvBitrateKbps": 0,
"videoSendBitrateKbps": 0,
"videoRecvBitrateKbps": 0,
"audioPacketLossPercent": 0,
"videoPacketLossPercent": 0,
"jitterMs": 0,
"rttMs": null
},
"localAudio": {"trackIdentifier": "mic"},
"localScreenShareAudio": {"trackIdentifier": "ss-audio"}
}
},
{
"name": "rolls up inbound audio video jitter rtt and callback drops",
"input": {
"rttMs": 25,
"droppedVideoFrameCallbacks": 3,
"outbound": [],
"inbound": [
{
"participantSid": "PA_1",
"trackSid": "a",
"kind": "audio",
"bitrateKbps": 32,
"packetsLost": 2,
"packetsReceived": 98,
"jitterMs": 8
},
{
"participantSid": "PA_1",
"trackSid": "v",
"kind": "video",
"bitrateKbps": 900,
"packetsLost": 0,
"packetsReceived": 1500
}
]
},
"expectedSummary": {
"network": {
"audioSendBitrateKbps": 0,
"audioRecvBitrateKbps": 32,
"videoSendBitrateKbps": 0,
"videoRecvBitrateKbps": 900,
"audioPacketLossPercent": 2,
"videoPacketLossPercent": 0,
"jitterMs": 8,
"rttMs": 25
},
"remoteAudio": {"trackIdentifier": "a"},
"remoteVideo": {"trackIdentifier": "v"},
"remoteScreenShare": null
}
},
{
"name": "uses worst case audio packet loss across send and receive",
"input": {
"rttMs": null,
"outbound": [
{
"trackSid": "a",
"source": "microphone",
"kind": "audio",
"bitrateKbps": 30,
"packetsLost": 5,
"packetsSent": 100
}
],
"inbound": [
{
"participantSid": "PA_1",
"trackSid": "b",
"kind": "audio",
"bitrateKbps": 30,
"packetsLost": 12,
"packetsReceived": 88
}
]
},
"expectedSummary": {
"network": {
"audioSendBitrateKbps": 30,
"audioRecvBitrateKbps": 30,
"videoSendBitrateKbps": 0,
"videoRecvBitrateKbps": 0,
"audioPacketLossPercent": 12,
"videoPacketLossPercent": 0,
"jitterMs": 0,
"rttMs": null
}
}
},
{
"name": "reports inbound video loss as a windowed percentage not a raw packet count",
"input": {
"rttMs": 1,
"outbound": [],
"inbound": [
{
"participantSid": "PA_1",
"trackSid": "v",
"kind": "video",
"bitrateKbps": 147,
"packetsLost": 13,
"packetsReceived": 4000
}
]
},
"expectedSummary": {
"network": {
"audioPacketLossPercent": 0,
"videoPacketLossPercent": 0.3,
"rttMs": 1
},
"remoteVideo": {"trackIdentifier": "v"}
}
},
{
"name": "handles empty stats as zero network and null tracks",
"input": {
"rttMs": null,
"outbound": [],
"inbound": []
},
"expectedSummary": {
"network": {
"audioSendBitrateKbps": 0,
"audioRecvBitrateKbps": 0,
"videoSendBitrateKbps": 0,
"videoRecvBitrateKbps": 0,
"audioPacketLossPercent": 0,
"videoPacketLossPercent": 0,
"jitterMs": 0,
"rttMs": null
},
"localAudio": null,
"localVideo": null,
"remoteAudio": null,
"remoteVideo": null
}
},
{
"name": "prefers first non-zero bitrate track when several candidates exist",
"input": {
"rttMs": null,
"outbound": [
{"trackSid": "silent", "source": "microphone", "kind": "audio", "bitrateKbps": 0, "packetsLost": 0},
{"trackSid": "live", "source": "microphone", "kind": "audio", "bitrateKbps": 40, "packetsLost": 0}
],
"inbound": []
},
"expectedSummary": {
"network": {
"audioSendBitrateKbps": 40,
"audioRecvBitrateKbps": 0,
"videoSendBitrateKbps": 0,
"videoRecvBitrateKbps": 0,
"audioPacketLossPercent": 0,
"videoPacketLossPercent": 0,
"jitterMs": 0,
"rttMs": null
},
"localAudio": {"trackIdentifier": "live"}
}
}
]
@@ -0,0 +1,123 @@
[
{
"name": "classifies all explicit local and remote publication ids",
"input": {
"tracks": [
{"direction": "send", "kind": "video", "trackIdentifier": "local-camera", "bitrateKbps": 500},
{"direction": "send", "kind": "audio", "trackIdentifier": "local-mic", "bitrateKbps": 48},
{"direction": "send", "kind": "video", "trackIdentifier": "local-screen", "bitrateKbps": 1200},
{"direction": "send", "kind": "audio", "trackIdentifier": "local-screen-audio", "bitrateKbps": 96},
{"direction": "recv", "kind": "audio", "trackIdentifier": "remote-mic", "bitrateKbps": 64},
{"direction": "recv", "kind": "video", "trackIdentifier": "remote-camera", "bitrateKbps": 900},
{"direction": "recv", "kind": "video", "trackIdentifier": "remote-screen", "bitrateKbps": 1800},
{"direction": "recv", "kind": "audio", "trackIdentifier": "remote-screen-audio", "bitrateKbps": 128}
],
"publications": {
"localCameraTrackId": "local-camera",
"localMicrophoneTrackId": "local-mic",
"localScreenShareTrackId": "local-screen",
"localScreenShareAudioTrackId": "local-screen-audio",
"remoteMicrophoneTrackIds": ["remote-mic"],
"remoteScreenShareTrackIds": ["remote-screen"],
"remoteScreenShareAudioTrackIds": ["remote-screen-audio"]
}
},
"expected": {
"localVideoTrackIndex": 0,
"localAudioTrackIndex": 1,
"localScreenShareTrackIndex": 2,
"localScreenShareAudioTrackIndex": 3,
"remoteVideoTrackIndex": 5,
"remoteAudioTrackIndex": 4,
"remoteScreenShareTrackIndex": 6,
"remoteScreenShareAudioTrackIndex": 7
}
},
{
"name": "falls back to rid screen-share and non-rid camera when local ids are missing",
"input": {
"tracks": [
{"direction": "send", "kind": "video", "trackIdentifier": "camera-fallback", "bitrateKbps": 700},
{"direction": "send", "kind": "video", "rid": "h", "trackIdentifier": "screen-fallback", "bitrateKbps": 1600},
{"direction": "send", "kind": "audio", "trackIdentifier": "local-mic", "bitrateKbps": 48}
],
"publications": {
"localCameraTrackId": null,
"localMicrophoneTrackId": null,
"localScreenShareTrackId": null,
"localScreenShareAudioTrackId": null,
"remoteMicrophoneTrackIds": [],
"remoteScreenShareTrackIds": [],
"remoteScreenShareAudioTrackIds": []
}
},
"expected": {
"localVideoTrackIndex": 0,
"localAudioTrackIndex": 2,
"localScreenShareTrackIndex": 1,
"localScreenShareAudioTrackIndex": null,
"remoteVideoTrackIndex": null,
"remoteAudioTrackIndex": null,
"remoteScreenShareTrackIndex": null,
"remoteScreenShareAudioTrackIndex": null
}
},
{
"name": "does not classify remote screen-share audio as remote microphone",
"input": {
"tracks": [
{"direction": "recv", "kind": "audio", "trackIdentifier": "remote-screen-audio", "bitrateKbps": 4},
{"direction": "recv", "kind": "video", "trackIdentifier": "remote-screen", "bitrateKbps": 1800}
],
"publications": {
"localCameraTrackId": null,
"localMicrophoneTrackId": null,
"localScreenShareTrackId": null,
"localScreenShareAudioTrackId": null,
"remoteMicrophoneTrackIds": [],
"remoteScreenShareTrackIds": ["remote-screen"],
"remoteScreenShareAudioTrackIds": ["remote-screen-audio"]
}
},
"expected": {
"localVideoTrackIndex": null,
"localAudioTrackIndex": null,
"localScreenShareTrackIndex": null,
"localScreenShareAudioTrackIndex": null,
"remoteVideoTrackIndex": null,
"remoteAudioTrackIndex": null,
"remoteScreenShareTrackIndex": 1,
"remoteScreenShareAudioTrackIndex": 0
}
},
{
"name": "chooses active remote screen-share and camera candidates over silent rows",
"input": {
"tracks": [
{"direction": "recv", "kind": "video", "trackIdentifier": "remote-camera-silent", "bitrateKbps": 0},
{"direction": "recv", "kind": "video", "trackIdentifier": "remote-camera-live", "bitrateKbps": 900},
{"direction": "recv", "kind": "video", "trackIdentifier": "remote-screen-silent", "bitrateKbps": 0},
{"direction": "recv", "kind": "video", "trackIdentifier": "remote-screen-live", "bitrateKbps": 1600}
],
"publications": {
"localCameraTrackId": null,
"localMicrophoneTrackId": null,
"localScreenShareTrackId": null,
"localScreenShareAudioTrackId": null,
"remoteMicrophoneTrackIds": [],
"remoteScreenShareTrackIds": ["remote-screen-silent", "remote-screen-live"],
"remoteScreenShareAudioTrackIds": []
}
},
"expected": {
"localVideoTrackIndex": null,
"localAudioTrackIndex": null,
"localScreenShareTrackIndex": null,
"localScreenShareAudioTrackIndex": null,
"remoteVideoTrackIndex": 1,
"remoteAudioTrackIndex": null,
"remoteScreenShareTrackIndex": 3,
"remoteScreenShareAudioTrackIndex": null
}
}
]
+31
View File
@@ -0,0 +1,31 @@
{
"name": "@fluxer/voice_engine_v2",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts",
"./bridge": "./src/bridge/index.ts",
"./core": "./src/core/index.ts",
"./implementations": "./src/implementations/index.ts",
"./policies": "./src/policies/index.ts",
"./ports": "./src/ports/index.ts",
"./protocol": "./src/protocol/index.ts",
"./runtime": "./src/runtime/index.ts",
"./testing": "./src/testing/index.ts"
},
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {
"bench": "vitest bench --run",
"test": "vitest run",
"test:watch": "vitest",
"typecheck": "tsgo --noEmit"
},
"devDependencies": {
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"vite-tsconfig-paths": "catalog:",
"vitest": "catalog:"
}
}
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {describe, expect, it} from 'vitest';
import {
runVoiceEngineV2AdmWarmup,
VOICE_ENGINE_V2_ADM_WARMUP_POLL_ATTEMPTS_MAX,
VOICE_ENGINE_V2_ADM_WARMUP_POLL_INTERVAL_MS,
} from './admWarmup';
describe('runVoiceEngineV2AdmWarmup', () => {
it('resolves ready when the first probe succeeds', async () => {
let probeCalls = 0;
const state = await runVoiceEngineV2AdmWarmup({
probe: async () => {
probeCalls += 1;
return true;
},
delay: async () => {},
});
expect(state).toEqual({status: 'ready'});
expect(probeCalls).toBe(1);
});
it('polls at the fixed cadence until the probe reports ready', async () => {
let probeCalls = 0;
const delays: Array<number> = [];
const state = await runVoiceEngineV2AdmWarmup({
probe: async () => {
probeCalls += 1;
return probeCalls >= 5;
},
delay: async (durationMs) => {
delays.push(durationMs);
},
});
expect(state).toEqual({status: 'ready'});
expect(probeCalls).toBe(5);
expect(delays.every((durationMs) => durationMs === VOICE_ENGINE_V2_ADM_WARMUP_POLL_INTERVAL_MS)).toBe(true);
});
it('fails after the bounded attempt count when the probe never reports ready', async () => {
let probeCalls = 0;
const state = await runVoiceEngineV2AdmWarmup({
probe: async () => {
probeCalls += 1;
return false;
},
delay: async () => {},
});
expect(state.status).toBe('failed');
expect(state.detail).toContain('deadline');
expect(probeCalls).toBe(VOICE_ENGINE_V2_ADM_WARMUP_POLL_ATTEMPTS_MAX);
});
it('fails with the probe error detail when the probe throws', async () => {
let probeCalls = 0;
const state = await runVoiceEngineV2AdmWarmup({
probe: async () => {
probeCalls += 1;
throw new Error('addon exploded');
},
delay: async () => {},
});
expect(state.status).toBe('failed');
expect(state.detail).toContain('addon exploded');
expect(probeCalls).toBe(1);
});
it('fails at the deadline without stacking probes when the probe hangs forever', async () => {
let probeCalls = 0;
const state = await runVoiceEngineV2AdmWarmup({
probe: () => {
probeCalls += 1;
return new Promise<boolean>(() => {});
},
delay: async () => {},
});
expect(state.status).toBe('failed');
expect(state.detail).toContain('deadline');
expect(probeCalls).toBe(1);
});
it('resolves ready when a slow probe settles after several poll intervals', async () => {
let probeCalls = 0;
let resolveProbe: ((ready: boolean) => void) | null = null;
let delayCalls = 0;
const state = await runVoiceEngineV2AdmWarmup({
probe: () => {
probeCalls += 1;
return new Promise<boolean>((resolve) => {
resolveProbe = resolve;
});
},
delay: async () => {
delayCalls += 1;
if (delayCalls === 3) {
resolveProbe?.(true);
}
},
});
expect(state).toEqual({status: 'ready'});
expect(probeCalls).toBe(1);
});
});
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {VoiceEngineV2BridgeAudioDeviceModuleState} from './types';
export const VOICE_ENGINE_V2_ADM_WARMUP_POLL_INTERVAL_MS = 250;
export const VOICE_ENGINE_V2_ADM_WARMUP_DEADLINE_MS = 60_000;
export const VOICE_ENGINE_V2_ADM_WARMUP_POLL_ATTEMPTS_MAX = Math.floor(
VOICE_ENGINE_V2_ADM_WARMUP_DEADLINE_MS / VOICE_ENGINE_V2_ADM_WARMUP_POLL_INTERVAL_MS,
);
export interface VoiceEngineV2AdmWarmupPorts {
probe(): Promise<boolean>;
delay(durationMs: number): Promise<void>;
}
type AdmProbeOutcome = {kind: 'ready'; ready: boolean} | {kind: 'error'; detail: string};
const ADM_PROBE_STILL_PENDING = Symbol('voice-engine-v2-adm-probe-still-pending');
function startBoundedAdmProbe(ports: VoiceEngineV2AdmWarmupPorts): Promise<AdmProbeOutcome> {
return ports.probe().then(
(ready) => ({kind: 'ready', ready}),
(error) => {
const detail = error instanceof Error ? error.message : String(error);
return {kind: 'error', detail: `audio device module probe failed: ${detail}`};
},
);
}
export async function runVoiceEngineV2AdmWarmup(
ports: VoiceEngineV2AdmWarmupPorts,
): Promise<VoiceEngineV2BridgeAudioDeviceModuleState> {
if (VOICE_ENGINE_V2_ADM_WARMUP_POLL_ATTEMPTS_MAX < 1) {
throw new Error('ADM warmup poll attempt bound must be at least 1');
}
let pendingProbe: Promise<AdmProbeOutcome> | null = null;
for (let attempt = 1; attempt <= VOICE_ENGINE_V2_ADM_WARMUP_POLL_ATTEMPTS_MAX; attempt++) {
if (pendingProbe === null) {
pendingProbe = startBoundedAdmProbe(ports);
}
const attemptTimeout = ports.delay(VOICE_ENGINE_V2_ADM_WARMUP_POLL_INTERVAL_MS).then(() => ADM_PROBE_STILL_PENDING);
const outcome = await Promise.race([pendingProbe, attemptTimeout]);
if (outcome === ADM_PROBE_STILL_PENDING) {
continue;
}
pendingProbe = null;
const probeOutcome = outcome as AdmProbeOutcome;
if (probeOutcome.kind === 'error') {
return {status: 'failed', detail: probeOutcome.detail};
}
if (probeOutcome.ready) {
return {status: 'ready'};
}
if (attempt < VOICE_ENGINE_V2_ADM_WARMUP_POLL_ATTEMPTS_MAX) {
await ports.delay(VOICE_ENGINE_V2_ADM_WARMUP_POLL_INTERVAL_MS);
}
}
return {
status: 'failed',
detail: `audio device module warmup exceeded ${VOICE_ENGINE_V2_ADM_WARMUP_DEADLINE_MS}ms deadline`,
};
}
@@ -0,0 +1,562 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {beforeEach, describe, expect, it} from 'vitest';
import fixtureJson from '../../fixtures/bridge/bridge_contract.json';
import {
assertAudioFrameInvariants,
assertSchemaVersion,
assertVideoFrameInvariants,
VOICE_ENGINE_V2_AUDIO_FRAME_BYTES_MAX,
VOICE_ENGINE_V2_VIDEO_DIMENSION_MAX,
VOICE_ENGINE_V2_VIDEO_FRAME_BYTES_MAX,
VoiceEngineV2FfiAssertError,
} from './ffi_assertions';
import {
assertVoiceEngineV2BridgeAudioOptionsInvariants,
assertVoiceEngineV2BridgeEnvelopeSchema,
assertVoiceEngineV2BridgeVideoOptionsInvariants,
getVoiceEngineV2BridgeDroppedEventCounts,
getVoiceEngineV2BridgeProcessedCameraI420ByteLength,
isVoiceEngineV2BridgeFloatPcmFrame,
isVoiceEngineV2BridgeKnownEventType,
isVoiceEngineV2BridgeProcessedCameraFrame,
isVoiceEngineV2BridgePublishCameraOptions,
isVoiceEngineV2BridgePublishProcessedCameraOptions,
isVoiceEngineV2BridgeReadiness,
resetVoiceEngineV2BridgeDroppedEventCounts,
translateVoiceEngineV2BridgeEventToEvents,
translateVoiceEngineV2BridgeVideoFrameToEvent,
VOICE_ENGINE_V2_ADM_STATUS_EVENT_TYPE,
VOICE_ENGINE_V2_BRIDGE_METHODS,
VOICE_ENGINE_V2_BRIDGE_VERSION,
VOICE_ENGINE_V2_ENGINE_READY_EVENT_TYPE,
VOICE_ENGINE_V2_EVENT_CHANNELS,
VOICE_ENGINE_V2_IPC_CHANNELS,
} from './index';
interface VoiceEngineV2BridgeContractFixture {
version: number;
methods: Array<string>;
ipcChannels: Record<string, string>;
eventChannels: Record<string, string>;
}
describe('voice engine v2 bridge contract', () => {
it('matches the shared bridge contract fixture', () => {
const fixture = fixtureJson as VoiceEngineV2BridgeContractFixture;
expect(VOICE_ENGINE_V2_BRIDGE_VERSION).toBe(fixture.version);
expect(VOICE_ENGINE_V2_BRIDGE_METHODS).toEqual(fixture.methods);
expect(VOICE_ENGINE_V2_IPC_CHANNELS).toEqual(fixture.ipcChannels);
expect(VOICE_ENGINE_V2_EVENT_CHANNELS).toEqual(fixture.eventChannels);
expect(new Set(VOICE_ENGINE_V2_BRIDGE_METHODS).size).toBe(VOICE_ENGINE_V2_BRIDGE_METHODS.length);
expect(new Set(Object.values(VOICE_ENGINE_V2_IPC_CHANNELS)).size).toBe(
Object.values(VOICE_ENGINE_V2_IPC_CHANNELS).length,
);
expect(new Set(Object.values(VOICE_ENGINE_V2_EVENT_CHANNELS)).size).toBe(
Object.values(VOICE_ENGINE_V2_EVENT_CHANNELS).length,
);
});
it('accepts the canonical schema version on the FFI boundary', () => {
expect(() => assertSchemaVersion(VOICE_ENGINE_V2_BRIDGE_VERSION)).not.toThrow();
});
it('rejects a schema version older than the bridge', () => {
try {
assertSchemaVersion(VOICE_ENGINE_V2_BRIDGE_VERSION - 1);
expect.fail('expected schema mismatch to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('schemaVersionMismatch');
}
});
it('rejects a schema version newer than the bridge', () => {
try {
assertSchemaVersion(VOICE_ENGINE_V2_BRIDGE_VERSION + 1);
expect.fail('expected schema mismatch to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('schemaVersionMismatch');
}
});
});
describe('voice engine v2 readiness contract', () => {
it('registers the readiness method and channel in the shared contract', () => {
expect(VOICE_ENGINE_V2_BRIDGE_METHODS).toContain('getVoiceEngineReadiness');
expect(VOICE_ENGINE_V2_IPC_CHANNELS.getVoiceEngineReadiness).toBe('voice-engine-v2:get-readiness');
});
it('classifies engineReady as a known bridge event type', () => {
expect(VOICE_ENGINE_V2_ENGINE_READY_EVENT_TYPE).toBe('engineReady');
expect(isVoiceEngineV2BridgeKnownEventType(VOICE_ENGINE_V2_ENGINE_READY_EVENT_TYPE)).toBe(true);
});
it('translates engineReady as a renderer-gating event with no reducer output', () => {
resetVoiceEngineV2BridgeDroppedEventCounts();
const events = translateVoiceEngineV2BridgeEventToEvents({
type: 'engineReady',
payload: {ready: true},
});
expect(events).toEqual([]);
const counts = getVoiceEngineV2BridgeDroppedEventCounts();
expect(counts.malformedPayloadCount).toBe(0);
expect(counts.unknownTypeCount).toBe(0);
});
it('translates audio device module status into a runtime event', () => {
resetVoiceEngineV2BridgeDroppedEventCounts();
const events = translateVoiceEngineV2BridgeEventToEvents({
type: VOICE_ENGINE_V2_ADM_STATUS_EVENT_TYPE,
payload: {status: 'warming'},
});
expect(events).toEqual([{type: 'nativeAudioDeviceModule.statusChanged', status: 'warming'}]);
const counts = getVoiceEngineV2BridgeDroppedEventCounts();
expect(counts.malformedPayloadCount).toBe(0);
expect(counts.unknownTypeCount).toBe(0);
});
it('accepts ready and not-ready readiness payloads', () => {
expect(isVoiceEngineV2BridgeReadiness({ready: true})).toBe(true);
expect(isVoiceEngineV2BridgeReadiness({ready: false})).toBe(true);
expect(isVoiceEngineV2BridgeReadiness({ready: false, reason: 'native voice engine prewarm pending'})).toBe(true);
});
it('rejects malformed readiness payloads', () => {
expect(isVoiceEngineV2BridgeReadiness(null)).toBe(false);
expect(isVoiceEngineV2BridgeReadiness({})).toBe(false);
expect(isVoiceEngineV2BridgeReadiness({ready: 'yes'})).toBe(false);
expect(isVoiceEngineV2BridgeReadiness({ready: false, reason: 42})).toBe(false);
expect(isVoiceEngineV2BridgeReadiness({ready: true, reason: 'ready engines carry no reason'})).toBe(false);
});
});
describe('voice engine v2 audio frame invariants', () => {
const canonicalAudioFrame = {
sampleRateHz: 48000,
numChannels: 2,
frameBytes: 1920,
timestampNs: 1000,
};
it('accepts a canonical audio frame with no previous timestamp', () => {
expect(() => assertAudioFrameInvariants(canonicalAudioFrame)).not.toThrow();
});
it('accepts a canonical audio frame with an older previous timestamp', () => {
expect(() => assertAudioFrameInvariants(canonicalAudioFrame, 500)).not.toThrow();
});
it('rejects an audio frame with zero bytes', () => {
try {
assertAudioFrameInvariants({...canonicalAudioFrame, frameBytes: 0});
expect.fail('expected size assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('audioFrameBytesOutOfRange');
}
});
it('rejects an audio frame over the byte cap', () => {
try {
assertAudioFrameInvariants({...canonicalAudioFrame, frameBytes: VOICE_ENGINE_V2_AUDIO_FRAME_BYTES_MAX + 1});
expect.fail('expected size assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('audioFrameBytesOutOfRange');
}
});
it('rejects an audio frame with an unsupported sample rate', () => {
try {
assertAudioFrameInvariants({...canonicalAudioFrame, sampleRateHz: 44100});
expect.fail('expected sample-rate assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('audioSampleRateInvalid');
}
});
it('rejects an audio frame with an unsupported channel count', () => {
try {
assertAudioFrameInvariants({...canonicalAudioFrame, numChannels: 3});
expect.fail('expected channels assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('audioChannelsInvalid');
}
});
it('rejects an audio frame whose timestamp does not advance', () => {
try {
assertAudioFrameInvariants(canonicalAudioFrame, canonicalAudioFrame.timestampNs);
expect.fail('expected timestamp assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('audioTimestampRegressed');
}
});
it('rejects an audio frame whose timestamp moves backwards', () => {
try {
assertAudioFrameInvariants(canonicalAudioFrame, canonicalAudioFrame.timestampNs + 1);
expect.fail('expected timestamp assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('audioTimestampRegressed');
}
});
});
describe('voice engine v2 video frame invariants', () => {
const canonicalVideoFrame = {
widthPx: 1920,
heightPx: 1080,
frameBytes: (1920 * 1080 * 3) / 2,
timestampNs: 1000,
};
it('accepts a canonical video frame with no previous timestamp', () => {
expect(() => assertVideoFrameInvariants(canonicalVideoFrame)).not.toThrow();
});
it('rejects a video frame with zero bytes', () => {
try {
assertVideoFrameInvariants({...canonicalVideoFrame, frameBytes: 0});
expect.fail('expected size assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('videoFrameBytesOutOfRange');
}
});
it('rejects a video frame over the byte cap', () => {
try {
assertVideoFrameInvariants({...canonicalVideoFrame, frameBytes: VOICE_ENGINE_V2_VIDEO_FRAME_BYTES_MAX + 1});
expect.fail('expected size assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('videoFrameBytesOutOfRange');
}
});
it('rejects a video frame with a zero dimension', () => {
try {
assertVideoFrameInvariants({...canonicalVideoFrame, widthPx: 0});
expect.fail('expected dimension assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('videoDimensionOutOfRange');
}
});
it('rejects a video frame with an oversized dimension', () => {
try {
assertVideoFrameInvariants({...canonicalVideoFrame, heightPx: VOICE_ENGINE_V2_VIDEO_DIMENSION_MAX + 1});
expect.fail('expected dimension assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('videoDimensionOutOfRange');
}
});
it('rejects a video frame whose timestamp regresses', () => {
try {
assertVideoFrameInvariants(canonicalVideoFrame, canonicalVideoFrame.timestampNs);
expect.fail('expected timestamp assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('videoTimestampRegressed');
}
});
it('documents that inboundVideo.frameReceived will activate this guard once the port leaves shadow', () => {
const hotPathTypes = ['inboundVideo.frameReceived', 'nativeCapture.frame'];
expect(hotPathTypes.length).toBeGreaterThan(0);
for (const type of hotPathTypes) {
expect(typeof type).toBe('string');
}
});
});
describe('voice engine v2 bridge schema version literal', () => {
it('pins the TS bridge version to the literal 18 to pair with the native addon bridge', () => {
expect(VOICE_ENGINE_V2_BRIDGE_VERSION).toBe(18);
});
it('reuses the schema-version airlock helper from bridge exports', () => {
expect(() => assertVoiceEngineV2BridgeEnvelopeSchema(VOICE_ENGINE_V2_BRIDGE_VERSION)).not.toThrow();
});
it('rejects bridge schema mismatch via the exported airlock helper', () => {
try {
assertVoiceEngineV2BridgeEnvelopeSchema(VOICE_ENGINE_V2_BRIDGE_VERSION + 1);
expect.fail('expected schema mismatch to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('schemaVersionMismatch');
}
});
});
describe('voice engine v2 float pcm bridge frames', () => {
it('accepts Float32Array payloads for native-side PCM conversion', () => {
expect(
isVoiceEngineV2BridgeFloatPcmFrame({
sampleRate: 48_000,
numChannels: 1,
samples: new Float32Array([0, 0.5, -0.5]),
}),
).toBe(true);
});
it('rejects non-float payloads on the float PCM bridge', () => {
expect(
isVoiceEngineV2BridgeFloatPcmFrame({
sampleRate: 48_000,
numChannels: 1,
samples: new Uint8Array([0, 1, 2, 3]).buffer,
}),
).toBe(false);
});
});
describe('voice engine v2 bridge translator airlock', () => {
function makeFrame(width: number, height: number, byteLength: number, timestampUs: number) {
return {
meta: {participantSid: 'PA', trackSid: 'TR', width, height, timestampUs},
data: new ArrayBuffer(byteLength),
};
}
it('translates a canonical video frame received from Rust', () => {
const event = translateVoiceEngineV2BridgeVideoFrameToEvent(makeFrame(1920, 1080, 4096, 1));
expect(event.type).toBe('inboundVideo.frameReceived');
});
it('panics through the airlock when an inbound video frame has zero dimensions', () => {
try {
translateVoiceEngineV2BridgeVideoFrameToEvent(makeFrame(0, 1080, 4096, 1));
expect.fail('expected dimension assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('videoDimensionOutOfRange');
}
});
it('panics through the airlock when an inbound video frame has zero bytes', () => {
try {
translateVoiceEngineV2BridgeVideoFrameToEvent(makeFrame(1920, 1080, 0, 1));
expect.fail('expected size assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('videoFrameBytesOutOfRange');
}
});
});
describe('voice engine v2 processed camera bridge airlock', () => {
it('accepts native camera background modes', () => {
expect(isVoiceEngineV2BridgePublishCameraOptions({backgroundMode: 'none'})).toBe(true);
expect(isVoiceEngineV2BridgePublishCameraOptions({backgroundMode: 'non'})).toBe(true);
expect(isVoiceEngineV2BridgePublishCameraOptions({backgroundMode: 'blur'})).toBe(true);
expect(
isVoiceEngineV2BridgePublishCameraOptions({
backgroundMode: 'custom',
backgroundCustomMediaPath: '/tmp/background.webm',
backgroundCustomMediaKind: 'video',
}),
).toBe(true);
});
it('registers the camera capture update channel in the shared contract', () => {
expect(VOICE_ENGINE_V2_BRIDGE_METHODS).toContain('updateCameraCapture');
expect(VOICE_ENGINE_V2_IPC_CHANNELS.updateCameraCapture).toBe('voice-engine-v2:update-camera-capture');
});
it('accepts native camera mirroring as a typed boolean only', () => {
expect(isVoiceEngineV2BridgePublishCameraOptions({mirror: true})).toBe(true);
expect(isVoiceEngineV2BridgePublishCameraOptions({mirror: false})).toBe(true);
expect(isVoiceEngineV2BridgePublishCameraOptions({mirror: 'true'})).toBe(false);
});
it('accepts camera effect strengths across the full integer range', () => {
expect(isVoiceEngineV2BridgePublishCameraOptions({backgroundBlurStrength: 0})).toBe(true);
expect(isVoiceEngineV2BridgePublishCameraOptions({backgroundBlurStrength: 100})).toBe(true);
expect(
isVoiceEngineV2BridgePublishCameraOptions({
backgroundMode: 'blur',
backgroundBlurStrength: 75,
}),
).toBe(true);
});
it('rejects out-of-range or non-integer camera effect strengths', () => {
expect(isVoiceEngineV2BridgePublishCameraOptions({backgroundBlurStrength: -1})).toBe(false);
expect(isVoiceEngineV2BridgePublishCameraOptions({backgroundBlurStrength: 101})).toBe(false);
expect(isVoiceEngineV2BridgePublishCameraOptions({backgroundBlurStrength: 49.5})).toBe(false);
expect(isVoiceEngineV2BridgePublishCameraOptions({backgroundBlurStrength: '50'})).toBe(false);
expect(isVoiceEngineV2BridgePublishCameraOptions({backgroundBlurStrength: Number.NaN})).toBe(false);
});
it('rejects invalid native camera background modes', () => {
expect(isVoiceEngineV2BridgePublishCameraOptions({backgroundMode: 'sepia'})).toBe(false);
expect(
isVoiceEngineV2BridgePublishCameraOptions({
backgroundMode: 'custom',
backgroundCustomMediaPath: '/tmp/background.webm',
backgroundCustomMediaKind: 'audio',
}),
).toBe(false);
});
it('accepts canonical processed camera publish options', () => {
expect(isVoiceEngineV2BridgePublishProcessedCameraOptions({width: 1280, height: 720, frameRate: 30})).toBe(true);
});
it('rejects processed camera publish options with odd dimensions', () => {
expect(isVoiceEngineV2BridgePublishProcessedCameraOptions({width: 1279, height: 720, frameRate: 30})).toBe(false);
});
it('calculates tight i420 payload byte length', () => {
expect(getVoiceEngineV2BridgeProcessedCameraI420ByteLength(4, 2)).toBe(12);
});
it('accepts tight i420 processed camera frames', () => {
const data = new Uint8Array(getVoiceEngineV2BridgeProcessedCameraI420ByteLength(4, 2));
expect(
isVoiceEngineV2BridgeProcessedCameraFrame({
format: 'i420',
width: 4,
height: 2,
timestampUs: 1,
data,
}),
).toBe(true);
});
it('rejects processed camera frames with loose byte lengths', () => {
expect(
isVoiceEngineV2BridgeProcessedCameraFrame({
format: 'i420',
width: 4,
height: 2,
timestampUs: 1,
data: new ArrayBuffer(11),
}),
).toBe(false);
});
});
describe('voice engine v2 bridge option airlock helpers', () => {
it('accepts canonical audio options', () => {
expect(() => assertVoiceEngineV2BridgeAudioOptionsInvariants({sampleRate: 48_000, numChannels: 2})).not.toThrow();
});
it('rejects audio options with an unsupported sample rate', () => {
try {
assertVoiceEngineV2BridgeAudioOptionsInvariants({sampleRate: 44_100, numChannels: 2});
expect.fail('expected sample rate assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('audioSampleRateInvalid');
}
});
it('rejects audio options with an unsupported channel count', () => {
try {
assertVoiceEngineV2BridgeAudioOptionsInvariants({sampleRate: 48_000, numChannels: 5});
expect.fail('expected channels assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('audioChannelsInvalid');
}
});
it('accepts canonical video options', () => {
expect(() => assertVoiceEngineV2BridgeVideoOptionsInvariants({width: 1280, height: 720})).not.toThrow();
});
it('rejects video options with a zero width', () => {
try {
assertVoiceEngineV2BridgeVideoOptionsInvariants({width: 0, height: 720});
expect.fail('expected dimension assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('videoDimensionOutOfRange');
}
});
it('rejects video options with an oversized height', () => {
try {
assertVoiceEngineV2BridgeVideoOptionsInvariants({width: 1280, height: 1 << 20});
expect.fail('expected dimension assertion to throw');
} catch (error) {
expect(error).toBeInstanceOf(VoiceEngineV2FfiAssertError);
expect((error as VoiceEngineV2FfiAssertError).code).toBe('videoDimensionOutOfRange');
}
});
});
describe('voice engine v2 bridge event translation drop accounting', () => {
beforeEach(() => {
resetVoiceEngineV2BridgeDroppedEventCounts();
});
it('returns no events for a known-but-ignored type without counting a drop', () => {
const events = translateVoiceEngineV2BridgeEventToEvents({
type: 'stats',
payload: {rttMs: 12, outbound: [], inbound: []},
});
expect(events).toEqual([]);
const counts = getVoiceEngineV2BridgeDroppedEventCounts();
expect(counts.malformedPayloadCount).toBe(0);
expect(counts.unknownTypeCount).toBe(0);
});
it('counts a malformed payload for a translated known type', () => {
const events = translateVoiceEngineV2BridgeEventToEvents({
type: 'trackMuted',
payload: {},
});
expect(events).toEqual([]);
const counts = getVoiceEngineV2BridgeDroppedEventCounts();
expect(counts.malformedPayloadCount).toBe(1);
expect(counts.unknownTypeCount).toBe(0);
});
it('counts an unknown event type as a dropped event', () => {
const events = translateVoiceEngineV2BridgeEventToEvents({
type: 'someFutureEvent',
payload: {},
});
expect(events).toEqual([]);
const counts = getVoiceEngineV2BridgeDroppedEventCounts();
expect(counts.malformedPayloadCount).toBe(0);
expect(counts.unknownTypeCount).toBe(1);
});
it('classifies every payload key as a known bridge event type', () => {
expect(isVoiceEngineV2BridgeKnownEventType('trackSubscribed')).toBe(true);
expect(isVoiceEngineV2BridgeKnownEventType('audioPlaybackUnavailable')).toBe(true);
expect(isVoiceEngineV2BridgeKnownEventType('someFutureEvent')).toBe(false);
});
it('still translates a well-formed known event after counting drops', () => {
translateVoiceEngineV2BridgeEventToEvents({type: 'trackMuted', payload: {}});
const events = translateVoiceEngineV2BridgeEventToEvents({
type: 'trackMuted',
payload: {trackSid: 'TR_1'},
});
expect(events).toEqual([{type: 'room.trackMuted', trackSid: 'TR_1'}]);
expect(getVoiceEngineV2BridgeDroppedEventCounts().malformedPayloadCount).toBe(1);
});
});
@@ -0,0 +1,178 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {VOICE_ENGINE_V2_BRIDGE_VERSION} from './index';
export const VOICE_ENGINE_V2_AUDIO_FRAME_BYTES_MAX = 1 << 20;
export const VOICE_ENGINE_V2_VIDEO_FRAME_BYTES_MAX = 64 << 20;
export const VOICE_ENGINE_V2_VIDEO_DIMENSION_MAX = 16384;
const VOICE_ENGINE_V2_AUDIO_SAMPLE_RATES_HZ = [16000, 32000, 48000] as const;
const VOICE_ENGINE_V2_AUDIO_CHANNELS_VALID = [1, 2] as const;
interface VoiceEngineV2AudioFrameInvariants {
sampleRateHz: number;
numChannels: number;
frameBytes: number;
timestampNs: number;
}
interface VoiceEngineV2VideoFrameInvariants {
widthPx: number;
heightPx: number;
frameBytes: number;
timestampNs: number;
}
export class VoiceEngineV2FfiAssertError extends Error {
readonly code:
| 'schemaVersionMismatch'
| 'audioFrameBytesOutOfRange'
| 'audioSampleRateInvalid'
| 'audioChannelsInvalid'
| 'audioTimestampRegressed'
| 'videoFrameBytesOutOfRange'
| 'videoDimensionOutOfRange'
| 'videoTimestampRegressed';
constructor(code: VoiceEngineV2FfiAssertError['code'], message: string) {
super(message);
this.code = code;
this.name = 'VoiceEngineV2FfiAssertError';
}
}
function isFiniteInteger(value: number): boolean {
return Number.isFinite(value) && Number.isInteger(value);
}
export function assertSchemaVersion(received: number): void {
if (!isFiniteInteger(received)) {
throw new VoiceEngineV2FfiAssertError(
'schemaVersionMismatch',
`schema version must be a finite integer (received ${received})`,
);
}
if (received !== VOICE_ENGINE_V2_BRIDGE_VERSION) {
throw new VoiceEngineV2FfiAssertError(
'schemaVersionMismatch',
`schema version mismatch: expected ${VOICE_ENGINE_V2_BRIDGE_VERSION}, received ${received}`,
);
}
}
function assertAudioFrameBytes(bytes: number): void {
if (!isFiniteInteger(bytes)) {
throw new VoiceEngineV2FfiAssertError(
'audioFrameBytesOutOfRange',
`audio frame bytes must be a finite integer (received ${bytes})`,
);
}
if (bytes <= 0) {
throw new VoiceEngineV2FfiAssertError(
'audioFrameBytesOutOfRange',
`audio frame bytes must be positive (received ${bytes})`,
);
}
if (bytes > VOICE_ENGINE_V2_AUDIO_FRAME_BYTES_MAX) {
throw new VoiceEngineV2FfiAssertError(
'audioFrameBytesOutOfRange',
`audio frame bytes ${bytes} exceeds cap ${VOICE_ENGINE_V2_AUDIO_FRAME_BYTES_MAX}`,
);
}
}
function assertAudioSampleRate(hz: number): void {
if (!(VOICE_ENGINE_V2_AUDIO_SAMPLE_RATES_HZ as ReadonlyArray<number>).includes(hz)) {
throw new VoiceEngineV2FfiAssertError(
'audioSampleRateInvalid',
`audio sample rate ${hz} not in [16000, 32000, 48000]`,
);
}
}
function assertAudioChannels(channels: number): void {
if (!(VOICE_ENGINE_V2_AUDIO_CHANNELS_VALID as ReadonlyArray<number>).includes(channels)) {
throw new VoiceEngineV2FfiAssertError('audioChannelsInvalid', `audio channel count ${channels} not in [1, 2]`);
}
}
function assertAudioTimestampMonotonic(timestampNs: number, previousTimestampNs: number | null): void {
if (previousTimestampNs === null) return;
if (timestampNs <= previousTimestampNs) {
throw new VoiceEngineV2FfiAssertError(
'audioTimestampRegressed',
`audio timestamp ${timestampNs} did not exceed previous ${previousTimestampNs}`,
);
}
}
export function assertAudioFrameInvariants(
frame: VoiceEngineV2AudioFrameInvariants,
previousTimestampNs: number | null = null,
): void {
assertAudioFrameBytes(frame.frameBytes);
assertAudioSampleRate(frame.sampleRateHz);
assertAudioChannels(frame.numChannels);
assertAudioTimestampMonotonic(frame.timestampNs, previousTimestampNs);
}
function assertVideoFrameBytes(bytes: number): void {
if (!isFiniteInteger(bytes)) {
throw new VoiceEngineV2FfiAssertError(
'videoFrameBytesOutOfRange',
`video frame bytes must be a finite integer (received ${bytes})`,
);
}
if (bytes <= 0) {
throw new VoiceEngineV2FfiAssertError(
'videoFrameBytesOutOfRange',
`video frame bytes must be positive (received ${bytes})`,
);
}
if (bytes > VOICE_ENGINE_V2_VIDEO_FRAME_BYTES_MAX) {
throw new VoiceEngineV2FfiAssertError(
'videoFrameBytesOutOfRange',
`video frame bytes ${bytes} exceeds cap ${VOICE_ENGINE_V2_VIDEO_FRAME_BYTES_MAX}`,
);
}
}
function assertVideoDimension(pixels: number, label: string): void {
if (!isFiniteInteger(pixels)) {
throw new VoiceEngineV2FfiAssertError(
'videoDimensionOutOfRange',
`video ${label} must be a finite integer (received ${pixels})`,
);
}
if (pixels <= 0) {
throw new VoiceEngineV2FfiAssertError(
'videoDimensionOutOfRange',
`video ${label} must be positive (received ${pixels})`,
);
}
if (pixels > VOICE_ENGINE_V2_VIDEO_DIMENSION_MAX) {
throw new VoiceEngineV2FfiAssertError(
'videoDimensionOutOfRange',
`video ${label} ${pixels} exceeds cap ${VOICE_ENGINE_V2_VIDEO_DIMENSION_MAX}`,
);
}
}
function assertVideoTimestampMonotonic(timestampNs: number, previousTimestampNs: number | null): void {
if (previousTimestampNs === null) return;
if (timestampNs <= previousTimestampNs) {
throw new VoiceEngineV2FfiAssertError(
'videoTimestampRegressed',
`video timestamp ${timestampNs} did not exceed previous ${previousTimestampNs}`,
);
}
}
export function assertVideoFrameInvariants(
frame: VoiceEngineV2VideoFrameInvariants,
previousTimestampNs: number | null = null,
): void {
assertVideoFrameBytes(frame.frameBytes);
assertVideoDimension(frame.widthPx, 'width');
assertVideoDimension(frame.heightPx, 'height');
assertVideoTimestampMonotonic(frame.timestampNs, previousTimestampNs);
}
@@ -0,0 +1,482 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {assertAudioFrameInvariants, assertSchemaVersion, assertVideoFrameInvariants} from './ffi_assertions';
import type {
VoiceEngineV2BridgeCameraBackgroundCustomMediaKind,
VoiceEngineV2BridgeCameraBackgroundMode,
VoiceEngineV2BridgeCameraPreviewInfo,
VoiceEngineV2BridgeCapabilities,
VoiceEngineV2BridgeConnectOptions,
VoiceEngineV2BridgeFloatPcmFrame,
VoiceEngineV2BridgeHardwareEncoderCapabilities,
VoiceEngineV2BridgeParticipantVolumeOptions,
VoiceEngineV2BridgePcmFrame,
VoiceEngineV2BridgeProcessedCameraFrame,
VoiceEngineV2BridgePublishCameraOptions,
VoiceEngineV2BridgePublishDataOptions,
VoiceEngineV2BridgePublishDeviceScreenShareOptions,
VoiceEngineV2BridgePublishMicrophoneOptions,
VoiceEngineV2BridgePublishProcessedCameraOptions,
VoiceEngineV2BridgePublishProcessedCameraResult,
VoiceEngineV2BridgePublishScreenAudioOptions,
VoiceEngineV2BridgePublishScreenOptions,
VoiceEngineV2BridgeReadiness,
VoiceEngineV2BridgeRemoteTrackSubscriptionOptions,
VoiceEngineV2BridgeRemoteTrackSubscriptionQuality,
VoiceEngineV2BridgeSpeakingDetectionOptions,
VoiceEngineV2BridgeUpdateScreenShareEncodingOptions,
} from './types';
import {VOICE_ENGINE_V2_CAMERA_EFFECT_STRENGTH_MAX, VOICE_ENGINE_V2_CAMERA_EFFECT_STRENGTH_MIN} from './types';
export function assertVoiceEngineV2BridgeAudioOptionsInvariants(options: {
sampleRate: number;
numChannels: number;
}): void {
assertAudioFrameInvariants({
sampleRateHz: options.sampleRate,
numChannels: options.numChannels,
frameBytes: 1,
timestampNs: 1,
});
}
export function assertVoiceEngineV2BridgeVideoOptionsInvariants(options: {width: number; height: number}): void {
assertVideoFrameInvariants({
widthPx: options.width,
heightPx: options.height,
frameBytes: 1,
timestampNs: 1,
});
}
export function assertVoiceEngineV2BridgeEnvelopeSchema(received: number): void {
assertSchemaVersion(received);
}
export function unavailableVoiceEngineV2BridgeCapabilities(): VoiceEngineV2BridgeCapabilities {
return {
microphoneCapture: false,
syntheticMicrophonePcm: false,
cameraCapture: false,
nativeCameraBackgrounds: false,
screenShare: false,
screenShareEncodingUpdate: false,
screenShareAudio: false,
deviceLists: false,
outputDeviceSelection: false,
participantVolume: false,
remoteTrackSubscription: false,
dataChannel: false,
connectionStats: false,
nativeVideoFrames: false,
hardwareEncoderCapabilities: false,
};
}
export function normalizeVoiceEngineV2BridgeCapabilities(value: unknown): VoiceEngineV2BridgeCapabilities {
if (typeof value !== 'object' || value === null) {
return unavailableVoiceEngineV2BridgeCapabilities();
}
const candidate = value as Partial<VoiceEngineV2BridgeCapabilities> & {
microphone?: boolean;
camera?: boolean;
cameraBackgrounds?: boolean;
screen?: boolean;
screenAudio?: boolean;
outputDevice?: boolean;
dataChannel?: boolean;
stats?: boolean;
hardwareEncoding?: boolean;
};
return {
microphoneCapture: candidate.microphoneCapture === true || candidate.microphone === true,
syntheticMicrophonePcm: candidate.syntheticMicrophonePcm === true,
cameraCapture: candidate.cameraCapture === true || candidate.camera === true,
nativeCameraBackgrounds: candidate.nativeCameraBackgrounds === true || candidate.cameraBackgrounds === true,
screenShare: candidate.screenShare === true || candidate.screen === true,
screenShareEncodingUpdate: candidate.screenShareEncodingUpdate === true,
screenShareAudio: candidate.screenShareAudio === true || candidate.screenAudio === true,
deviceLists: candidate.deviceLists === true,
outputDeviceSelection: candidate.outputDeviceSelection === true || candidate.outputDevice === true,
participantVolume: candidate.participantVolume === true,
remoteTrackSubscription: candidate.remoteTrackSubscription === true,
dataChannel: candidate.dataChannel === true,
connectionStats: candidate.connectionStats === true || candidate.stats === true,
nativeVideoFrames: candidate.nativeVideoFrames === true,
hardwareEncoderCapabilities: candidate.hardwareEncoderCapabilities === true || candidate.hardwareEncoding === true,
};
}
export function unavailableVoiceEngineV2BridgeHardwareEncoderCapabilities(
reason: NonNullable<VoiceEngineV2BridgeHardwareEncoderCapabilities['reason']>,
detail?: string,
): VoiceEngineV2BridgeHardwareEncoderCapabilities {
return {
available: false,
backend: 'none',
compiled: false,
runtime: false,
codecs: [],
zeroCopy: false,
nativeInputs: [],
reason,
...(detail ? {detail} : {}),
};
}
export function normalizeVoiceEngineV2BridgeHardwareEncoderCapabilities(
value: unknown,
): VoiceEngineV2BridgeHardwareEncoderCapabilities {
if (typeof value !== 'object' || value === null) {
return unavailableVoiceEngineV2BridgeHardwareEncoderCapabilities(
'query-failed',
'Native addon returned an invalid result',
);
}
const candidate = value as Partial<VoiceEngineV2BridgeHardwareEncoderCapabilities>;
const backend = typeof candidate.backend === 'string' && candidate.backend.length > 0 ? candidate.backend : 'none';
return {
available: candidate.available === true,
backend,
compiled: candidate.compiled === true,
runtime: candidate.runtime === true,
codecs: Array.isArray(candidate.codecs) ? candidate.codecs.filter((codec) => typeof codec === 'string') : [],
zeroCopy: candidate.zeroCopy === true,
nativeInputs: Array.isArray(candidate.nativeInputs)
? candidate.nativeInputs.filter((input) => typeof input === 'string')
: [],
...(typeof candidate.reason === 'string' ? {reason: candidate.reason} : {}),
...(typeof candidate.detail === 'string' ? {detail: candidate.detail} : {}),
};
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}
function isFiniteNumber(value: unknown): value is number {
return typeof value === 'number' && Number.isFinite(value);
}
function isPositiveFiniteNumber(value: unknown): value is number {
return isFiniteNumber(value) && value > 0;
}
function isOptionalPositiveFiniteNumber(value: unknown): value is number | undefined {
return value === undefined || isPositiveFiniteNumber(value);
}
function isOptionalDeepFilterNoiseReductionLevel(value: unknown): value is number | undefined {
if (value === undefined) return true;
return isFiniteNumber(value) && value >= 0 && value <= 100;
}
function isOptionalBoolean(value: unknown): value is boolean | undefined {
return value === undefined || typeof value === 'boolean';
}
function isOptionalString(value: unknown): value is string | undefined {
return value === undefined || typeof value === 'string';
}
function isOptionalCameraBackgroundMode(value: unknown): value is VoiceEngineV2BridgeCameraBackgroundMode | undefined {
return value === undefined || value === 'none' || value === 'non' || value === 'blur' || value === 'custom';
}
export function isVoiceEngineV2BridgeCameraEffectStrength(value: unknown): value is number {
return (
typeof value === 'number' &&
Number.isInteger(value) &&
value >= VOICE_ENGINE_V2_CAMERA_EFFECT_STRENGTH_MIN &&
value <= VOICE_ENGINE_V2_CAMERA_EFFECT_STRENGTH_MAX
);
}
function isOptionalCameraEffectStrength(value: unknown): value is number | undefined {
return value === undefined || isVoiceEngineV2BridgeCameraEffectStrength(value);
}
function isOptionalCameraBackgroundCustomMediaKind(
value: unknown,
): value is VoiceEngineV2BridgeCameraBackgroundCustomMediaKind | undefined {
return value === undefined || value === 'static' || value === 'animated' || value === 'video';
}
function isStringArray(value: unknown): value is Array<string> {
return Array.isArray(value) && value.every((item) => typeof item === 'string');
}
function isOptionalStringArray(value: unknown): value is Array<string> | undefined {
return value === undefined || isStringArray(value);
}
function isArrayBuffer(value: unknown): value is ArrayBuffer {
return value instanceof ArrayBuffer || Object.prototype.toString.call(value) === '[object ArrayBuffer]';
}
function isFloat32Array(value: unknown): value is Float32Array {
return value instanceof Float32Array || Object.prototype.toString.call(value) === '[object Float32Array]';
}
export function isVoiceEngineV2BridgeBinaryPayload(value: unknown): value is ArrayBuffer | ArrayBufferView {
return isArrayBuffer(value) || ArrayBuffer.isView(value);
}
export function getVoiceEngineV2BridgeProcessedCameraI420ByteLength(width: number, height: number): number {
return (width * height * 3) / 2;
}
function isPositiveFiniteInteger(value: unknown): value is number {
return isPositiveFiniteNumber(value) && Number.isInteger(value);
}
function isPositiveEvenFiniteInteger(value: unknown): value is number {
return isPositiveFiniteInteger(value) && value % 2 === 0;
}
function getVoiceEngineV2BridgeBinaryPayloadByteLength(value: ArrayBuffer | ArrayBufferView): number {
return value.byteLength;
}
export function isVoiceEngineV2BridgeRemoteTrackSubscriptionQuality(
value: unknown,
): value is VoiceEngineV2BridgeRemoteTrackSubscriptionQuality {
return value === 'low' || value === 'medium' || value === 'high';
}
export function isVoiceEngineV2BridgeConnectOptions(value: unknown): value is VoiceEngineV2BridgeConnectOptions {
if (!isRecord(value)) return false;
return (
typeof value.url === 'string' &&
value.url.length > 0 &&
typeof value.token === 'string' &&
value.token.length > 0 &&
(value.e2eeKey === undefined || value.e2eeKey === null || isArrayBuffer(value.e2eeKey))
);
}
const VOICE_ENGINE_V2_SCREEN_CODECS = new Set(['', 'vp8', 'vp9', 'h264', 'h265', 'av1']);
const VOICE_ENGINE_V2_SCREEN_PACING_MODES = new Set(['sender', 'source']);
export function isVoiceEngineV2BridgePublishScreenOptions(
value: unknown,
): value is VoiceEngineV2BridgePublishScreenOptions {
if (!isRecord(value)) return false;
return (
typeof value.captureId === 'string' &&
value.captureId.length > 0 &&
isPositiveFiniteNumber(value.width) &&
isPositiveFiniteNumber(value.height) &&
(value.codec === undefined ||
(typeof value.codec === 'string' && VOICE_ENGINE_V2_SCREEN_CODECS.has(value.codec))) &&
isOptionalBoolean(value.hardwareEncoding) &&
isOptionalBoolean(value.zeroCopyRequired) &&
isOptionalPositiveFiniteNumber(value.maxBitrateBps) &&
isOptionalPositiveFiniteNumber(value.maxFramerate) &&
isOptionalBoolean(value.adaptiveSend) &&
isOptionalPositiveFiniteNumber(value.minVideoFps) &&
isOptionalPositiveFiniteNumber(value.maxAudioBufferMs) &&
(value.trackName === undefined || (typeof value.trackName === 'string' && value.trackName.length > 0)) &&
(value.pacing === undefined ||
(typeof value.pacing === 'string' && VOICE_ENGINE_V2_SCREEN_PACING_MODES.has(value.pacing)))
);
}
export function isVoiceEngineV2BridgeUpdateScreenShareEncodingOptions(
value: unknown,
): value is VoiceEngineV2BridgeUpdateScreenShareEncodingOptions {
if (!isRecord(value)) return false;
return (
typeof value.captureId === 'string' &&
value.captureId.length > 0 &&
isPositiveFiniteNumber(value.width) &&
isPositiveFiniteNumber(value.height) &&
isOptionalPositiveFiniteNumber(value.frameRate) &&
isOptionalPositiveFiniteNumber(value.maxBitrateBps) &&
(value.codec === undefined ||
(typeof value.codec === 'string' && VOICE_ENGINE_V2_SCREEN_CODECS.has(value.codec))) &&
isOptionalBoolean(value.hardwareEncoding) &&
isOptionalBoolean(value.zeroCopyRequired)
);
}
export function isVoiceEngineV2BridgePublishMicrophoneOptions(
value: unknown,
): value is VoiceEngineV2BridgePublishMicrophoneOptions {
if (!isRecord(value)) return false;
const mode = value.mode ?? 'device';
const pcmTestValuesAreValid =
mode !== 'pcm-test' || (isPositiveFiniteNumber(value.sampleRate) && isPositiveFiniteNumber(value.numChannels));
return (
(mode === 'device' || mode === 'pcm-test') &&
isOptionalString(value.deviceId) &&
isOptionalBoolean(value.echoCancellation) &&
isOptionalBoolean(value.noiseSuppression) &&
isOptionalBoolean(value.autoGainControl) &&
isOptionalBoolean(value.deepFilter) &&
isOptionalDeepFilterNoiseReductionLevel(value.deepFilterNoiseReductionLevel) &&
isOptionalPositiveFiniteNumber(value.sampleRate) &&
isOptionalPositiveFiniteNumber(value.numChannels) &&
isOptionalPositiveFiniteNumber(value.maxBitrateBps) &&
pcmTestValuesAreValid
);
}
export function isVoiceEngineV2BridgePcmFrame(value: unknown): value is VoiceEngineV2BridgePcmFrame {
if (!isRecord(value)) return false;
return (
isPositiveFiniteNumber(value.sampleRate) &&
isPositiveFiniteNumber(value.numChannels) &&
isArrayBuffer(value.samples)
);
}
export function isVoiceEngineV2BridgeFloatPcmFrame(value: unknown): value is VoiceEngineV2BridgeFloatPcmFrame {
if (!isRecord(value)) return false;
return (
isPositiveFiniteNumber(value.sampleRate) &&
isPositiveFiniteNumber(value.numChannels) &&
isFloat32Array(value.samples)
);
}
export function isVoiceEngineV2BridgePublishScreenAudioOptions(
value: unknown,
): value is VoiceEngineV2BridgePublishScreenAudioOptions {
if (!isRecord(value)) return false;
return (
isPositiveFiniteNumber(value.sampleRate) &&
isPositiveFiniteNumber(value.numChannels) &&
(value.route === undefined || value.route === 'browser' || value.route === 'native') &&
isOptionalString(value.captureId) &&
isOptionalString(value.tapId)
);
}
export function isVoiceEngineV2ParticipantVolumeOptions(
value: unknown,
): value is VoiceEngineV2BridgeParticipantVolumeOptions {
if (!isRecord(value)) return false;
return typeof value.participantSid === 'string' && value.participantSid.length > 0 && isFiniteNumber(value.volume);
}
export function clampVoiceEngineV2ParticipantVolume(volume: number): number {
if (!Number.isFinite(volume)) return 1;
return Math.max(0, Math.min(2, volume));
}
export function isVoiceEngineV2BridgeSpeakingDetectionOptions(
value: unknown,
): value is VoiceEngineV2BridgeSpeakingDetectionOptions {
if (!isRecord(value)) return false;
return isPositiveFiniteNumber(value.localThresholdRms) && isPositiveFiniteNumber(value.remoteThresholdRms);
}
export function isVoiceEngineV2BridgeRemoteTrackSubscriptionOptions(
value: unknown,
): value is VoiceEngineV2BridgeRemoteTrackSubscriptionOptions {
if (!isRecord(value)) return false;
return (
typeof value.participantIdentity === 'string' &&
value.participantIdentity.length > 0 &&
typeof value.source === 'string' &&
value.source.length > 0 &&
typeof value.subscribed === 'boolean' &&
isOptionalBoolean(value.enabled) &&
(value.quality === undefined || isVoiceEngineV2BridgeRemoteTrackSubscriptionQuality(value.quality))
);
}
export function isVoiceEngineV2BridgePublishDataOptions(
value: unknown,
): value is VoiceEngineV2BridgePublishDataOptions {
if (!isRecord(value)) return false;
return (
isVoiceEngineV2BridgeBinaryPayload(value.payload) &&
isOptionalBoolean(value.reliable) &&
isOptionalString(value.topic) &&
isOptionalStringArray(value.destinationIdentities)
);
}
export function isVoiceEngineV2BridgePublishCameraOptions(
value: unknown,
): value is VoiceEngineV2BridgePublishCameraOptions {
if (!isRecord(value)) return false;
return (
isOptionalString(value.deviceId) &&
isOptionalPositiveFiniteNumber(value.width) &&
isOptionalPositiveFiniteNumber(value.height) &&
isOptionalPositiveFiniteNumber(value.frameRate) &&
isOptionalBoolean(value.mirror) &&
isOptionalCameraBackgroundMode(value.backgroundMode) &&
isOptionalString(value.backgroundCustomMediaPath) &&
isOptionalCameraBackgroundCustomMediaKind(value.backgroundCustomMediaKind) &&
isOptionalCameraEffectStrength(value.backgroundBlurStrength)
);
}
export function isVoiceEngineV2BridgePublishProcessedCameraOptions(
value: unknown,
): value is VoiceEngineV2BridgePublishProcessedCameraOptions {
if (!isRecord(value)) return false;
return (
isPositiveEvenFiniteInteger(value.width) &&
isPositiveEvenFiniteInteger(value.height) &&
isPositiveFiniteInteger(value.frameRate)
);
}
export function isVoiceEngineV2BridgePublishProcessedCameraResult(
value: unknown,
): value is VoiceEngineV2BridgePublishProcessedCameraResult {
if (!isRecord(value)) return false;
return typeof value.trackSid === 'string' && value.trackSid.length > 0;
}
export function isVoiceEngineV2BridgeCameraPreviewInfo(value: unknown): value is VoiceEngineV2BridgeCameraPreviewInfo {
if (!isRecord(value)) return false;
return (
typeof value.trackSid === 'string' &&
value.trackSid.length > 0 &&
isPositiveEvenFiniteInteger(value.width) &&
isPositiveEvenFiniteInteger(value.height) &&
isPositiveFiniteInteger(value.frameRate)
);
}
export function isVoiceEngineV2BridgeProcessedCameraFrame(
value: unknown,
): value is VoiceEngineV2BridgeProcessedCameraFrame {
if (!isRecord(value)) return false;
if (value.format !== 'i420') return false;
if (!isPositiveEvenFiniteInteger(value.width)) return false;
if (!isPositiveEvenFiniteInteger(value.height)) return false;
if (!isPositiveFiniteInteger(value.timestampUs)) return false;
if (!isVoiceEngineV2BridgeBinaryPayload(value.data)) return false;
const expectedBytes = getVoiceEngineV2BridgeProcessedCameraI420ByteLength(value.width, value.height);
return getVoiceEngineV2BridgeBinaryPayloadByteLength(value.data) === expectedBytes;
}
export function isVoiceEngineV2BridgeReadiness(value: unknown): value is VoiceEngineV2BridgeReadiness {
if (!isRecord(value)) return false;
if (typeof value.ready !== 'boolean') return false;
if (value.ready) return value.reason === undefined;
return isOptionalString(value.reason);
}
export function isVoiceEngineV2BridgePublishDeviceScreenShareOptions(
value: unknown,
): value is VoiceEngineV2BridgePublishDeviceScreenShareOptions {
if (!isRecord(value)) return false;
return (
isOptionalString(value.deviceId) &&
isOptionalPositiveFiniteNumber(value.width) &&
isOptionalPositiveFiniteNumber(value.height) &&
isOptionalPositiveFiniteNumber(value.frameRate) &&
(value.codec === undefined ||
(typeof value.codec === 'string' && VOICE_ENGINE_V2_SCREEN_CODECS.has(value.codec))) &&
isOptionalPositiveFiniteNumber(value.maxBitrateBps) &&
isOptionalPositiveFiniteNumber(value.maxFramerate)
);
}
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
export * from './admWarmup';
export * from './guards';
export * from './stats';
export * from './translate';
export * from './types';
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
/// <reference types="node" />
import {readdirSync, readFileSync, statSync} from 'node:fs';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import {describe, expect, it} from 'vitest';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, '../../../..');
const BLOCKED_IMPORT = '@fluxer/' + 'voice_' + 'engine_contract';
const BLOCKED_PATH = 'voice_' + 'engine_contract';
const BLOCKED_MARKERS = [
BLOCKED_IMPORT,
BLOCKED_PATH,
'Voice' + 'EngineApi',
'VOICE_ENGINE_' + 'CONTRACT_VERSION',
'contract' + 'Version',
];
const SCAN_TARGETS = ['fluxer_app', 'fluxer_desktop', 'packages', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'];
const TEXT_EXTENSIONS = new Set([
'.cjs',
'.cts',
'.js',
'.json',
'.jsx',
'.mjs',
'.mts',
'.rs',
'.ts',
'.tsx',
'.toml',
'.yaml',
'.yml',
]);
const SKIPPED_DIRECTORIES = new Set(['.git', 'coverage', 'dist', 'node_modules', 'target', 'vendor']);
function shouldScanFile(filePath: string): boolean {
if (path.basename(filePath) === 'package.json') return true;
return TEXT_EXTENSIONS.has(path.extname(filePath));
}
function collectFiles(targetPath: string, files: Array<string> = []): Array<string> {
const stat = statSync(targetPath);
if (stat.isFile()) {
if (shouldScanFile(targetPath)) files.push(targetPath);
return files;
}
if (!stat.isDirectory()) return files;
if (SKIPPED_DIRECTORIES.has(path.basename(targetPath))) return files;
for (const entry of readdirSync(targetPath)) {
collectFiles(path.join(targetPath, entry), files);
}
return files;
}
describe('voice engine v2 package boundary', () => {
it('does not reintroduce the deleted v1 voice engine contract', () => {
const matches = SCAN_TARGETS.flatMap((target) => collectFiles(path.join(REPO_ROOT, target))).flatMap((filePath) => {
const source = readFileSync(filePath, 'utf8');
const blockedMarker = BLOCKED_MARKERS.find((marker) => source.includes(marker));
if (!blockedMarker) return [];
return [`${path.relative(REPO_ROOT, filePath)} -> ${blockedMarker}`];
});
expect(matches).toEqual([]);
});
});
@@ -0,0 +1,141 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {
VoiceEngineV2BridgeInboundStat,
VoiceEngineV2BridgeOutboundStat,
VoiceEngineV2BridgeSendStats,
VoiceEngineV2BridgeStats,
VoiceEngineV2BridgeTrackKind,
} from './types';
function asTrackKind(value: unknown): VoiceEngineV2BridgeTrackKind {
return value === 'video' ? 'video' : 'audio';
}
function asNumber(value: unknown): number {
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
}
function asNonNegativeNumber(value: unknown): number {
return Math.max(0, asNumber(value));
}
function asOptionalNumber(value: unknown): number | undefined {
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
}
function asOptionalCount(value: unknown): number | undefined {
const number = asOptionalNumber(value);
return number == null ? undefined : Math.max(0, Math.trunc(number));
}
function asString(value: unknown): string | undefined {
return typeof value === 'string' && value.length > 0 ? value : undefined;
}
function coerceVoiceEngineV2BridgeSendStats(value: unknown): VoiceEngineV2BridgeSendStats | null | undefined {
if (value == null || typeof value !== 'object') return value === null ? null : undefined;
const payload = value as Record<string, unknown>;
return {
outgoingVideoQueueDepth: asNonNegativeNumber(payload.outgoingVideoQueueDepth),
outgoingVideoQueueCapacity: asNonNegativeNumber(payload.outgoingVideoQueueCapacity),
outgoingVideoMaxQueueDepth: asNonNegativeNumber(payload.outgoingVideoMaxQueueDepth),
outgoingVideoFramesProduced: asNonNegativeNumber(payload.outgoingVideoFramesProduced),
outgoingVideoFramesAccepted: asNonNegativeNumber(payload.outgoingVideoFramesAccepted),
outgoingVideoFramesDropped: asNonNegativeNumber(payload.outgoingVideoFramesDropped),
outgoingVideoFramesCoalesced: asNonNegativeNumber(payload.outgoingVideoFramesCoalesced),
outgoingVideoFramesCaptured: asNonNegativeNumber(payload.outgoingVideoFramesCaptured),
outgoingVideoCaptureFailures: asNonNegativeNumber(payload.outgoingVideoCaptureFailures),
outgoingVideoEffectiveFps: asNonNegativeNumber(payload.outgoingVideoEffectiveFps),
outgoingVideoTargetFps: asNonNegativeNumber(payload.outgoingVideoTargetFps),
outgoingVideoPacingTargetFps: asNonNegativeNumber(payload.outgoingVideoPacingTargetFps),
outgoingVideoMaxQueueAgeMs: asNonNegativeNumber(payload.outgoingVideoMaxQueueAgeMs),
outgoingVideoMaxPushLatencyMs: asNonNegativeNumber(payload.outgoingVideoMaxPushLatencyMs),
outgoingVideoPacingMode: asString(payload.outgoingVideoPacingMode) ?? 'unknown',
outgoingVideoBusActive: payload.outgoingVideoBusActive === true,
outgoingAudioBufferTargetMs: asNonNegativeNumber(payload.outgoingAudioBufferTargetMs),
outgoingAudioBufferMaxMs: asNonNegativeNumber(payload.outgoingAudioBufferMaxMs),
outgoingAudioUnderruns: asNonNegativeNumber(payload.outgoingAudioUnderruns),
outgoingAudioRebuffers: asNonNegativeNumber(payload.outgoingAudioRebuffers),
outgoingAudioMaxFrameGapMs: asNonNegativeNumber(payload.outgoingAudioMaxFrameGapMs),
adaptiveSendTier: asString(payload.adaptiveSendTier) ?? 'unknown',
adaptiveSendReason: asString(payload.adaptiveSendReason) ?? 'unknown',
};
}
function coerceVoiceEngineV2BridgeOutboundStat(entry: unknown): Array<VoiceEngineV2BridgeOutboundStat> {
if (entry == null || typeof entry !== 'object') return [];
const e = entry as Record<string, unknown>;
const zeroCopy = e.zeroCopy === true ? true : undefined;
return [
{
trackSid: asString(e.trackSid) ?? '',
source: asString(e.source) ?? '',
kind: asTrackKind(e.kind),
codec: asString(e.codec),
bitrateKbps: asNumber(e.bitrateKbps),
packetsLost: asNumber(e.packetsLost),
fps: asOptionalNumber(e.fps),
audioLevel: asOptionalNumber(e.audioLevel),
width: asOptionalCount(e.width),
height: asOptionalCount(e.height),
sourceWidth: asOptionalCount(e.sourceWidth),
sourceHeight: asOptionalCount(e.sourceHeight),
targetBitrateKbps: asOptionalNumber(e.targetBitrateKbps),
configuredFps: asOptionalNumber(e.configuredFps),
targetFps: asOptionalNumber(e.targetFps),
effectiveFps: asOptionalNumber(e.effectiveFps),
framesProduced: asOptionalCount(e.framesProduced),
framesAccepted: asOptionalCount(e.framesAccepted),
framesDropped: asOptionalCount(e.framesDropped),
framesCoalesced: asOptionalCount(e.framesCoalesced),
framesCaptured: asOptionalCount(e.framesCaptured),
captureFailures: asOptionalCount(e.captureFailures),
maxQueueAgeMs: asOptionalNumber(e.maxQueueAgeMs),
maxPushLatencyMs: asOptionalNumber(e.maxPushLatencyMs),
adaptiveSendTier: asString(e.adaptiveSendTier),
adaptiveSendReason: asString(e.adaptiveSendReason),
...(zeroCopy === true ? {zeroCopy} : {}),
},
];
}
function coerceVoiceEngineV2BridgeInboundStat(entry: unknown): Array<VoiceEngineV2BridgeInboundStat> {
if (entry == null || typeof entry !== 'object') return [];
const e = entry as Record<string, unknown>;
const participantIdentity = asString(e.participantIdentity);
return [
{
...(participantIdentity ? {participantIdentity} : {}),
participantSid: asString(e.participantSid) ?? '',
trackSid: asString(e.trackSid) ?? '',
source: asString(e.source),
kind: asTrackKind(e.kind),
codec: asString(e.codec),
bitrateKbps: asNumber(e.bitrateKbps),
packetsLost: asNumber(e.packetsLost),
jitterMs: asOptionalNumber(e.jitterMs),
audioLevel: asOptionalNumber(e.audioLevel),
width: asOptionalCount(e.width),
height: asOptionalCount(e.height),
fps: asOptionalNumber(e.fps),
sourceWidth: asOptionalCount(e.sourceWidth),
sourceHeight: asOptionalCount(e.sourceHeight),
},
];
}
export function coerceVoiceEngineV2BridgeStats(payload: Record<string, unknown>): VoiceEngineV2BridgeStats {
const rttRaw = payload.rttMs;
const rttMs = typeof rttRaw === 'number' && Number.isFinite(rttRaw) ? rttRaw : null;
const droppedNativeVideoFrames = asOptionalCount(payload.droppedNativeVideoFrames);
const droppedVideoFrameCallbacks = asOptionalCount(payload.droppedVideoFrameCallbacks);
const outbound = Array.isArray(payload.outbound)
? payload.outbound.flatMap((entry) => coerceVoiceEngineV2BridgeOutboundStat(entry))
: [];
const inbound = Array.isArray(payload.inbound)
? payload.inbound.flatMap((entry) => coerceVoiceEngineV2BridgeInboundStat(entry))
: [];
const send = coerceVoiceEngineV2BridgeSendStats(payload.send);
return {rttMs, outbound, inbound, droppedNativeVideoFrames, droppedVideoFrameCallbacks, send};
}
@@ -0,0 +1,310 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {
VoiceEngineV2Event,
VoiceEngineV2NativeAudioDeviceModuleStatus,
VoiceEngineV2TrackKind,
VoiceEngineV2TrackSource,
} from '../protocol';
import {assertVideoFrameInvariants} from './ffi_assertions';
import type {VoiceEngineV2BridgeEvent, VoiceEngineV2BridgeKnownEventType, VoiceEngineV2BridgeVideoFrame} from './types';
type VoiceEngineV2RoomTrackPublishedEvent = Extract<VoiceEngineV2Event, {type: 'room.trackPublished'}>;
type VoiceEngineV2BridgeRecord = Record<string, unknown>;
function bridgeString(value: unknown): string | null {
return typeof value === 'string' && value.length > 0 ? value : null;
}
function bridgeBoolean(value: unknown): boolean {
return value === true;
}
function bridgeAudioDeviceModuleStatus(value: unknown): VoiceEngineV2NativeAudioDeviceModuleStatus | null {
if (value === 'warming' || value === 'ready' || value === 'failed') return value;
return null;
}
function normalizeVoiceEngineV2BridgeTrackSource(value: unknown): VoiceEngineV2TrackSource | string {
switch (value) {
case 'microphone':
return 'microphone';
case 'camera':
return 'camera';
case 'screen':
case 'screen_share':
case 'screenshare':
return 'screen';
case 'screenAudio':
case 'screen_share_audio':
case 'screenshareAudio':
case 'screenshare_audio':
case 'screen_audio':
case 'system_audio':
return 'screenAudio';
default:
return 'unknown';
}
}
function normalizeVoiceEngineV2BridgeTrackKind(value: unknown): VoiceEngineV2TrackKind | null {
return value === 'audio' || value === 'video' ? value : null;
}
function participantEventFromBridgePayload(payload: VoiceEngineV2BridgeRecord): VoiceEngineV2Event | null {
const identity = bridgeString(payload.identity);
const sid = bridgeString(payload.sid);
if (!identity || !sid) return null;
return {
type: 'room.participantJoined',
participant: {
identity,
sid,
name: bridgeString(payload.name) ?? identity,
},
};
}
function trackEventFromBridgePayload(payload: VoiceEngineV2BridgeRecord): VoiceEngineV2RoomTrackPublishedEvent | null {
const participantIdentity = bridgeString(payload.identity) ?? bridgeString(payload.participantIdentity);
const participantSid = bridgeString(payload.participantSid);
const trackSid = bridgeString(payload.trackSid);
const kind = normalizeVoiceEngineV2BridgeTrackKind(payload.kind);
if (!participantIdentity || !participantSid || !trackSid || !kind) return null;
const source = normalizeVoiceEngineV2BridgeTrackSource(payload.source);
return {
type: 'room.trackPublished',
track: {
participantIdentity,
participantSid,
trackSid,
trackName: bridgeString(payload.trackName) ?? String(source),
kind,
source,
muted: bridgeBoolean(payload.muted),
},
};
}
function nativeAudioDeviceModuleStatusEventFromBridgePayload(
payload: VoiceEngineV2BridgeRecord,
): VoiceEngineV2Event | null {
const status = bridgeAudioDeviceModuleStatus(payload.status);
if (!status) return null;
const detail = bridgeString(payload.detail);
return {
type: 'nativeAudioDeviceModule.statusChanged',
status,
...(detail ? {detail} : {}),
};
}
const VOICE_ENGINE_V2_BRIDGE_KNOWN_EVENT_TYPE_FLAGS: {[Type in VoiceEngineV2BridgeKnownEventType]: true} = {
connected: true,
connectionState: true,
disconnected: true,
participantJoined: true,
participantLeft: true,
participantNameChanged: true,
participantMetadataChanged: true,
participantAttributesChanged: true,
trackPublished: true,
trackUnpublished: true,
trackSubscribed: true,
trackUnsubscribed: true,
trackSubscriptionFailed: true,
trackMuted: true,
trackUnmuted: true,
localTrackPublished: true,
localTrackUnpublished: true,
localTrackRepublished: true,
activeSpeakers: true,
speakingChanged: true,
connectionQuality: true,
dataReceived: true,
e2eeState: true,
stats: true,
audioPlaybackUnavailable: true,
engineReady: true,
audioDeviceModuleStatus: true,
};
const VOICE_ENGINE_V2_BRIDGE_KNOWN_EVENT_TYPE_SET: ReadonlySet<string> = new Set(
Object.keys(VOICE_ENGINE_V2_BRIDGE_KNOWN_EVENT_TYPE_FLAGS),
);
export function isVoiceEngineV2BridgeKnownEventType(value: string): value is VoiceEngineV2BridgeKnownEventType {
return VOICE_ENGINE_V2_BRIDGE_KNOWN_EVENT_TYPE_SET.has(value);
}
export const VOICE_ENGINE_V2_BRIDGE_DROPPED_EVENTS_COUNT_MAX = 1_000_000_000;
export interface VoiceEngineV2BridgeDroppedEventCounts {
malformedPayloadCount: number;
unknownTypeCount: number;
}
let bridgeMalformedPayloadCount = 0;
let bridgeUnknownTypeCount = 0;
function saturatingDroppedEventCount(count: number): number {
return Math.min(count + 1, VOICE_ENGINE_V2_BRIDGE_DROPPED_EVENTS_COUNT_MAX);
}
export function getVoiceEngineV2BridgeDroppedEventCounts(): VoiceEngineV2BridgeDroppedEventCounts {
return {
malformedPayloadCount: bridgeMalformedPayloadCount,
unknownTypeCount: bridgeUnknownTypeCount,
};
}
export function resetVoiceEngineV2BridgeDroppedEventCounts(): void {
bridgeMalformedPayloadCount = 0;
bridgeUnknownTypeCount = 0;
}
type VoiceEngineV2BridgeTranslationResult = Array<VoiceEngineV2Event> | 'ignored' | 'malformed';
export function translateVoiceEngineV2BridgeEventToEvents(event: VoiceEngineV2BridgeEvent): Array<VoiceEngineV2Event> {
if (!isVoiceEngineV2BridgeKnownEventType(event.type)) {
bridgeUnknownTypeCount = saturatingDroppedEventCount(bridgeUnknownTypeCount);
return [];
}
const payload = event.payload as VoiceEngineV2BridgeRecord;
const translated = translateKnownVoiceEngineV2BridgeEvent(event.type, payload);
if (translated === 'ignored') return [];
if (translated === 'malformed') {
bridgeMalformedPayloadCount = saturatingDroppedEventCount(bridgeMalformedPayloadCount);
return [];
}
return translated;
}
function translateKnownVoiceEngineV2BridgeEvent(
type: VoiceEngineV2BridgeKnownEventType,
payload: VoiceEngineV2BridgeRecord,
): VoiceEngineV2BridgeTranslationResult {
switch (type) {
case 'participantJoined':
case 'participantNameChanged':
case 'participantMetadataChanged':
case 'participantAttributesChanged': {
const participantEvent = participantEventFromBridgePayload(payload);
return participantEvent ? [participantEvent] : 'malformed';
}
case 'participantLeft':
return participantLeftEventsFromBridgePayload(payload);
case 'trackPublished':
case 'localTrackPublished': {
const trackEvent = trackEventFromBridgePayload(payload);
return trackEvent ? [trackEvent] : 'malformed';
}
case 'trackSubscribed':
return trackSubscribedEventsFromBridgePayload(payload);
case 'trackUnpublished':
case 'localTrackUnpublished': {
const trackSid = bridgeString(payload.trackSid);
return trackSid
? [
{type: 'room.trackUnpublished', trackSid},
{type: 'inboundVideo.trackUnsubscribed', trackSid},
]
: 'malformed';
}
case 'trackUnsubscribed':
case 'trackSubscriptionFailed': {
const trackSid = bridgeString(payload.trackSid);
return trackSid ? [{type: 'inboundVideo.trackUnsubscribed', trackSid}] : 'malformed';
}
case 'trackMuted': {
const trackSid = bridgeString(payload.trackSid);
return trackSid ? [{type: 'room.trackMuted', trackSid}] : 'malformed';
}
case 'trackUnmuted': {
const trackSid = bridgeString(payload.trackSid);
return trackSid ? [{type: 'room.trackUnmuted', trackSid}] : 'malformed';
}
case 'activeSpeakers':
return Array.isArray(payload.participants)
? payload.participants.flatMap((participant) =>
typeof participant === 'object' && participant !== null
? (participantEventFromBridgePayload(participant as VoiceEngineV2BridgeRecord) ?? [])
: [],
)
: 'malformed';
case 'connected':
case 'connectionState':
case 'disconnected':
case 'localTrackRepublished':
case 'speakingChanged':
case 'connectionQuality':
case 'dataReceived':
case 'e2eeState':
case 'stats':
case 'audioPlaybackUnavailable':
case 'engineReady':
return 'ignored';
case 'audioDeviceModuleStatus': {
const event = nativeAudioDeviceModuleStatusEventFromBridgePayload(payload);
return event ? [event] : 'malformed';
}
}
}
function participantLeftEventsFromBridgePayload(
payload: VoiceEngineV2BridgeRecord,
): VoiceEngineV2BridgeTranslationResult {
const participantIdentity = bridgeString(payload.identity);
const participantSid = bridgeString(payload.sid);
if (!participantIdentity && !participantSid) return 'malformed';
return [
{
type: 'room.participantLeft',
...(participantIdentity ? {participantIdentity} : {}),
...(participantSid ? {participantSid} : {}),
},
];
}
function trackSubscribedEventsFromBridgePayload(
payload: VoiceEngineV2BridgeRecord,
): VoiceEngineV2BridgeTranslationResult {
const trackEvent = trackEventFromBridgePayload(payload);
if (!trackEvent) return 'malformed';
if (trackEvent.track.kind !== 'video') return [trackEvent];
return [
trackEvent,
{
type: 'inboundVideo.trackSubscribed',
track: {
participantSid: trackEvent.track.participantSid,
participantIdentity: trackEvent.track.participantIdentity,
trackSid: trackEvent.track.trackSid,
source: trackEvent.track.source,
},
},
];
}
export function translateVoiceEngineV2BridgeVideoFrameToEvent(
frame: VoiceEngineV2BridgeVideoFrame,
): VoiceEngineV2Event {
assertVideoFrameInvariants({
widthPx: frame.meta.width,
heightPx: frame.meta.height,
frameBytes: frame.data.byteLength,
timestampNs: frame.meta.timestampUs * 1000,
});
return {
type: 'inboundVideo.frameReceived',
frame: {
participantSid: frame.meta.participantSid,
trackSid: frame.meta.trackSid,
width: frame.meta.width,
height: frame.meta.height,
timestampUs: frame.meta.timestampUs,
byteLength: frame.data.byteLength,
},
};
}
@@ -0,0 +1,680 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// biome-ignore-all lint/suspicious/noConfusingVoidType: IPC result metadata mirrors void-returning bridge methods.
import type {
VoiceEngineV2CameraOptions,
VoiceEngineV2ConnectOptions,
VoiceEngineV2DataOptions,
VoiceEngineV2HardwareEncoderCapabilities,
VoiceEngineV2PcmFrame,
VoiceEngineV2RemoteTrackQuality,
VoiceEngineV2RemoteTrackSubscriptionOptions,
VoiceEngineV2ScreenAudioOptions,
VoiceEngineV2ScreenOptions,
VoiceEngineV2SendStats,
VoiceEngineV2TrackKind,
VoiceEngineV2TrackSource,
VoiceEngineV2VideoCodec,
} from '../protocol';
export const VOICE_ENGINE_V2_BRIDGE_VERSION = 18;
export type VoiceEngineV2BridgeMethodName =
| 'isSupported'
| 'getCapabilities'
| 'prewarm'
| 'getHardwareEncoderCapabilities'
| 'connect'
| 'disconnect'
| 'isConnected'
| 'publishMicrophone'
| 'pushPcm'
| 'publishScreen'
| 'updateScreenShareEncoding'
| 'unpublishScreen'
| 'publishScreenAudio'
| 'pushScreenAudioPcm'
| 'pushScreenAudioFloat'
| 'unpublishScreenAudio'
| 'setMicEnabled'
| 'setSpeakingDetection'
| 'listAudioInputDevices'
| 'listAudioOutputDevices'
| 'setAudioOutputDevice'
| 'setParticipantVolume'
| 'setRemoteTrackSubscription'
| 'publishData'
| 'listCameraDevices'
| 'publishCamera'
| 'updateCameraCapture'
| 'publishNativeCameraSink'
| 'publishProcessedCamera'
| 'pushProcessedCameraFrame'
| 'pushCameraBackgroundFrame'
| 'clearCameraBackgroundFrame'
| 'publishDeviceScreenShare'
| 'unpublishCamera'
| 'isPublishingCamera'
| 'startCameraPreview'
| 'stopCameraPreview'
| 'getConnectionStats'
| 'getVoiceEngineReadiness'
| 'getAudioDeviceModuleState'
| 'onEvent'
| 'onVideoFrame';
export type VoiceEngineV2InvokeMethodName = Exclude<VoiceEngineV2BridgeMethodName, 'onEvent' | 'onVideoFrame'>;
export type VoiceEngineV2BridgeConnectOptions = VoiceEngineV2ConnectOptions;
export type VoiceEngineV2BridgeScreenPacing = 'sender' | 'source';
export interface VoiceEngineV2BridgePublishScreenOptions extends VoiceEngineV2ScreenOptions {
codec?: VoiceEngineV2VideoCodec;
pacing?: VoiceEngineV2BridgeScreenPacing;
trackName?: string;
}
export interface VoiceEngineV2BridgeUpdateScreenShareEncodingOptions {
captureId: string;
width: number;
height: number;
frameRate?: number;
maxBitrateBps?: number;
codec?: VoiceEngineV2VideoCodec;
hardwareEncoding?: boolean;
zeroCopyRequired?: boolean;
}
export interface VoiceEngineV2BridgePublishMicrophoneOptions {
mode?: 'device' | 'pcm-test';
deviceId?: string;
echoCancellation?: boolean;
noiseSuppression?: boolean;
autoGainControl?: boolean;
deepFilter?: boolean;
deepFilterNoiseReductionLevel?: number;
sampleRate?: number;
numChannels?: number;
maxBitrateBps?: number;
}
export type VoiceEngineV2BridgePcmFrame = VoiceEngineV2PcmFrame;
export interface VoiceEngineV2BridgeFloatPcmFrame {
sampleRate: number;
numChannels: number;
samples: Float32Array;
}
export type VoiceEngineV2BridgePublishScreenAudioOptions = VoiceEngineV2ScreenAudioOptions;
export type VoiceEngineV2BridgePublishDataOptions = VoiceEngineV2DataOptions;
export type VoiceEngineV2BridgeTrackKind = VoiceEngineV2TrackKind;
export type VoiceEngineV2BridgeTrackSource =
| VoiceEngineV2TrackSource
| 'screen_share'
| 'screen_share_audio'
| 'screenshare'
| 'screenshareAudio';
export type VoiceEngineV2BridgeSubscriptionStatus = 'desired' | 'subscribed' | 'unsubscribed';
export type VoiceEngineV2BridgeConnectionQuality = 'excellent' | 'good' | 'poor' | 'lost';
export interface VoiceEngineV2BridgeParticipantEventPayload {
sid: string;
identity: string;
name: string;
}
export interface VoiceEngineV2BridgeTrackEventPayload {
participantSid: string;
identity: string;
participantName: string;
trackSid: string;
trackName: string;
kind: VoiceEngineV2BridgeTrackKind;
source: VoiceEngineV2BridgeTrackSource;
muted: boolean;
}
export interface VoiceEngineV2BridgeSubscribedTrackEventPayload extends VoiceEngineV2BridgeTrackEventPayload {
subscribed: boolean;
subscriptionStatus: VoiceEngineV2BridgeSubscriptionStatus;
}
export interface VoiceEngineV2BridgeTrackSubscriptionFailedEventPayload {
participantSid: string;
identity: string;
participantName: string;
trackSid: string;
error: string;
trackName?: string;
kind?: VoiceEngineV2BridgeTrackKind;
source?: VoiceEngineV2BridgeTrackSource;
muted?: boolean;
subscribed?: boolean;
subscriptionStatus?: VoiceEngineV2BridgeSubscriptionStatus;
}
export interface VoiceEngineV2BridgeLocalTrackRepublishedEventPayload extends VoiceEngineV2BridgeTrackEventPayload {
previousTrackSid: string;
}
export interface VoiceEngineV2BridgeEventPayloads {
connected: Record<string, never>;
connectionState: {state: string};
disconnected: {reason: string};
participantJoined: VoiceEngineV2BridgeParticipantEventPayload;
participantLeft: VoiceEngineV2BridgeParticipantEventPayload;
participantNameChanged: {sid: string; identity: string; oldName: string; name: string};
participantMetadataChanged: {
sid: string;
identity: string;
name: string;
oldMetadata: string;
metadata: string;
attributes: Record<string, string>;
};
participantAttributesChanged: {
sid: string;
identity: string;
name: string;
attributes: Record<string, string>;
changedAttributes: Record<string, string>;
};
trackPublished: VoiceEngineV2BridgeSubscribedTrackEventPayload;
trackUnpublished: VoiceEngineV2BridgeSubscribedTrackEventPayload;
trackSubscribed: VoiceEngineV2BridgeSubscribedTrackEventPayload;
trackUnsubscribed: VoiceEngineV2BridgeSubscribedTrackEventPayload;
trackSubscriptionFailed: VoiceEngineV2BridgeTrackSubscriptionFailedEventPayload;
trackMuted: VoiceEngineV2BridgeTrackEventPayload;
trackUnmuted: VoiceEngineV2BridgeTrackEventPayload;
localTrackPublished: VoiceEngineV2BridgeTrackEventPayload;
localTrackUnpublished: VoiceEngineV2BridgeTrackEventPayload;
localTrackRepublished: VoiceEngineV2BridgeLocalTrackRepublishedEventPayload;
activeSpeakers: {
sids: Array<string>;
participants: Array<VoiceEngineV2BridgeParticipantEventPayload>;
};
speakingChanged: {
participantSid: string;
identity: string;
trackSid: string;
source: VoiceEngineV2BridgeTrackSource;
isLocal: boolean;
speaking: boolean;
};
connectionQuality: {
sid: string;
identity: string;
name: string;
quality: VoiceEngineV2BridgeConnectionQuality;
};
dataReceived: {
payloadBytes: Array<number>;
payloadText?: string;
topic?: string;
reliable: boolean;
kind: 'reliable' | 'lossy';
participantSid?: string;
identity?: string;
participantName?: string;
};
e2eeState: {sid: string; identity: string; name: string; state: string};
stats: VoiceEngineV2BridgeStats;
audioPlaybackUnavailable: {message: string};
engineReady: {ready: true};
audioDeviceModuleStatus: VoiceEngineV2BridgeAudioDeviceModuleState;
}
export const VOICE_ENGINE_V2_ENGINE_READY_EVENT_TYPE = 'engineReady';
export const VOICE_ENGINE_V2_ADM_STATUS_EVENT_TYPE = 'audioDeviceModuleStatus';
export interface VoiceEngineV2BridgeReadiness {
ready: boolean;
reason?: string;
}
export type VoiceEngineV2BridgeAudioDeviceModuleStatus = 'warming' | 'ready' | 'failed';
export interface VoiceEngineV2BridgeAudioDeviceModuleState {
status: VoiceEngineV2BridgeAudioDeviceModuleStatus;
detail?: string;
}
export type VoiceEngineV2BridgeKnownEventType = keyof VoiceEngineV2BridgeEventPayloads;
export type VoiceEngineV2BridgeEventType = VoiceEngineV2BridgeKnownEventType | (string & {});
export type VoiceEngineV2BridgeKnownEvent = {
[Type in VoiceEngineV2BridgeKnownEventType]: {type: Type; payload: VoiceEngineV2BridgeEventPayloads[Type]};
}[VoiceEngineV2BridgeKnownEventType];
export type VoiceEngineV2BridgeEvent =
| VoiceEngineV2BridgeKnownEvent
| {type: string & {}; payload: Record<string, unknown>};
export interface VoiceEngineV2BridgeVideoFrameMeta {
bridgeVersion?: number;
participantSid: string;
participantIdentity?: string;
trackSid: string;
trackName?: string;
source?: VoiceEngineV2BridgeTrackSource;
width: number;
height: number;
timestampUs: number;
}
export interface VoiceEngineV2BridgeVideoFrame {
meta: VoiceEngineV2BridgeVideoFrameMeta;
data: ArrayBuffer;
}
export type VoiceEngineV2BridgeAudioDeviceRole = 'default' | 'communications' | 'endpoint';
export interface VoiceEngineV2BridgeAudioOutputDevice {
deviceId: string;
label: string;
isDefault: boolean;
role?: VoiceEngineV2BridgeAudioDeviceRole;
endpointLabel?: string;
isDefaultRoute?: boolean;
}
export interface VoiceEngineV2BridgeAudioInputDevice {
deviceId: string;
label: string;
isDefault: boolean;
role?: VoiceEngineV2BridgeAudioDeviceRole;
endpointLabel?: string;
isDefaultRoute?: boolean;
}
export interface VoiceEngineV2BridgeCameraDevice {
deviceId: string;
label: string;
description: string;
index?: number | null;
deviceIdAliases?: Array<string>;
}
export const VOICE_ENGINE_V2_CAMERA_EFFECT_STRENGTH_MIN = 0;
export const VOICE_ENGINE_V2_CAMERA_EFFECT_STRENGTH_MAX = 100;
export const VOICE_ENGINE_V2_CAMERA_EFFECT_STRENGTH_DEFAULT = 50;
export interface VoiceEngineV2BridgePublishCameraOptions extends VoiceEngineV2CameraOptions {
mirror?: boolean;
backgroundBlurStrength?: number;
}
export type VoiceEngineV2BridgeUpdateCameraCaptureOptions = VoiceEngineV2BridgePublishCameraOptions;
export type VoiceEngineV2BridgeCameraBackgroundMode = 'none' | 'non' | 'blur' | 'custom';
export type VoiceEngineV2BridgeCameraBackgroundCustomMediaKind = 'static' | 'animated' | 'video';
export interface VoiceEngineV2BridgePublishProcessedCameraOptions {
width: number;
height: number;
frameRate: number;
}
export interface VoiceEngineV2BridgePublishProcessedCameraResult {
trackSid: string;
}
export type VoiceEngineV2BridgePublishNativeCameraSinkResult = VoiceEngineV2BridgePublishProcessedCameraResult;
export type VoiceEngineV2BridgeStartCameraPreviewOptions = VoiceEngineV2BridgePublishCameraOptions;
export interface VoiceEngineV2BridgeCameraPreviewInfo {
trackSid: string;
width: number;
height: number;
frameRate: number;
}
export type VoiceEngineV2BridgeProcessedCameraFrameFormat = 'i420';
export interface VoiceEngineV2BridgeProcessedCameraFrame {
format: VoiceEngineV2BridgeProcessedCameraFrameFormat;
width: number;
height: number;
timestampUs: number;
data: ArrayBuffer | ArrayBufferView;
}
export interface VoiceEngineV2BridgePublishDeviceScreenShareOptions {
deviceId?: string;
width?: number;
height?: number;
frameRate?: number;
codec?: VoiceEngineV2VideoCodec;
maxBitrateBps?: number;
maxFramerate?: number;
}
export interface VoiceEngineV2BridgeOutboundStat {
trackSid: string;
source: string;
kind: VoiceEngineV2BridgeTrackKind;
codec?: string;
bitrateKbps: number;
packetsLost: number;
fps?: number;
audioLevel?: number;
width?: number;
height?: number;
sourceWidth?: number;
sourceHeight?: number;
targetBitrateKbps?: number;
configuredFps?: number;
targetFps?: number;
effectiveFps?: number;
framesProduced?: number;
framesAccepted?: number;
framesDropped?: number;
framesCoalesced?: number;
framesCaptured?: number;
captureFailures?: number;
maxQueueAgeMs?: number;
maxPushLatencyMs?: number;
adaptiveSendTier?: string;
adaptiveSendReason?: string;
zeroCopy?: boolean;
}
export interface VoiceEngineV2BridgeInboundStat {
participantSid: string;
participantIdentity?: string;
trackSid: string;
source?: string;
kind: VoiceEngineV2BridgeTrackKind;
codec?: string;
bitrateKbps: number;
packetsLost: number;
jitterMs?: number;
audioLevel?: number;
width?: number;
height?: number;
fps?: number;
sourceWidth?: number;
sourceHeight?: number;
}
export type VoiceEngineV2BridgeSendStats = VoiceEngineV2SendStats;
export interface VoiceEngineV2BridgeStats {
rttMs: number | null;
outbound: Array<VoiceEngineV2BridgeOutboundStat>;
inbound: Array<VoiceEngineV2BridgeInboundStat>;
droppedNativeVideoFrames?: number;
droppedVideoFrameCallbacks?: number;
send?: VoiceEngineV2BridgeSendStats | null;
}
export type VoiceEngineV2BridgeRemoteTrackSubscriptionQuality = VoiceEngineV2RemoteTrackQuality;
export type VoiceEngineV2BridgeRemoteTrackSubscriptionOptions = VoiceEngineV2RemoteTrackSubscriptionOptions;
export type VoiceEngineV2BridgeHardwareEncoderBackend = 'nvenc' | 'videotoolbox' | 'none' | string;
export type VoiceEngineV2BridgeHardwareEncoderCapabilities = VoiceEngineV2HardwareEncoderCapabilities;
export interface VoiceEngineV2BridgeCapabilities {
microphoneCapture: boolean;
syntheticMicrophonePcm: boolean;
cameraCapture: boolean;
nativeCameraBackgrounds: boolean;
screenShare: boolean;
screenShareEncodingUpdate: boolean;
screenShareAudio: boolean;
deviceLists: boolean;
outputDeviceSelection: boolean;
participantVolume: boolean;
remoteTrackSubscription: boolean;
dataChannel: boolean;
connectionStats: boolean;
nativeVideoFrames: boolean;
hardwareEncoderCapabilities: boolean;
}
export type VoiceEngineV2BridgeOperationErrorCode =
| 'unsupported-capability'
| 'not-connected'
| 'invalid-args'
| 'native-error';
export interface VoiceEngineV2BridgeOperationError {
code: VoiceEngineV2BridgeOperationErrorCode;
message: string;
capability?: keyof VoiceEngineV2BridgeCapabilities | string;
}
export interface VoiceEngineV2BridgeOperationSuccess {
ok: true;
}
export interface VoiceEngineV2BridgeOperationFailure {
ok: false;
error: VoiceEngineV2BridgeOperationError;
}
export type VoiceEngineV2BridgeOperationResult =
| VoiceEngineV2BridgeOperationSuccess
| VoiceEngineV2BridgeOperationFailure;
export const VOICE_ENGINE_V2_OPERATION_SUCCESS: VoiceEngineV2BridgeOperationSuccess = {ok: true};
export function createVoiceEngineV2OperationFailure(
error: VoiceEngineV2BridgeOperationError,
): VoiceEngineV2BridgeOperationFailure {
return {ok: false, error};
}
export interface VoiceEngineV2BridgeParticipantVolumeOptions {
participantSid: string;
volume: number;
}
export interface VoiceEngineV2BridgeSpeakingDetectionOptions {
localThresholdRms: number;
remoteThresholdRms: number;
}
export interface VoiceEngineV2BridgeIpcMethods {
isSupported: {args: []; result: boolean};
getCapabilities: {args: []; result: VoiceEngineV2BridgeCapabilities};
prewarm: {args: []; result: void};
getHardwareEncoderCapabilities: {args: []; result: VoiceEngineV2BridgeHardwareEncoderCapabilities};
connect: {args: [VoiceEngineV2BridgeConnectOptions]; result: void};
disconnect: {args: []; result: void};
isConnected: {args: []; result: boolean};
publishMicrophone: {args: [VoiceEngineV2BridgePublishMicrophoneOptions]; result: VoiceEngineV2BridgeOperationResult};
pushPcm: {args: [VoiceEngineV2BridgePcmFrame]; result: boolean};
publishScreen: {args: [VoiceEngineV2BridgePublishScreenOptions]; result: void};
updateScreenShareEncoding: {args: [VoiceEngineV2BridgeUpdateScreenShareEncodingOptions]; result: void};
unpublishScreen: {args: []; result: void};
publishScreenAudio: {args: [VoiceEngineV2BridgePublishScreenAudioOptions]; result: void};
pushScreenAudioPcm: {args: [VoiceEngineV2BridgePcmFrame]; result: boolean};
pushScreenAudioFloat: {args: [VoiceEngineV2BridgeFloatPcmFrame]; result: boolean};
unpublishScreenAudio: {args: []; result: void};
setMicEnabled: {args: [boolean]; result: VoiceEngineV2BridgeOperationResult};
setSpeakingDetection: {args: [VoiceEngineV2BridgeSpeakingDetectionOptions]; result: void};
listAudioInputDevices: {args: []; result: Array<VoiceEngineV2BridgeAudioInputDevice>};
listAudioOutputDevices: {args: []; result: Array<VoiceEngineV2BridgeAudioOutputDevice>};
setAudioOutputDevice: {args: [string]; result: void};
setParticipantVolume: {args: [VoiceEngineV2BridgeParticipantVolumeOptions]; result: void};
setRemoteTrackSubscription: {args: [VoiceEngineV2BridgeRemoteTrackSubscriptionOptions]; result: void};
publishData: {args: [VoiceEngineV2BridgePublishDataOptions]; result: void};
listCameraDevices: {args: []; result: Array<VoiceEngineV2BridgeCameraDevice>};
publishCamera: {args: [VoiceEngineV2BridgePublishCameraOptions]; result: void};
updateCameraCapture: {args: [VoiceEngineV2BridgeUpdateCameraCaptureOptions]; result: void};
publishNativeCameraSink: {
args: [VoiceEngineV2BridgePublishCameraOptions];
result: VoiceEngineV2BridgePublishNativeCameraSinkResult;
};
publishProcessedCamera: {
args: [VoiceEngineV2BridgePublishProcessedCameraOptions];
result: VoiceEngineV2BridgePublishProcessedCameraResult;
};
pushProcessedCameraFrame: {args: [VoiceEngineV2BridgeProcessedCameraFrame]; result: boolean};
pushCameraBackgroundFrame: {args: [VoiceEngineV2BridgeProcessedCameraFrame]; result: boolean};
clearCameraBackgroundFrame: {args: []; result: void};
publishDeviceScreenShare: {args: [VoiceEngineV2BridgePublishDeviceScreenShareOptions]; result: void};
unpublishCamera: {args: []; result: void};
isPublishingCamera: {args: []; result: boolean};
startCameraPreview: {
args: [VoiceEngineV2BridgeStartCameraPreviewOptions];
result: VoiceEngineV2BridgeCameraPreviewInfo;
};
stopCameraPreview: {args: []; result: void};
getConnectionStats: {args: []; result: VoiceEngineV2BridgeStats};
getVoiceEngineReadiness: {args: []; result: VoiceEngineV2BridgeReadiness};
getAudioDeviceModuleState: {args: []; result: VoiceEngineV2BridgeAudioDeviceModuleState};
}
export const VOICE_ENGINE_V2_BRIDGE_METHODS = [
'isSupported',
'getCapabilities',
'prewarm',
'getHardwareEncoderCapabilities',
'connect',
'disconnect',
'isConnected',
'publishMicrophone',
'pushPcm',
'publishScreen',
'updateScreenShareEncoding',
'unpublishScreen',
'publishScreenAudio',
'pushScreenAudioPcm',
'pushScreenAudioFloat',
'unpublishScreenAudio',
'setMicEnabled',
'setSpeakingDetection',
'listAudioInputDevices',
'listAudioOutputDevices',
'setAudioOutputDevice',
'setParticipantVolume',
'setRemoteTrackSubscription',
'publishData',
'listCameraDevices',
'publishCamera',
'updateCameraCapture',
'publishNativeCameraSink',
'publishProcessedCamera',
'pushProcessedCameraFrame',
'pushCameraBackgroundFrame',
'clearCameraBackgroundFrame',
'publishDeviceScreenShare',
'unpublishCamera',
'isPublishingCamera',
'startCameraPreview',
'stopCameraPreview',
'getConnectionStats',
'getVoiceEngineReadiness',
'getAudioDeviceModuleState',
'onEvent',
'onVideoFrame',
] as const satisfies ReadonlyArray<VoiceEngineV2BridgeMethodName>;
export const VOICE_ENGINE_V2_IPC_CHANNELS = {
isSupported: 'voice-engine-v2:is-supported',
getCapabilities: 'voice-engine-v2:get-capabilities',
prewarm: 'voice-engine-v2:prewarm',
getHardwareEncoderCapabilities: 'voice-engine-v2:get-hardware-encoder-capabilities',
connect: 'voice-engine-v2:connect',
disconnect: 'voice-engine-v2:disconnect',
isConnected: 'voice-engine-v2:is-connected',
publishMicrophone: 'voice-engine-v2:publish-microphone',
pushPcm: 'voice-engine-v2:push-pcm',
publishScreen: 'voice-engine-v2:publish-screen',
updateScreenShareEncoding: 'voice-engine-v2:update-screen-share-encoding',
unpublishScreen: 'voice-engine-v2:unpublish-screen',
publishScreenAudio: 'voice-engine-v2:publish-screen-audio',
pushScreenAudioPcm: 'voice-engine-v2:push-screen-audio-pcm',
pushScreenAudioFloat: 'voice-engine-v2:push-screen-audio-float',
unpublishScreenAudio: 'voice-engine-v2:unpublish-screen-audio',
setMicEnabled: 'voice-engine-v2:set-mic-enabled',
setSpeakingDetection: 'voice-engine-v2:set-speaking-detection',
listAudioInputDevices: 'voice-engine-v2:list-audio-input-devices',
listAudioOutputDevices: 'voice-engine-v2:list-audio-output-devices',
setAudioOutputDevice: 'voice-engine-v2:set-audio-output-device',
setParticipantVolume: 'voice-engine-v2:set-participant-volume',
setRemoteTrackSubscription: 'voice-engine-v2:set-remote-track-subscription',
publishData: 'voice-engine-v2:publish-data',
listCameraDevices: 'voice-engine-v2:list-camera-devices',
publishCamera: 'voice-engine-v2:publish-camera',
updateCameraCapture: 'voice-engine-v2:update-camera-capture',
publishNativeCameraSink: 'voice-engine-v2:publish-native-camera-sink',
publishProcessedCamera: 'voice-engine-v2:publish-processed-camera',
pushProcessedCameraFrame: 'voice-engine-v2:push-processed-camera-frame',
pushCameraBackgroundFrame: 'voice-engine-v2:push-camera-background-frame',
clearCameraBackgroundFrame: 'voice-engine-v2:clear-camera-background-frame',
publishDeviceScreenShare: 'voice-engine-v2:publish-device-screen-share',
unpublishCamera: 'voice-engine-v2:unpublish-camera',
isPublishingCamera: 'voice-engine-v2:is-publishing-camera',
startCameraPreview: 'voice-engine-v2:start-camera-preview',
stopCameraPreview: 'voice-engine-v2:stop-camera-preview',
getConnectionStats: 'voice-engine-v2:get-connection-stats',
getVoiceEngineReadiness: 'voice-engine-v2:get-readiness',
getAudioDeviceModuleState: 'voice-engine-v2:get-audio-device-module-state',
} as const satisfies {[Method in VoiceEngineV2InvokeMethodName]: string};
export const VOICE_ENGINE_V2_EVENT_CHANNELS = {
event: 'voice-engine-v2:event',
videoFrame: 'voice-engine-v2:video-frame',
} as const satisfies {
event: string;
videoFrame: string;
};
export interface VoiceEngineV2BridgePushedEvents {
event: VoiceEngineV2BridgeEvent;
videoFrame: VoiceEngineV2BridgeVideoFrame;
}
export interface VoiceEngineV2BridgeApi {
bridgeVersion: number;
isSupported(): Promise<boolean>;
getCapabilities(): Promise<VoiceEngineV2BridgeCapabilities>;
prewarm(): Promise<void>;
getHardwareEncoderCapabilities(): Promise<VoiceEngineV2BridgeHardwareEncoderCapabilities>;
connect(options: VoiceEngineV2BridgeConnectOptions): Promise<void>;
disconnect(): Promise<void>;
isConnected(): Promise<boolean>;
publishMicrophone(options: VoiceEngineV2BridgePublishMicrophoneOptions): Promise<VoiceEngineV2BridgeOperationResult>;
pushPcm(frame: VoiceEngineV2BridgePcmFrame): Promise<boolean>;
publishScreen(options: VoiceEngineV2BridgePublishScreenOptions): Promise<void>;
updateScreenShareEncoding(options: VoiceEngineV2BridgeUpdateScreenShareEncodingOptions): Promise<void>;
unpublishScreen(): Promise<void>;
publishScreenAudio(options: VoiceEngineV2BridgePublishScreenAudioOptions): Promise<void>;
pushScreenAudioPcm(frame: VoiceEngineV2BridgePcmFrame): Promise<boolean>;
pushScreenAudioFloat(frame: VoiceEngineV2BridgeFloatPcmFrame): Promise<boolean>;
unpublishScreenAudio(): Promise<void>;
setMicEnabled(enabled: boolean): Promise<VoiceEngineV2BridgeOperationResult>;
setSpeakingDetection(options: VoiceEngineV2BridgeSpeakingDetectionOptions): Promise<void>;
listAudioInputDevices(): Promise<Array<VoiceEngineV2BridgeAudioInputDevice>>;
listAudioOutputDevices(): Promise<Array<VoiceEngineV2BridgeAudioOutputDevice>>;
setAudioOutputDevice(deviceId: string): Promise<void>;
setParticipantVolume(participantSid: string, volume: number): Promise<void>;
setRemoteTrackSubscription(options: VoiceEngineV2BridgeRemoteTrackSubscriptionOptions): Promise<void>;
publishData(options: VoiceEngineV2BridgePublishDataOptions): Promise<void>;
listCameraDevices(): Promise<Array<VoiceEngineV2BridgeCameraDevice>>;
publishCamera(options: VoiceEngineV2BridgePublishCameraOptions): Promise<void>;
updateCameraCapture(options: VoiceEngineV2BridgeUpdateCameraCaptureOptions): Promise<void>;
publishNativeCameraSink(
options: VoiceEngineV2BridgePublishCameraOptions,
): Promise<VoiceEngineV2BridgePublishNativeCameraSinkResult>;
publishProcessedCamera(
options: VoiceEngineV2BridgePublishProcessedCameraOptions,
): Promise<VoiceEngineV2BridgePublishProcessedCameraResult>;
pushProcessedCameraFrame(frame: VoiceEngineV2BridgeProcessedCameraFrame): Promise<boolean>;
pushCameraBackgroundFrame(frame: VoiceEngineV2BridgeProcessedCameraFrame): Promise<boolean>;
clearCameraBackgroundFrame(): Promise<void>;
publishDeviceScreenShare(options: VoiceEngineV2BridgePublishDeviceScreenShareOptions): Promise<void>;
unpublishCamera(): Promise<void>;
isPublishingCamera(): Promise<boolean>;
startCameraPreview(
options: VoiceEngineV2BridgeStartCameraPreviewOptions,
): Promise<VoiceEngineV2BridgeCameraPreviewInfo>;
stopCameraPreview(): Promise<void>;
getConnectionStats(): Promise<VoiceEngineV2BridgeStats>;
getVoiceEngineReadiness(): Promise<VoiceEngineV2BridgeReadiness>;
getAudioDeviceModuleState?(): Promise<VoiceEngineV2BridgeAudioDeviceModuleState>;
onEvent(callback: (event: VoiceEngineV2BridgeEvent) => void): () => void;
onVideoFrame(callback: (frame: VoiceEngineV2BridgeVideoFrame) => void): () => void;
}
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Command} from '../protocol/commands';
import type {VoiceEngineV2Snapshot} from './state';
export function isVoiceEngineV2CommandCompletionStale(
snapshot: VoiceEngineV2Snapshot,
command: VoiceEngineV2Command,
): boolean {
assert.ok(snapshot != null, 'isVoiceEngineV2CommandCompletionStale snapshot must not be null');
assert.ok(command != null, 'isVoiceEngineV2CommandCompletionStale command must not be null');
assert.equal(typeof command.type, 'string', 'command.type must be a string');
const local = staleLocalMediaCommand(snapshot, command);
if (local !== null) return local;
const session = staleSessionCommand(snapshot, command);
if (session !== null) return session;
const platform = stalePlatformCommand(snapshot, command);
if (platform !== null) return platform;
return staleAlwaysFreshCommand(command);
}
function staleLocalMediaCommand(snapshot: VoiceEngineV2Snapshot, command: VoiceEngineV2Command): boolean | null {
assert.ok(snapshot != null, 'staleLocalMediaCommand snapshot must not be null');
assert.ok(command != null, 'staleLocalMediaCommand command must not be null');
switch (command.type) {
case 'connection.connect':
case 'connection.disconnect':
return snapshot.connection.operationId !== command.operationId;
case 'microphone.publish':
case 'microphone.unpublish':
return snapshot.microphone.operationId !== command.operationId;
case 'microphone.setEnabled':
return snapshot.microphone.setEnabledOperationId !== command.operationId;
case 'camera.publish':
case 'camera.updateEncoding':
case 'camera.unpublish':
return snapshot.camera.operationId !== command.operationId;
case 'screen.publish':
case 'screen.updateEncoding':
case 'screen.unpublish':
return snapshot.screen.operationId !== command.operationId;
case 'screenAudio.publish':
case 'screenAudio.unpublish':
return snapshot.screenAudio.operationId !== command.operationId;
default:
return null;
}
}
function staleSessionCommand(snapshot: VoiceEngineV2Snapshot, command: VoiceEngineV2Command): boolean | null {
assert.ok(snapshot != null, 'staleSessionCommand snapshot must not be null');
assert.ok(command != null, 'staleSessionCommand command must not be null');
switch (command.type) {
case 'outputDevice.set':
return snapshot.outputDevice.operationId !== command.operationId;
case 'stats.collect':
return snapshot.statsOperationId !== command.operationId;
case 'capabilities.queryHardwareEncoder':
return snapshot.hardwareEncoder.operationId !== command.operationId;
case 'gateway.voiceState.write':
case 'gateway.voiceState.clear':
return snapshot.gateway.operationId !== command.operationId;
case 'lifecycle.teardown':
return snapshot.lifecycle.operationId !== command.operationId;
case 'e2ee.setEnabled':
return snapshot.e2ee.operationId !== command.operationId;
default:
return null;
}
}
function stalePlatformCommand(snapshot: VoiceEngineV2Snapshot, command: VoiceEngineV2Command): boolean | null {
assert.ok(snapshot != null, 'stalePlatformCommand snapshot must not be null');
assert.ok(command != null, 'stalePlatformCommand command must not be null');
switch (command.type) {
case 'permissions.check':
case 'permissions.request':
return snapshot.permissions.operationIds[String(command.name)] !== command.operationId;
case 'devices.enumerate':
case 'devices.selectAudioInput':
case 'devices.selectAudioOutput':
case 'devices.selectCamera':
return snapshot.devices.operationId !== command.operationId;
case 'nativeCapture.start':
case 'nativeCapture.update':
return snapshot.nativeCapture.operationIds[command.options.captureId] !== command.operationId;
case 'nativeCapture.stop':
return snapshot.nativeCapture.operationIds[command.captureId] !== command.operationId;
case 'nativeAudioTap.start':
return snapshot.nativeAudioTap.operationIds[command.options.tapId] !== command.operationId;
case 'nativeAudioTap.stop':
return snapshot.nativeAudioTap.operationIds[command.tapId] !== command.operationId;
case 'nativeFrameSink.attach':
return snapshot.nativeFrameSink.operationIds[command.options.sinkId] !== command.operationId;
case 'nativeFrameSink.detach':
return snapshot.nativeFrameSink.operationIds[command.sinkId] !== command.operationId;
default:
return null;
}
}
function staleAlwaysFreshCommand(command: VoiceEngineV2Command): boolean {
assert.ok(command != null, 'staleAlwaysFreshCommand command must not be null');
assert.equal(typeof command.type, 'string', 'command.type must be a string');
switch (command.type) {
case 'implementation.prewarm':
case 'participantVolume.set':
case 'remoteTrackSubscription.set':
case 'data.publish':
case 'timer.schedule':
case 'timer.cancel':
case 'diagnostics.log':
case 'operation.cancel':
return false;
default:
return false;
}
}
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {describe, expect, it} from 'vitest';
import gatewayConnectionFixtureJson from '../../fixtures/event_logs/gateway_connection.json';
import reconnectChannelMoveFixtureJson from '../../fixtures/event_logs/reconnect_channel_move.json';
import resumePreservesMediaFixtureJson from '../../fixtures/event_logs/resume_preserves_media.json';
import staleConnectionFixtureJson from '../../fixtures/event_logs/stale_connection.json';
import teardownRendererWindowFixtureJson from '../../fixtures/event_logs/teardown_renderer_window.json';
import type {VoiceEngineV2Command} from '../protocol/commands';
import type {VoiceEngineV2Event} from '../protocol/events';
import {transitionVoiceEngineV2} from './reducer';
import {
availableVoiceEngineV2Capabilities,
createVoiceEngineV2InitialSnapshot,
type VoiceEngineV2OperationStatus,
type VoiceEngineV2Snapshot,
} from './state';
interface VoiceEngineV2ConnectionGatewayFixtureStep {
name: string;
event: VoiceEngineV2Event;
commands: Array<VoiceEngineV2Command>;
}
interface VoiceEngineV2ConnectionGatewayFixtureExpected {
nextOperationId: number;
connectionStatus: string;
activeUrl: string | null;
desiredUrl: string | null;
gatewayChannelId: string | null;
lifecycleTearingDown: boolean;
operationStatuses?: Record<string, VoiceEngineV2OperationStatus>;
}
interface VoiceEngineV2ConnectionGatewayFixture {
name: string;
steps: Array<VoiceEngineV2ConnectionGatewayFixtureStep>;
expected: VoiceEngineV2ConnectionGatewayFixtureExpected;
}
const fixtures = [
gatewayConnectionFixtureJson,
reconnectChannelMoveFixtureJson,
resumePreservesMediaFixtureJson,
staleConnectionFixtureJson,
teardownRendererWindowFixtureJson,
] as Array<unknown>;
function initialSnapshot(): VoiceEngineV2Snapshot {
return createVoiceEngineV2InitialSnapshot(availableVoiceEngineV2Capabilities());
}
function replayFixture(fixture: VoiceEngineV2ConnectionGatewayFixture): VoiceEngineV2Snapshot {
let snapshot = initialSnapshot();
for (const step of fixture.steps) {
const transition = transitionVoiceEngineV2(snapshot, step.event);
expect(transition.commands, `${fixture.name}: ${step.name}`).toEqual(step.commands);
snapshot = transition.snapshot;
}
return snapshot;
}
describe('connection and gateway event-log fixtures', () => {
it.each(fixtures)('replays %s as pure snapshot transitions', (fixtureJson) => {
const fixture = fixtureJson as VoiceEngineV2ConnectionGatewayFixture;
const snapshot = replayFixture(fixture);
expect(snapshot.nextOperationId).toBe(fixture.expected.nextOperationId);
expect(snapshot.connection.status).toBe(fixture.expected.connectionStatus);
expect(snapshot.connection.active?.url ?? null).toBe(fixture.expected.activeUrl);
expect(snapshot.connection.desired?.url ?? null).toBe(fixture.expected.desiredUrl);
expect(snapshot.gateway.selfVoiceState?.channelId ?? null).toBe(fixture.expected.gatewayChannelId);
expect(snapshot.lifecycle.tearingDown).toBe(fixture.expected.lifecycleTearingDown);
for (const [operationId, status] of Object.entries(fixture.expected.operationStatuses ?? {})) {
expect(snapshot.operations[operationId]?.status, `${fixture.name}: operation ${operationId}`).toBe(status);
}
});
it('does not mutate the previous snapshot when planning a connection command', () => {
const snapshot = initialSnapshot();
const transition = transitionVoiceEngineV2(snapshot, {
type: 'connection.connectRequested',
options: {url: 'wss://voice.example.test', token: 'token'},
});
expect(snapshot.connection.status).toBe('idle');
expect(snapshot.connection.operationId).toBeNull();
expect(snapshot.nextOperationId).toBe(1);
expect(transition.snapshot).not.toBe(snapshot);
expect(transition.snapshot.connection.status).toBe('connecting');
expect(transition.commands).toEqual([
{
type: 'connection.connect',
operationId: 1,
options: {url: 'wss://voice.example.test', token: 'token'},
},
]);
});
});
@@ -0,0 +1,152 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {describe, expect, it} from 'vitest';
import fixtureJson from '../../fixtures/basic_session.json';
import cameraInPlaceUpdateFixtureJson from '../../fixtures/event_logs/camera_in_place_update.json';
import codecRepublishFixtureJson from '../../fixtures/event_logs/codec_republish.json';
import nativeFrameSinkFixtureJson from '../../fixtures/event_logs/native_frame_sink.json';
import nativeZeroCopyScreenFixtureJson from '../../fixtures/event_logs/native_zero_copy_screen.json';
import screenAudioInclusionToggleFixtureJson from '../../fixtures/event_logs/screen_audio_inclusion_toggle.json';
import screenAudioRoutingFixtureJson from '../../fixtures/event_logs/screen_audio_routing.json';
import screenShareStartUpdateStopFixtureJson from '../../fixtures/event_logs/screen_share_start_update_stop.json';
import externallyEstablishedFixtureJson from '../../fixtures/externally_established_session.json';
import type {VoiceEngineV2Command, VoiceEngineV2Event} from '../protocol';
import {transitionVoiceEngineV2} from './reducer';
import {availableVoiceEngineV2Capabilities, createVoiceEngineV2InitialSnapshot} from './state';
interface VoiceEngineV2FixtureExpected {
nextOperationId: number;
connectionStatus: string;
activeUrl: string;
microphoneStatus: string;
microphoneDeviceId: string;
gatewayChannelId: string;
}
interface VoiceEngineV2Fixture {
name: string;
events: Array<VoiceEngineV2Event>;
expected: VoiceEngineV2FixtureExpected;
}
interface VoiceEngineV2ScreenFixtureExpected {
nextOperationId: number;
cameraStatus?: string;
cameraDeviceId?: string | null;
cameraWidth?: number;
cameraFrameRate?: number;
cameraMirror?: boolean;
cameraBackgroundMode?: string;
screenStatus?: string;
screenCaptureId?: string | null;
screenCodec?: string;
screenHardwareEncoding?: boolean;
screenZeroCopyRequired?: boolean;
screenAudioStatus?: string;
screenAudioRoute?: string;
screenAudioTapId?: string;
nativeCaptureCount?: number;
nativeFrameSinkCount?: number;
lastFailureCode?: string | null;
}
interface VoiceEngineV2CommandReplayFixture {
name: string;
events: Array<VoiceEngineV2Event>;
expectedCommands: Array<VoiceEngineV2Command>;
expected: VoiceEngineV2ScreenFixtureExpected;
}
const screenEventLogFixtures = [
screenShareStartUpdateStopFixtureJson,
cameraInPlaceUpdateFixtureJson,
screenAudioInclusionToggleFixtureJson,
screenAudioRoutingFixtureJson,
codecRepublishFixtureJson,
nativeZeroCopyScreenFixtureJson,
nativeFrameSinkFixtureJson,
] as Array<VoiceEngineV2CommandReplayFixture>;
const sessionFixtures = [fixtureJson, externallyEstablishedFixtureJson] as Array<VoiceEngineV2Fixture>;
describe('voice engine v2 shared fixtures', () => {
it.each(sessionFixtures)('replays the session event log fixture: $name', (fixture) => {
let snapshot = createVoiceEngineV2InitialSnapshot(availableVoiceEngineV2Capabilities());
for (const event of fixture.events) {
snapshot = transitionVoiceEngineV2(snapshot, event).snapshot;
}
expect(snapshot.nextOperationId).toBe(fixture.expected.nextOperationId);
expect(snapshot.connection.status).toBe(fixture.expected.connectionStatus);
expect(snapshot.connection.active?.url).toBe(fixture.expected.activeUrl);
expect(snapshot.microphone.status).toBe(fixture.expected.microphoneStatus);
expect(snapshot.microphone.published?.deviceId).toBe(fixture.expected.microphoneDeviceId);
expect(snapshot.gateway.selfVoiceState?.channelId).toBe(fixture.expected.gatewayChannelId);
});
it.each(screenEventLogFixtures)('replays screen/native event log fixture: $name', (fixture) => {
let snapshot = createVoiceEngineV2InitialSnapshot(availableVoiceEngineV2Capabilities());
const commands: Array<VoiceEngineV2Command> = [];
for (const event of fixture.events) {
const transition = transitionVoiceEngineV2(snapshot, event);
commands.push(...transition.commands);
snapshot = transition.snapshot;
}
expect(commands).toEqual(fixture.expectedCommands);
expect(snapshot.nextOperationId).toBe(fixture.expected.nextOperationId);
if (fixture.expected.cameraStatus !== undefined) {
expect(snapshot.camera.status).toBe(fixture.expected.cameraStatus);
}
if (fixture.expected.cameraDeviceId !== undefined) {
expect(snapshot.camera.published?.deviceId ?? null).toBe(fixture.expected.cameraDeviceId);
}
if (fixture.expected.cameraWidth !== undefined) {
expect(snapshot.camera.published?.width).toBe(fixture.expected.cameraWidth);
}
if (fixture.expected.cameraFrameRate !== undefined) {
expect(snapshot.camera.published?.frameRate).toBe(fixture.expected.cameraFrameRate);
}
if (fixture.expected.cameraMirror !== undefined) {
expect(snapshot.camera.published?.mirror).toBe(fixture.expected.cameraMirror);
}
if (fixture.expected.cameraBackgroundMode !== undefined) {
expect(snapshot.camera.published?.backgroundMode).toBe(fixture.expected.cameraBackgroundMode);
}
if (fixture.expected.screenStatus !== undefined) {
expect(snapshot.screen.status).toBe(fixture.expected.screenStatus);
}
if (fixture.expected.screenCaptureId !== undefined) {
expect(snapshot.screen.published?.captureId ?? null).toBe(fixture.expected.screenCaptureId);
}
if (fixture.expected.screenCodec !== undefined) {
expect(snapshot.screen.published?.codec).toBe(fixture.expected.screenCodec);
}
if (fixture.expected.screenHardwareEncoding !== undefined) {
expect(snapshot.screen.published?.hardwareEncoding).toBe(fixture.expected.screenHardwareEncoding);
}
if (fixture.expected.screenZeroCopyRequired !== undefined) {
expect(snapshot.screen.published?.zeroCopyRequired).toBe(fixture.expected.screenZeroCopyRequired);
}
if (fixture.expected.screenAudioStatus !== undefined) {
expect(snapshot.screenAudio.status).toBe(fixture.expected.screenAudioStatus);
}
if (fixture.expected.screenAudioRoute !== undefined) {
expect(snapshot.screenAudio.published?.route).toBe(fixture.expected.screenAudioRoute);
}
if (fixture.expected.screenAudioTapId !== undefined) {
expect(snapshot.screenAudio.published?.tapId).toBe(fixture.expected.screenAudioTapId);
}
if (fixture.expected.nativeCaptureCount !== undefined) {
expect(Object.keys(snapshot.nativeCapture.captures)).toHaveLength(fixture.expected.nativeCaptureCount);
}
if (fixture.expected.nativeFrameSinkCount !== undefined) {
expect(Object.keys(snapshot.nativeFrameSink.sinks)).toHaveLength(fixture.expected.nativeFrameSinkCount);
}
if (fixture.expected.lastFailureCode !== undefined) {
expect(snapshot.lastFailure?.code ?? null).toBe(fixture.expected.lastFailureCode);
}
});
});
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
export {isVoiceEngineV2CommandCompletionStale, transitionVoiceEngineV2} from './reducer';
export {shouldApplyGatewayVoiceStateEcho} from './reducers/gateway';
export type {
VoiceEngineV2CapabilitiesProjection,
VoiceEngineV2DeviceProjection,
VoiceEngineV2DiagnosticsProjection,
VoiceEngineV2ParticipantProjection,
VoiceEngineV2StatsPresentationNetworkSummary,
VoiceEngineV2StatsPresentationProjection,
VoiceEngineV2StatsProjection,
} from './selectors';
export {
selectVoiceEngineV2CapabilitiesProjection,
selectVoiceEngineV2ConnectionProjection,
selectVoiceEngineV2DeviceProjection,
selectVoiceEngineV2DiagnosticsProjection,
selectVoiceEngineV2E2eeProjection,
selectVoiceEngineV2FailedSourceIds,
selectVoiceEngineV2MediaProjection,
selectVoiceEngineV2Model,
selectVoiceEngineV2ParticipantProjection,
selectVoiceEngineV2SourceLifecycle,
selectVoiceEngineV2StatsPresentationProjection,
selectVoiceEngineV2StatsProjection,
selectVoiceEngineV2StatsSummary,
selectVoiceEngineV2StreamNegotiation,
selectVoiceEngineV2WatchedStreams,
} from './selectors';
export type {
SourceLifecycleState,
VoiceEngineV2ConnectionState,
VoiceEngineV2DeviceState,
VoiceEngineV2E2eeLifecycleState,
VoiceEngineV2GatewayState,
VoiceEngineV2HardwareEncoderState,
VoiceEngineV2InboundVideoState,
VoiceEngineV2LifecycleState,
VoiceEngineV2LiveKitState,
VoiceEngineV2LocalMediaState,
VoiceEngineV2MicrophoneState,
VoiceEngineV2NativeAudioTapState,
VoiceEngineV2NativeCaptureState,
VoiceEngineV2NativeFrameSinkState,
VoiceEngineV2OperationState,
VoiceEngineV2OperationStatus,
VoiceEngineV2OutputDeviceState,
VoiceEngineV2PermissionState,
VoiceEngineV2RoomState,
VoiceEngineV2Snapshot,
VoiceEngineV2Transition,
} from './state';
export {
availableVoiceEngineV2Capabilities,
createVoiceEngineV2InitialSnapshot,
createVoiceEngineV2PermissionResult,
emptyVoiceEngineV2DeviceInventory,
unavailableVoiceEngineV2Capabilities,
} from './state';
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {describe, expect, it} from 'vitest';
import deviceHotswapFixtureJson from '../../fixtures/event_logs/device_hotswap.json';
import localMediaFixtureJson from '../../fixtures/event_logs/local_media.json';
import microphoneFailureRecoveryFixtureJson from '../../fixtures/event_logs/microphone_failure_recovery.json';
import permissionsRecoveryFixtureJson from '../../fixtures/event_logs/permissions_recovery.json';
import pttPtmFixtureJson from '../../fixtures/event_logs/ptt_ptm.json';
import type {VoiceEngineV2Command, VoiceEngineV2Event} from '../protocol';
import {transitionVoiceEngineV2} from './reducer';
import {
availableVoiceEngineV2Capabilities,
createVoiceEngineV2InitialSnapshot,
type VoiceEngineV2Snapshot,
} from './state';
interface LocalMediaSnapshotExpectation {
nextOperationId?: number;
connectionStatus?: string;
microphoneStatus?: string;
microphoneEnabled?: boolean;
localSpeakingOverride?: boolean | null;
audioMode?: string;
locallyMuted?: boolean;
preferredLocallyMuted?: boolean;
locallyDeafened?: boolean;
mutedByPermission?: boolean;
shouldUnmuteOnUndeafen?: boolean;
publishedDeviceId?: string | null;
desiredDeviceId?: string | null;
selectedAudioInputId?: string | null;
permissionStatus?: string;
lastFailureCode?: string | null;
}
interface LocalMediaCheckpoint extends LocalMediaSnapshotExpectation {
afterEvent: number;
}
interface LocalMediaFixture {
name: string;
events: Array<VoiceEngineV2Event>;
expectedCommands: Array<VoiceEngineV2Command>;
checkpoints?: Array<LocalMediaCheckpoint>;
expectedSnapshot: LocalMediaSnapshotExpectation;
}
const fixtures = [
localMediaFixtureJson,
pttPtmFixtureJson,
deviceHotswapFixtureJson,
permissionsRecoveryFixtureJson,
microphoneFailureRecoveryFixtureJson,
] as Array<LocalMediaFixture>;
function applyExpectation(snapshot: VoiceEngineV2Snapshot, expected: LocalMediaSnapshotExpectation): void {
if (expected.nextOperationId !== undefined) expect(snapshot.nextOperationId).toBe(expected.nextOperationId);
if (expected.connectionStatus !== undefined) expect(snapshot.connection.status).toBe(expected.connectionStatus);
if (expected.microphoneStatus !== undefined) expect(snapshot.microphone.status).toBe(expected.microphoneStatus);
if (expected.microphoneEnabled !== undefined) expect(snapshot.microphone.enabled).toBe(expected.microphoneEnabled);
if (expected.localSpeakingOverride !== undefined) {
expect(snapshot.microphone.localSpeakingOverride).toBe(expected.localSpeakingOverride);
}
if (expected.audioMode !== undefined) expect(snapshot.audioControls.mode).toBe(expected.audioMode);
if (expected.locallyMuted !== undefined) expect(snapshot.audioControls.locallyMuted).toBe(expected.locallyMuted);
if (expected.preferredLocallyMuted !== undefined) {
expect(snapshot.audioControls.preferredLocallyMuted).toBe(expected.preferredLocallyMuted);
}
if (expected.locallyDeafened !== undefined) {
expect(snapshot.audioControls.locallyDeafened).toBe(expected.locallyDeafened);
}
if (expected.mutedByPermission !== undefined) {
expect(snapshot.audioControls.mutedByPermission).toBe(expected.mutedByPermission);
}
if (expected.shouldUnmuteOnUndeafen !== undefined) {
expect(snapshot.audioControls.shouldUnmuteOnUndeafen).toBe(expected.shouldUnmuteOnUndeafen);
}
if (expected.publishedDeviceId !== undefined) {
expect(snapshot.microphone.published?.deviceId ?? null).toBe(expected.publishedDeviceId);
}
if (expected.desiredDeviceId !== undefined) {
expect(snapshot.microphone.desired?.deviceId ?? null).toBe(expected.desiredDeviceId);
}
if (expected.selectedAudioInputId !== undefined) {
expect(snapshot.devices.inventory.selectedAudioInputId).toBe(expected.selectedAudioInputId);
}
if (expected.permissionStatus !== undefined) {
expect(snapshot.permissions.results.microphone?.status).toBe(expected.permissionStatus);
}
if (expected.lastFailureCode !== undefined) expect(snapshot.lastFailure?.code ?? null).toBe(expected.lastFailureCode);
}
describe('voice engine v2 local media fixture replay', () => {
it.each(fixtures)('replays local media fixture: $name', (fixture) => {
let snapshot = createVoiceEngineV2InitialSnapshot(availableVoiceEngineV2Capabilities());
const commands: Array<VoiceEngineV2Command> = [];
fixture.events.forEach((event, index) => {
const transition = transitionVoiceEngineV2(snapshot, event);
snapshot = transition.snapshot;
commands.push(...transition.commands);
for (const checkpoint of fixture.checkpoints ?? []) {
if (checkpoint.afterEvent === index + 1) applyExpectation(snapshot, checkpoint);
}
});
expect(commands).toEqual(fixture.expectedCommands);
applyExpectation(snapshot, fixture.expectedSnapshot);
});
});
@@ -0,0 +1,158 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {describe, expect, it} from 'vitest';
import capabilitiesLifecycleFixtureJson from '../../fixtures/event_logs/capabilities_lifecycle.json';
import deviceInventoryFixtureJson from '../../fixtures/event_logs/device_inventory.json';
import diagnosticsLifecycleFixtureJson from '../../fixtures/event_logs/diagnostics_lifecycle.json';
import e2eeLifecycleFixtureJson from '../../fixtures/event_logs/e2ee_lifecycle.json';
import statsLifecycleFixtureJson from '../../fixtures/event_logs/stats_lifecycle.json';
import type {VoiceEngineV2Command} from '../protocol/commands';
import type {VoiceEngineV2Event} from '../protocol/events';
import {transitionVoiceEngineV2} from './reducer';
import {
selectVoiceEngineV2CapabilitiesProjection,
selectVoiceEngineV2DeviceProjection,
selectVoiceEngineV2DiagnosticsProjection,
selectVoiceEngineV2E2eeProjection,
selectVoiceEngineV2StatsProjection,
} from './selectors';
import {
availableVoiceEngineV2Capabilities,
createVoiceEngineV2InitialSnapshot,
type VoiceEngineV2Snapshot,
} from './state';
interface VoiceEngineV2EventLogFixture<Expected> {
name: string;
events: Array<VoiceEngineV2Event>;
expectedCommands: Array<Array<VoiceEngineV2Command>>;
expected: Expected;
}
interface StatsLifecycleExpected {
nextOperationId: number;
statsOperationId: number | null;
statsFailureCode: string | null;
summary: Record<string, unknown>;
}
interface DiagnosticsLifecycleExpected {
nextOperationId: number;
entryIds: Array<string>;
lastCode: string;
operationStatuses: Record<string, string>;
}
interface CapabilitiesLifecycleExpected {
nextOperationId: number;
hardwareEncoderOperationId: number | null;
hardwareEncoderFailureCode: string | null;
hardwareEncoderBackend: string;
hasZeroCopyNativeInput: boolean;
hasNativeNvencH264: boolean;
hasNativeNvencH265: boolean;
}
interface E2eeLifecycleExpected {
nextOperationId: number;
status: string;
keyId: string | null;
operationId: number | null;
failureCode: string | null;
}
interface DeviceInventoryExpected {
nextOperationId: number;
deviceOperationId: number | null;
failureCode: string | null;
selectedAudioInputId: string | null;
selectedAudioInputLabel: string | null;
selectedAudioInputRole: string | null;
audioOutputCount: number;
cameraCount: number;
operationStatuses: Record<string, string>;
}
function replayFixture<Expected>(
fixture: VoiceEngineV2EventLogFixture<Expected>,
initialSnapshot: VoiceEngineV2Snapshot = createVoiceEngineV2InitialSnapshot(availableVoiceEngineV2Capabilities()),
): VoiceEngineV2Snapshot {
let snapshot = initialSnapshot;
fixture.events.forEach((event, index) => {
const transition = transitionVoiceEngineV2(snapshot, event);
expect(transition.commands).toEqual(fixture.expectedCommands[index]);
snapshot = transition.snapshot;
});
return snapshot;
}
describe('voice engine v2 observability lifecycle fixtures', () => {
it('replays stats collection into the stats summary projection', () => {
const fixture = statsLifecycleFixtureJson as VoiceEngineV2EventLogFixture<StatsLifecycleExpected>;
const snapshot = replayFixture(fixture);
const statsProjection = selectVoiceEngineV2StatsProjection(snapshot);
expect(snapshot.nextOperationId).toBe(fixture.expected.nextOperationId);
expect(snapshot.statsOperationId).toBe(fixture.expected.statsOperationId);
expect(snapshot.statsFailure?.code ?? null).toBe(fixture.expected.statsFailureCode);
expect(statsProjection.summary).toMatchObject(fixture.expected.summary);
});
it('replays diagnostics logging into append-only diagnostics projection', () => {
const fixture = diagnosticsLifecycleFixtureJson as VoiceEngineV2EventLogFixture<DiagnosticsLifecycleExpected>;
const snapshot = replayFixture(fixture);
const diagnosticsProjection = selectVoiceEngineV2DiagnosticsProjection(snapshot);
expect(snapshot.nextOperationId).toBe(fixture.expected.nextOperationId);
expect(diagnosticsProjection.entries.map((entry) => entry.id)).toEqual(fixture.expected.entryIds);
expect(diagnosticsProjection.entries.at(-1)?.code).toBe(fixture.expected.lastCode);
expect(
Object.fromEntries(Object.entries(snapshot.operations).map(([id, operation]) => [id, operation.status])),
).toEqual(fixture.expected.operationStatuses);
});
it('replays hardware capability discovery into the capabilities projection', () => {
const fixture = capabilitiesLifecycleFixtureJson as VoiceEngineV2EventLogFixture<CapabilitiesLifecycleExpected>;
const snapshot = replayFixture(fixture);
const capabilitiesProjection = selectVoiceEngineV2CapabilitiesProjection(snapshot);
expect(snapshot.nextOperationId).toBe(fixture.expected.nextOperationId);
expect(snapshot.hardwareEncoder.operationId).toBe(fixture.expected.hardwareEncoderOperationId);
expect(snapshot.hardwareEncoder.failure?.code ?? null).toBe(fixture.expected.hardwareEncoderFailureCode);
expect(capabilitiesProjection.hardwareEncoderCapabilities?.backend).toBe(fixture.expected.hardwareEncoderBackend);
expect(capabilitiesProjection.hasZeroCopyNativeInput).toBe(fixture.expected.hasZeroCopyNativeInput);
expect(capabilitiesProjection.hasNativeNvencH264).toBe(fixture.expected.hasNativeNvencH264);
expect(capabilitiesProjection.hasNativeNvencH265).toBe(fixture.expected.hasNativeNvencH265);
});
it('rejects stale E2EE completions and projects public E2EE state without reducer bookkeeping', () => {
const fixture = e2eeLifecycleFixtureJson as VoiceEngineV2EventLogFixture<E2eeLifecycleExpected>;
const snapshot = replayFixture(fixture);
const e2eeProjection = selectVoiceEngineV2E2eeProjection(snapshot);
expect(snapshot.nextOperationId).toBe(fixture.expected.nextOperationId);
expect(e2eeProjection.status).toBe(fixture.expected.status);
expect(e2eeProjection.keyId).toBe(fixture.expected.keyId);
expect(e2eeProjection.failure?.code ?? null).toBe(fixture.expected.failureCode);
expect('operationId' in e2eeProjection).toBe(false);
expect(snapshot.e2ee.operationId).toBe(fixture.expected.operationId);
});
it('replays typed device inventory and clears selection operations', () => {
const fixture = deviceInventoryFixtureJson as VoiceEngineV2EventLogFixture<DeviceInventoryExpected>;
const snapshot = replayFixture(fixture);
const deviceProjection = selectVoiceEngineV2DeviceProjection(snapshot);
expect(snapshot.nextOperationId).toBe(fixture.expected.nextOperationId);
expect(snapshot.devices.operationId).toBe(fixture.expected.deviceOperationId);
expect(snapshot.devices.failure?.code ?? null).toBe(fixture.expected.failureCode);
expect(deviceProjection.devices.selectedAudioInputId).toBe(fixture.expected.selectedAudioInputId);
expect(deviceProjection.selectedAudioInput?.label ?? null).toBe(fixture.expected.selectedAudioInputLabel);
expect(deviceProjection.selectedAudioInput?.role ?? null).toBe(fixture.expected.selectedAudioInputRole);
expect(deviceProjection.devices.audioOutputs).toHaveLength(fixture.expected.audioOutputCount);
expect(deviceProjection.devices.cameras).toHaveLength(fixture.expected.cameraCount);
expect(
Object.fromEntries(Object.entries(snapshot.operations).map(([id, operation]) => [id, operation.status])),
).toEqual(fixture.expected.operationStatuses);
});
});
@@ -0,0 +1,189 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {describe, expect, it} from 'vitest';
import inboundVideoFrameStatsFixtureJson from '../../fixtures/event_logs/inbound_video_frame_stats.json';
import inboundVideoFramesFixtureJson from '../../fixtures/event_logs/inbound_video_frames.json';
import participantsTracksFixtureJson from '../../fixtures/event_logs/participants_tracks.json';
import subscriptionQualityFixtureJson from '../../fixtures/event_logs/subscription_quality.json';
import watchedStreamsFixtureJson from '../../fixtures/event_logs/watched_streams.json';
import {translateVoiceEngineV2BridgeEventToEvents, translateVoiceEngineV2BridgeVideoFrameToEvent} from '../bridge';
import type {VoiceEngineV2Command} from '../protocol/commands';
import type {VoiceEngineV2Event} from '../protocol/events';
import type {
VoiceEngineV2InboundVideoTrack,
VoiceEngineV2Participant,
VoiceEngineV2RemoteTrackSubscriptionOptions,
VoiceEngineV2Track,
VoiceEngineV2WatchedStream,
} from '../protocol/types';
import {transitionVoiceEngineV2} from './reducer';
import {selectVoiceEngineV2ParticipantProjection, selectVoiceEngineV2WatchedStreams} from './selectors';
import {
availableVoiceEngineV2Capabilities,
createVoiceEngineV2InitialSnapshot,
type VoiceEngineV2Snapshot,
} from './state';
interface ParticipantsTracksFixtureExpected {
participants?: Array<VoiceEngineV2Participant>;
tracks?: Array<VoiceEngineV2Track>;
inboundVideoTracks?: Array<VoiceEngineV2InboundVideoTrack>;
droppedFrameCount?: number;
watchedStreams?: Array<VoiceEngineV2WatchedStream>;
remoteTrackSubscriptions?: Record<string, VoiceEngineV2RemoteTrackSubscriptionOptions>;
participantVolumes?: Record<string, number>;
commands?: Array<VoiceEngineV2Command>;
}
interface ParticipantsTracksFixture {
name: string;
events: Array<VoiceEngineV2Event>;
expected: ParticipantsTracksFixtureExpected;
}
function replayFixture(fixture: ParticipantsTracksFixture): {
snapshot: VoiceEngineV2Snapshot;
commands: Array<VoiceEngineV2Command>;
} {
let snapshot = createVoiceEngineV2InitialSnapshot(availableVoiceEngineV2Capabilities());
const commands: Array<VoiceEngineV2Command> = [];
for (const event of fixture.events) {
const transition = transitionVoiceEngineV2(snapshot, event);
snapshot = transition.snapshot;
commands.push(...transition.commands);
}
return {snapshot, commands};
}
function assertFixture(fixture: ParticipantsTracksFixture): void {
const {snapshot, commands} = replayFixture(fixture);
const participantProjection = selectVoiceEngineV2ParticipantProjection(snapshot);
if (fixture.expected.participants) {
expect(participantProjection.participants).toEqual(fixture.expected.participants);
}
if (fixture.expected.tracks) {
expect(participantProjection.tracks).toEqual(fixture.expected.tracks);
}
if (fixture.expected.inboundVideoTracks) {
expect(participantProjection.inboundVideoTracks).toEqual(fixture.expected.inboundVideoTracks);
}
if (fixture.expected.droppedFrameCount !== undefined) {
expect(snapshot.inboundVideo.droppedFrameCount).toBe(fixture.expected.droppedFrameCount);
}
if (fixture.expected.watchedStreams) {
expect(selectVoiceEngineV2WatchedStreams(snapshot)).toEqual(fixture.expected.watchedStreams);
}
if (fixture.expected.remoteTrackSubscriptions) {
expect(snapshot.remoteTrackSubscriptions).toEqual(fixture.expected.remoteTrackSubscriptions);
}
if (fixture.expected.participantVolumes) {
expect(snapshot.participantVolumes).toEqual(fixture.expected.participantVolumes);
}
if (fixture.expected.commands) {
expect(commands).toEqual(fixture.expected.commands);
}
}
describe('voice engine v2 participants, subscriptions, and inbound video fixtures', () => {
for (const fixture of [
participantsTracksFixtureJson,
subscriptionQualityFixtureJson,
watchedStreamsFixtureJson,
inboundVideoFramesFixtureJson,
inboundVideoFrameStatsFixtureJson,
] as Array<ParticipantsTracksFixture>) {
it(`replays ${fixture.name}`, () => {
assertFixture(fixture);
});
}
it('removes participant-owned room and inbound-video state when a participant leaves by sid', () => {
const fixture: ParticipantsTracksFixture = {
name: 'participant sid cleanup',
events: [
{type: 'room.participantJoined', participant: {sid: 'PA_alice', identity: 'alice', name: 'Alice'}},
{
type: 'room.trackPublished',
track: {
participantIdentity: 'alice',
participantSid: 'PA_alice',
trackSid: 'TR_screen',
trackName: 'screen',
kind: 'video',
source: 'screen',
muted: false,
},
},
{
type: 'inboundVideo.trackSubscribed',
track: {
participantSid: 'PA_alice',
participantIdentity: 'alice',
trackSid: 'TR_screen',
source: 'screen',
},
},
{type: 'room.participantLeft', participantSid: 'PA_alice'},
],
expected: {
participants: [],
tracks: [],
inboundVideoTracks: [],
commands: [],
},
};
assertFixture(fixture);
});
it('translates native bridge participant, track, and frame events into v2 projections', () => {
let snapshot = createVoiceEngineV2InitialSnapshot(availableVoiceEngineV2Capabilities());
const bridgeEvents = translateVoiceEngineV2BridgeEventToEvents({
type: 'trackSubscribed',
payload: {
participantSid: 'PA_alice',
identity: 'alice',
participantName: 'Alice',
trackSid: 'TR_screen',
trackName: 'screen_share',
kind: 'video',
source: 'screen_share',
muted: false,
subscribed: true,
subscriptionStatus: 'subscribed',
},
});
for (const event of bridgeEvents) {
snapshot = transitionVoiceEngineV2(snapshot, event).snapshot;
}
snapshot = transitionVoiceEngineV2(
snapshot,
translateVoiceEngineV2BridgeVideoFrameToEvent({
meta: {
participantSid: 'PA_alice',
trackSid: 'TR_screen',
width: 320,
height: 180,
timestampUs: 100,
},
data: new ArrayBuffer(86_400),
}),
).snapshot;
expect(snapshot.room.tracks.TR_screen).toMatchObject({
participantIdentity: 'alice',
participantSid: 'PA_alice',
source: 'screen',
kind: 'video',
});
expect(snapshot.inboundVideo.tracks.TR_screen).toMatchObject({
participantSid: 'PA_alice',
participantIdentity: 'alice',
source: 'screen',
width: 320,
height: 180,
frameCount: 1,
lastFrameByteLength: 86_400,
});
});
});
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {bench, describe} from 'vitest';
import basicSessionFixture from '../../fixtures/basic_session.json';
import type {VoiceEngineV2Event} from '../protocol/events';
import type {
VoiceEngineV2InboundVideoFrame,
VoiceEngineV2InboundVideoFrameStats,
VoiceEngineV2MicrophoneOptions,
VoiceEngineV2Participant,
VoiceEngineV2ScreenOptions,
VoiceEngineV2Stats,
} from '../protocol/types';
import {transitionVoiceEngineV2} from './reducer';
import {
availableVoiceEngineV2Capabilities,
createVoiceEngineV2InitialSnapshot,
type VoiceEngineV2Snapshot,
} from './state';
interface VoiceEngineV2BenchFixture {
events: Array<VoiceEngineV2Event>;
}
function buildConnectedSnapshotWithVideoTrack(): VoiceEngineV2Snapshot {
const fixture = basicSessionFixture as VoiceEngineV2BenchFixture;
let snapshot = createVoiceEngineV2InitialSnapshot(availableVoiceEngineV2Capabilities());
for (const event of fixture.events) {
snapshot = transitionVoiceEngineV2(snapshot, event).snapshot;
}
snapshot = transitionVoiceEngineV2(snapshot, {
type: 'inboundVideo.trackSubscribed',
track: {
participantSid: 'sid-remote',
participantIdentity: 'identity-remote',
trackSid: 'track-video-1',
source: 'camera',
width: 1280,
height: 720,
},
}).snapshot;
return snapshot;
}
const connectedSnapshot = buildConnectedSnapshotWithVideoTrack();
const frameEvent: VoiceEngineV2Event = {
type: 'inboundVideo.frameReceived',
frame: Object.freeze({
participantSid: 'sid-remote',
participantIdentity: 'identity-remote',
trackSid: 'track-video-1',
width: 1280,
height: 720,
timestampUs: 1_000_000,
byteLength: 320_000,
}) satisfies VoiceEngineV2InboundVideoFrame,
};
const frameStatsEvent: VoiceEngineV2Event = {
type: 'inboundVideo.frameStats',
stats: Object.freeze({
participantSid: 'sid-remote',
participantIdentity: 'identity-remote',
trackSid: 'track-video-1',
width: 1280,
height: 720,
frameCount: 900,
lastFrameTimestampUs: 30_000_000,
lastFrameByteLength: 320_000,
}) satisfies VoiceEngineV2InboundVideoFrameStats,
};
const microphoneEvent: VoiceEngineV2Event = {
type: 'microphone.publishRequested',
options: Object.freeze({deviceId: 'mic-alt', echoCancellation: true}) satisfies VoiceEngineV2MicrophoneOptions,
};
const screenEvent: VoiceEngineV2Event = {
type: 'screen.publishRequested',
options: Object.freeze({
captureId: 'capture-1',
width: 1920,
height: 1080,
codec: 'h264',
maxFramerate: 30,
maxBitrateBps: 4_000_000,
}) satisfies VoiceEngineV2ScreenOptions,
};
const participantEvent: VoiceEngineV2Event = {
type: 'room.participantJoined',
participant: Object.freeze({
sid: 'sid-joiner',
identity: 'identity-joiner',
name: 'Joiner',
}) satisfies VoiceEngineV2Participant,
};
const statsPayload: VoiceEngineV2Stats = Object.freeze({rttMs: 42, outbound: [], inbound: []});
const statsEvent: VoiceEngineV2Event = {type: 'stats.collected', operationId: 9_999_999, stats: statsPayload};
describe('voice engine v2 reducer hot paths', () => {
bench('inboundVideo.frameReceived', () => {
transitionVoiceEngineV2(connectedSnapshot, frameEvent);
});
bench('inboundVideo.frameStats', () => {
transitionVoiceEngineV2(connectedSnapshot, frameStatsEvent);
});
bench('microphone.publishRequested', () => {
transitionVoiceEngineV2(connectedSnapshot, microphoneEvent);
});
bench('screen.publishRequested', () => {
transitionVoiceEngineV2(connectedSnapshot, screenEvent);
});
bench('room.participantJoined', () => {
transitionVoiceEngineV2(connectedSnapshot, participantEvent);
});
bench('stats.collected', () => {
transitionVoiceEngineV2(connectedSnapshot, statsEvent);
});
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../protocol/events';
import {
dispatchLocalMediaEvent,
dispatchObservabilityEvent,
dispatchPlatformEvent,
dispatchRuntimeEvent,
dispatchSessionEvent,
} from './reducers/_dispatch';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from './state';
export {isVoiceEngineV2CommandCompletionStale} from './commandCompletion';
export function transitionVoiceEngineV2(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2Event,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionVoiceEngineV2 snapshot must not be null');
assert.ok(event != null, 'transitionVoiceEngineV2 event must not be null');
assert.equal(typeof event.type, 'string', 'event.type must be a string');
assert.ok(event.type.length > 0, 'event.type must not be empty');
const local = dispatchLocalMediaEvent(snapshot, event);
if (local) return local;
const session = dispatchSessionEvent(snapshot, event);
if (session) return session;
const platform = dispatchPlatformEvent(snapshot, event);
if (platform) return platform;
const runtime = dispatchRuntimeEvent(snapshot, event);
if (runtime) return runtime;
const observability = dispatchObservabilityEvent(snapshot, event);
if (observability) return observability;
return {snapshot, commands: []};
}
@@ -0,0 +1,286 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {planDesiredState} from './_plan';
import {transitionCamera} from './camera';
import {transitionCapabilities} from './capabilities';
import {transitionCodecNegotiation} from './codecNegotiation';
import {transitionCommand} from './command';
import {transitionConnection} from './connection';
import {transitionData} from './data';
import {transitionDevices} from './devices';
import {transitionE2ee} from './e2ee';
import {transitionGateway} from './gateway';
import {transitionImplementation} from './implementation';
import {transitionInboundVideo} from './inboundVideo';
import {transitionLifecycle} from './lifecycle';
import {transitionMicrophone} from './microphone';
import {transitionNativeAudioDeviceModule} from './nativeAudioDeviceModule';
import {transitionNativeAudioTap} from './nativeAudioTap';
import {transitionNativeCapture} from './nativeCapture';
import {transitionNativeFrameSink} from './nativeFrameSink';
import {transitionOutputDevice} from './outputDevice';
import {transitionParticipantVolume} from './participantVolume';
import {transitionPermissions} from './permissions';
import {transitionRemoteTrackSubscription} from './remoteTrackSubscription';
import {transitionRoom} from './room';
import {transitionScreen} from './screen';
import {transitionScreenAudio} from './screenAudio';
import {transitionSourceLifecycles} from './sourceLifecycles';
import {transitionStats} from './stats';
import {transitionUtilityPorts} from './utilityPorts';
export function dispatchLocalMediaEvent(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2Event,
): VoiceEngineV2Transition | null {
assert.ok(snapshot != null, 'dispatchLocalMediaEvent snapshot must not be null');
assert.ok(event != null, 'dispatchLocalMediaEvent event must not be null');
assert.equal(typeof event.type, 'string', 'dispatchLocalMediaEvent event.type must be a string');
switch (event.type) {
case 'implementation.prewarmRequested':
case 'implementation.prewarmSucceeded':
case 'implementation.prewarmFailed':
return transitionImplementation(snapshot, event);
case 'connection.connectRequested':
case 'connection.connectSucceeded':
case 'connection.connectFailed':
case 'connection.disconnectRequested':
case 'connection.disconnectSucceeded':
case 'connection.disconnectFailed':
case 'connection.remoteDisconnected':
case 'connection.reconnectRequested':
case 'connection.externallyEstablished':
return transitionConnection(snapshot, event, planDesiredState);
case 'microphone.publishRequested':
case 'microphone.publishSucceeded':
case 'microphone.publishFailed':
case 'microphone.unpublishRequested':
case 'microphone.unpublishSucceeded':
case 'microphone.unpublishFailed':
case 'microphone.setEnabledRequested':
case 'microphone.setEnabledSucceeded':
case 'microphone.setEnabledFailed':
case 'localAudio.muteRequested':
case 'localAudio.deafenRequested':
case 'audioControls.changed':
return transitionMicrophone(snapshot, event);
case 'camera.publishRequested':
case 'camera.publishSucceeded':
case 'camera.publishFailed':
case 'camera.updateEncodingRequested':
case 'camera.updateEncodingSucceeded':
case 'camera.updateEncodingFailed':
case 'camera.unpublishRequested':
case 'camera.unpublishSucceeded':
case 'camera.unpublishFailed':
return transitionCamera(snapshot, event);
default:
return dispatchScreenEvent(snapshot, event);
}
}
function dispatchScreenEvent(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2Event,
): VoiceEngineV2Transition | null {
assert.ok(snapshot != null, 'dispatchScreenEvent snapshot must not be null');
assert.ok(event != null, 'dispatchScreenEvent event must not be null');
switch (event.type) {
case 'screen.publishRequested':
case 'screen.publishSucceeded':
case 'screen.publishFailed':
case 'screen.updateEncodingRequested':
case 'screen.updateEncodingSucceeded':
case 'screen.updateEncodingFailed':
case 'screen.unpublishRequested':
case 'screen.unpublishSucceeded':
case 'screen.unpublishFailed':
return transitionScreen(snapshot, event);
case 'screenAudio.publishRequested':
case 'screenAudio.publishSucceeded':
case 'screenAudio.publishFailed':
case 'screenAudio.unpublishRequested':
case 'screenAudio.unpublishSucceeded':
case 'screenAudio.unpublishFailed':
return transitionScreenAudio(snapshot, event);
default:
return null;
}
}
export function dispatchSessionEvent(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2Event,
): VoiceEngineV2Transition | null {
assert.ok(snapshot != null, 'dispatchSessionEvent snapshot must not be null');
assert.ok(event != null, 'dispatchSessionEvent event must not be null');
assert.equal(typeof event.type, 'string', 'dispatchSessionEvent event.type must be a string');
switch (event.type) {
case 'outputDevice.setRequested':
case 'outputDevice.setSucceeded':
case 'outputDevice.setFailed':
return transitionOutputDevice(snapshot, event);
case 'participantVolume.setRequested':
case 'participantVolume.setSucceeded':
case 'participantVolume.setFailed':
return transitionParticipantVolume(snapshot, event);
case 'remoteTrackSubscription.setRequested':
case 'remoteTrackSubscription.setSucceeded':
case 'remoteTrackSubscription.setFailed':
case 'watchedStream.watchRequested':
case 'watchedStream.unwatchRequested':
case 'watchedStreams.replaced':
return transitionRemoteTrackSubscription(snapshot, event);
case 'data.publishRequested':
case 'data.publishSucceeded':
case 'data.publishFailed':
return transitionData(snapshot, event);
case 'codecNegotiation.overrideSetRequested':
case 'codecNegotiation.localCapabilityChanged':
case 'codecNegotiation.remoteCapabilityChanged':
case 'codecNegotiation.streamRegistered':
case 'codecNegotiation.streamUnregistered':
case 'codecNegotiation.viewerChanged':
return transitionCodecNegotiation(snapshot, event);
case 'stats.collectRequested':
case 'stats.collected':
case 'stats.collectFailed':
return transitionStats(snapshot, event);
case 'capabilities.changed':
case 'capabilities.hardwareEncoderQueryRequested':
case 'capabilities.hardwareEncoderChanged':
case 'capabilities.hardwareEncoderQueryFailed':
return transitionCapabilities(snapshot, event);
default:
return null;
}
}
export function dispatchPlatformEvent(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2Event,
): VoiceEngineV2Transition | null {
assert.ok(snapshot != null, 'dispatchPlatformEvent snapshot must not be null');
assert.ok(event != null, 'dispatchPlatformEvent event must not be null');
assert.equal(typeof event.type, 'string', 'dispatchPlatformEvent event.type must be a string');
switch (event.type) {
case 'permissions.checkRequested':
case 'permissions.requestRequested':
case 'permissions.result':
case 'permissions.failed':
return transitionPermissions(snapshot, event);
case 'devices.enumerateRequested':
case 'devices.changed':
case 'devices.enumerateFailed':
case 'devices.selectAudioInputRequested':
case 'devices.selectAudioOutputRequested':
case 'devices.selectCameraRequested':
return transitionDevices(snapshot, event);
case 'nativeAudioDeviceModule.statusChanged':
return transitionNativeAudioDeviceModule(snapshot, event);
case 'nativeCapture.startRequested':
case 'nativeCapture.updateRequested':
case 'nativeCapture.stopRequested':
case 'nativeCapture.started':
case 'nativeCapture.stopped':
case 'nativeCapture.failed':
case 'nativeCapture.frame':
return transitionNativeCapture(snapshot, event);
case 'nativeAudioTap.startRequested':
case 'nativeAudioTap.stopRequested':
return transitionNativeAudioTap(snapshot, event);
case 'nativeFrameSink.attachRequested':
case 'nativeFrameSink.detachRequested':
return transitionNativeFrameSink(snapshot, event);
case 'e2ee.setEnabledRequested':
case 'e2ee.enabled':
case 'e2ee.disabled':
case 'e2ee.failed':
return transitionE2ee(snapshot, event);
default:
return null;
}
}
export function dispatchRuntimeEvent(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2Event,
): VoiceEngineV2Transition | null {
assert.ok(snapshot != null, 'dispatchRuntimeEvent snapshot must not be null');
assert.ok(event != null, 'dispatchRuntimeEvent event must not be null');
assert.equal(typeof event.type, 'string', 'dispatchRuntimeEvent event.type must be a string');
switch (event.type) {
case 'timer.scheduleRequested':
case 'timer.cancelRequested':
case 'timer.fired':
case 'diagnostics.logRequested':
case 'diagnostics.logged':
return transitionUtilityPorts(snapshot, event);
case 'lifecycle.teardownRequested':
case 'lifecycle.teardownSucceeded':
case 'lifecycle.teardownFailed':
return transitionLifecycle(snapshot, event);
case 'room.participantJoined':
case 'room.participantLeft':
case 'room.trackPublished':
case 'room.trackUnpublished':
case 'room.trackMuted':
case 'room.trackUnmuted':
return transitionRoom(snapshot, event);
case 'inboundVideo.trackSubscribed':
case 'inboundVideo.trackUnsubscribed':
case 'inboundVideo.frameReceived':
case 'inboundVideo.frameStats':
return transitionInboundVideo(snapshot, event);
default:
return dispatchControlEvent(snapshot, event);
}
}
function dispatchControlEvent(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2Event,
): VoiceEngineV2Transition | null {
assert.ok(snapshot != null, 'dispatchControlEvent snapshot must not be null');
assert.ok(event != null, 'dispatchControlEvent event must not be null');
switch (event.type) {
case 'command.succeeded':
case 'command.failed':
case 'command.staleCompletionRejected':
case 'operation.cancelRequested':
case 'operation.cancelled':
return transitionCommand(snapshot, event);
case 'gateway.desiredVoiceStateChanged':
case 'gateway.voiceStateReconcileRequested':
case 'gateway.voiceStateWriteRequested':
case 'gateway.voiceStateWriteSucceeded':
case 'gateway.voiceStateWriteFailed':
case 'gateway.voiceStateClearRequested':
case 'gateway.voiceStateClearSucceeded':
case 'gateway.voiceStateClearFailed':
case 'gateway.voiceStateUpdated':
case 'gateway.voiceServerUpdated':
case 'livekit.roomStateChanged':
return transitionGateway(snapshot, event);
default:
return null;
}
}
export function dispatchObservabilityEvent(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2Event,
): VoiceEngineV2Transition | null {
assert.ok(snapshot != null, 'dispatchObservabilityEvent snapshot must not be null');
assert.ok(event != null, 'dispatchObservabilityEvent event must not be null');
assert.equal(typeof event.type, 'string', 'dispatchObservabilityEvent event.type must be a string');
switch (event.type) {
case 'sourceLifecycle.transitioned':
return transitionSourceLifecycles(snapshot, event);
default:
return null;
}
}
@@ -0,0 +1,372 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {getVoiceEngineV2CommandResourceKey, type VoiceEngineV2Command} from '../../protocol/commands';
import type {
VoiceEngineV2DataOptions,
VoiceEngineV2DiagnosticEntry,
VoiceEngineV2Error,
VoiceEngineV2OperationId,
VoiceEngineV2OutputDeviceOptions,
VoiceEngineV2ParticipantVolumeOptions,
VoiceEngineV2RemoteTrackSubscriptionOptions,
VoiceEngineV2ResourceKey,
} from '../../protocol/types';
import type {
VoiceEngineV2OperationState,
VoiceEngineV2OperationStatus,
VoiceEngineV2Snapshot,
VoiceEngineV2Transition,
} from '../state';
export const VOICE_ENGINE_V2_TERMINAL_OPERATIONS_KEPT_MAX = 64;
const VOICE_ENGINE_V2_DIAGNOSTICS_KEPT_MAX = 200;
const VOICE_ENGINE_V2_TERMINAL_OPERATION_STATUSES: ReadonlySet<VoiceEngineV2OperationStatus> = new Set([
'succeeded',
'failed',
'cancelled',
'stale',
]);
interface OperationAllocation {
snapshot: VoiceEngineV2Snapshot;
operationId: VoiceEngineV2OperationId;
}
function isTerminalVoiceEngineV2OperationStatus(status: VoiceEngineV2OperationStatus): boolean {
assert.equal(typeof status, 'string', 'isTerminalVoiceEngineV2OperationStatus status must be a string');
assert.ok(status.length > 0, 'isTerminalVoiceEngineV2OperationStatus status must not be empty');
return VOICE_ENGINE_V2_TERMINAL_OPERATION_STATUSES.has(status);
}
function pruneTerminalOperations(
operations: Record<string, VoiceEngineV2OperationState>,
): Record<string, VoiceEngineV2OperationState> {
assert.ok(operations != null, 'pruneTerminalOperations operations must not be null');
const terminalOperationIds: Array<number> = [];
for (const operation of Object.values(operations)) {
if (isTerminalVoiceEngineV2OperationStatus(operation.status)) {
terminalOperationIds.push(operation.operationId);
}
}
if (terminalOperationIds.length <= VOICE_ENGINE_V2_TERMINAL_OPERATIONS_KEPT_MAX) return operations;
terminalOperationIds.sort((a, b) => a - b);
const dropCount = terminalOperationIds.length - VOICE_ENGINE_V2_TERMINAL_OPERATIONS_KEPT_MAX;
assert.ok(dropCount >= 1, 'pruneTerminalOperations over cap must drop at least one entry');
const pruned = {...operations};
for (let index = 0; index < dropCount; index += 1) {
const operationId = terminalOperationIds[index];
assert.ok(operationId !== undefined, 'pruneTerminalOperations ids must not contain holes');
delete pruned[String(operationId)];
}
assert.ok(
Object.keys(pruned).length === Object.keys(operations).length - dropCount,
'pruneTerminalOperations must remove exactly the dropped entries',
);
return pruned;
}
function assertVoiceEngineV2Transition(result: VoiceEngineV2Transition): void {
assert.ok(result != null, 'transition result must not be null');
assert.ok(result.snapshot != null, 'transition snapshot must not be null');
assert.ok(Array.isArray(result.commands), 'transition commands must be an array');
}
export function implementationError(message: string, capability?: string): VoiceEngineV2Error {
assert.equal(typeof message, 'string', 'implementationError message must be a string');
assert.ok(message.length > 0, 'implementationError message must not be empty');
return {
code: 'implementationError',
message,
...(capability ? {capability} : {}),
};
}
export function invalidArgument(message: string, capability?: string): VoiceEngineV2Error {
assert.equal(typeof message, 'string', 'invalidArgument message must be a string');
assert.ok(message.length > 0, 'invalidArgument message must not be empty');
return {
code: 'invalidArgument',
message,
...(capability ? {capability} : {}),
};
}
export function unsupportedCapability(capability: string): VoiceEngineV2Error {
assert.equal(typeof capability, 'string', 'unsupportedCapability capability must be a string');
assert.ok(capability.length > 0, 'unsupportedCapability capability must not be empty');
return {
code: 'unsupportedCapability',
capability,
message: `Voice engine v2 implementation does not support ${capability}`,
};
}
export function allocateOperation(snapshot: VoiceEngineV2Snapshot): OperationAllocation {
assert.ok(snapshot != null, 'allocateOperation snapshot must not be null');
assert.ok(Number.isInteger(snapshot.nextOperationId), 'nextOperationId must be an integer');
assert.ok(snapshot.nextOperationId >= 1, 'nextOperationId must be positive');
assert.ok(snapshot.nextOperationId <= Number.MAX_SAFE_INTEGER, 'nextOperationId past safe-integer cap');
return {
operationId: snapshot.nextOperationId,
snapshot: {
...snapshot,
nextOperationId: snapshot.nextOperationId + 1,
},
};
}
export function recordQueuedCommand(
snapshot: VoiceEngineV2Snapshot,
command: VoiceEngineV2Command,
): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'recordQueuedCommand snapshot must not be null');
assert.ok(command != null, 'recordQueuedCommand command must not be null');
assert.ok(Number.isInteger(command.operationId), 'command.operationId must be an integer');
return {
...snapshot,
operations: {
...snapshot.operations,
[String(command.operationId)]: {
operationId: command.operationId,
commandType: command.type,
resourceKey: getVoiceEngineV2CommandResourceKey(command),
status: 'queued',
error: null,
},
},
};
}
export function queueCommand(snapshot: VoiceEngineV2Snapshot, command: VoiceEngineV2Command): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'queueCommand snapshot must not be null');
assert.ok(command != null, 'queueCommand command must not be null');
const result: VoiceEngineV2Transition = {
snapshot: recordQueuedCommand(snapshot, command),
commands: [command],
};
assertVoiceEngineV2Transition(result);
return result;
}
export function markOperation(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
status: VoiceEngineV2OperationStatus,
error: VoiceEngineV2Error | null = null,
): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'markOperation snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'markOperation operationId must be an integer');
assert.equal(typeof status, 'string', 'markOperation status must be a string');
const operation = snapshot.operations[String(operationId)];
if (!operation) return snapshot;
const operations: Record<string, VoiceEngineV2OperationState> = {
...snapshot.operations,
[String(operationId)]: {
...operation,
status,
error,
},
};
return {
...snapshot,
operations: isTerminalVoiceEngineV2OperationStatus(status) ? pruneTerminalOperations(operations) : operations,
};
}
export function isConnected(snapshot: VoiceEngineV2Snapshot): boolean {
assert.ok(snapshot != null, 'isConnected snapshot must not be null');
assert.ok(snapshot.connection != null, 'isConnected snapshot.connection must not be null');
return snapshot.connection.status === 'connected';
}
export function appendTransition(
base: VoiceEngineV2Transition,
next: VoiceEngineV2Transition,
): VoiceEngineV2Transition {
assertVoiceEngineV2Transition(base);
assertVoiceEngineV2Transition(next);
const result: VoiceEngineV2Transition = {
snapshot: next.snapshot,
commands: [...base.commands, ...next.commands],
};
assertVoiceEngineV2Transition(result);
return result;
}
export function appendDiagnostic(
diagnostics: Array<VoiceEngineV2DiagnosticEntry>,
entry: VoiceEngineV2DiagnosticEntry,
): Array<VoiceEngineV2DiagnosticEntry> {
assert.ok(Array.isArray(diagnostics), 'appendDiagnostic diagnostics must be an array');
assert.ok(entry != null, 'appendDiagnostic entry must not be null');
return [...diagnostics, entry].slice(-VOICE_ENGINE_V2_DIAGNOSTICS_KEPT_MAX);
}
export function recordFailure(snapshot: VoiceEngineV2Snapshot, error: VoiceEngineV2Error): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'recordFailure snapshot must not be null');
assert.ok(error != null, 'recordFailure error must not be null');
const result: VoiceEngineV2Transition = {
snapshot: {
...snapshot,
lastFailure: error,
},
commands: [],
};
assertVoiceEngineV2Transition(result);
return result;
}
export function clearOperationForResource(
snapshot: VoiceEngineV2Snapshot,
resourceKey: VoiceEngineV2ResourceKey,
operationId: VoiceEngineV2OperationId,
): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'clearOperationForResource snapshot must not be null');
assert.equal(typeof resourceKey, 'string', 'clearOperationForResource resourceKey must be a string');
assert.ok(Number.isInteger(operationId), 'clearOperationForResource operationId must be an integer');
if (resourceKey === 'connection' && snapshot.connection.operationId === operationId) {
return {...snapshot, connection: {...snapshot.connection, operationId: null}};
}
if (resourceKey === 'microphone' && snapshot.microphone.operationId === operationId) {
return {...snapshot, microphone: {...snapshot.microphone, operationId: null}};
}
if (resourceKey === 'camera' && snapshot.camera.operationId === operationId) {
return {...snapshot, camera: {...snapshot.camera, operationId: null}};
}
if (resourceKey === 'screen' && snapshot.screen.operationId === operationId) {
return {...snapshot, screen: {...snapshot.screen, operationId: null}};
}
if (resourceKey === 'screenAudio' && snapshot.screenAudio.operationId === operationId) {
return {...snapshot, screenAudio: {...snapshot.screenAudio, operationId: null}};
}
if (resourceKey === 'outputDevice' && snapshot.outputDevice.operationId === operationId) {
return {...snapshot, outputDevice: {...snapshot.outputDevice, operationId: null}};
}
if (resourceKey === 'stats' && snapshot.statsOperationId === operationId) {
return {...snapshot, statsOperationId: null};
}
if (resourceKey === 'capabilities' && snapshot.hardwareEncoder.operationId === operationId) {
return {...snapshot, hardwareEncoder: {...snapshot.hardwareEncoder, operationId: null}};
}
if (resourceKey === 'gateway' && snapshot.gateway.operationId === operationId) {
return {...snapshot, gateway: {...snapshot.gateway, operationId: null}};
}
if (resourceKey === 'devices' && snapshot.devices.operationId === operationId) {
return {...snapshot, devices: {...snapshot.devices, operationId: null}};
}
if (resourceKey === 'e2ee' && snapshot.e2ee.operationId === operationId) {
return {...snapshot, e2ee: {...snapshot.e2ee, operationId: null}};
}
if (resourceKey === 'lifecycle' && snapshot.lifecycle.operationId === operationId) {
return {...snapshot, lifecycle: {...snapshot.lifecycle, operationId: null}};
}
return snapshot;
}
type ImmediateCommandDraft =
| {type: 'outputDevice.set'; options: VoiceEngineV2OutputDeviceOptions}
| {type: 'participantVolume.set'; options: VoiceEngineV2ParticipantVolumeOptions}
| {type: 'remoteTrackSubscription.set'; options: VoiceEngineV2RemoteTrackSubscriptionOptions}
| {type: 'data.publish'; options: VoiceEngineV2DataOptions}
| {type: 'stats.collect'};
export function commandIfConnected(
snapshot: VoiceEngineV2Snapshot,
capability: keyof VoiceEngineV2Snapshot['capabilities'],
draft: ImmediateCommandDraft,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'commandIfConnected snapshot must not be null');
assert.ok(draft != null, 'commandIfConnected draft must not be null');
assert.equal(typeof draft.type, 'string', 'commandIfConnected draft.type must be a string');
if (!snapshot.capabilities[capability]) {
const error = unsupportedCapability(capability);
return {snapshot: {...snapshot, lastFailure: error}, commands: []};
}
if (!isConnected(snapshot)) return {snapshot, commands: []};
const allocated = allocateOperation(snapshot);
const command = {...draft, operationId: allocated.operationId} as VoiceEngineV2Command;
if (draft.type === 'outputDevice.set') {
return {
snapshot: {
...allocated.snapshot,
outputDevice: {...allocated.snapshot.outputDevice, operationId: allocated.operationId, failure: null},
},
commands: [command],
};
}
if (draft.type === 'stats.collect') {
return {
snapshot: {
...allocated.snapshot,
statsOperationId: allocated.operationId,
statsFailure: null,
},
commands: [command],
};
}
return {snapshot: allocated.snapshot, commands: [command]};
}
type UnpublishableMediaKey = 'microphone' | 'camera' | 'screen' | 'screenAudio';
type UnpublishCommandType = 'microphone.unpublish' | 'camera.unpublish' | 'screen.unpublish' | 'screenAudio.unpublish';
export function beginUnpublish(
snapshot: VoiceEngineV2Snapshot,
key: UnpublishableMediaKey,
commandType: UnpublishCommandType,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'beginUnpublish snapshot must not be null');
assert.equal(typeof key, 'string', 'beginUnpublish key must be a string');
assert.equal(typeof commandType, 'string', 'beginUnpublish commandType must be a string');
const state = snapshot[key];
const base = {
...snapshot,
[key]: {
...state,
desired: null,
},
};
if (!isConnected(base) || (state.status === 'idle' && state.published == null)) return {snapshot: base, commands: []};
const allocated = allocateOperation(base);
return {
snapshot: {
...allocated.snapshot,
[key]: {
...allocated.snapshot[key],
status: 'unpublishing',
operationId: allocated.operationId,
failure: null,
},
},
commands: [{type: commandType, operationId: allocated.operationId} as VoiceEngineV2Command],
};
}
export function failUnpublish(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
error: VoiceEngineV2Error,
key: UnpublishableMediaKey,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'failUnpublish snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'failUnpublish operationId must be an integer');
assert.ok(error != null, 'failUnpublish error must not be null');
assert.equal(typeof key, 'string', 'failUnpublish key must be a string');
const state = snapshot[key];
if (state.operationId !== operationId) return {snapshot, commands: []};
return {
snapshot: {
...snapshot,
[key]: {
...state,
status: 'failed',
operationId: null,
failure: error,
},
lastFailure: error,
},
commands: [],
};
}
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Error, VoiceEngineV2OperationId} from '../../protocol/types';
import type {VoiceEngineV2LocalMediaState, VoiceEngineV2Snapshot} from '../state';
export function applyMediaSuccess<Options>(
state: VoiceEngineV2LocalMediaState<Options>,
operationId: VoiceEngineV2OperationId,
): VoiceEngineV2LocalMediaState<Options> {
assert.ok(state != null, 'applyMediaSuccess state must not be null');
assert.ok(Number.isInteger(operationId), 'applyMediaSuccess operationId must be an integer');
if (state.operationId !== operationId) return state;
return {
...state,
status: state.desired ? 'published' : 'idle',
published: state.desired,
operationId: null,
failure: null,
};
}
export function applyMediaFailure<Options>(
state: VoiceEngineV2LocalMediaState<Options>,
operationId: VoiceEngineV2OperationId,
error: VoiceEngineV2Error,
): VoiceEngineV2LocalMediaState<Options> {
assert.ok(state != null, 'applyMediaFailure state must not be null');
assert.ok(Number.isInteger(operationId), 'applyMediaFailure operationId must be an integer');
assert.ok(error != null, 'applyMediaFailure error must not be null');
if (state.operationId !== operationId) return state;
return {
...state,
status: 'failed',
published: null,
operationId: null,
failure: error,
};
}
export function completeUnpublish<Options>(
state: VoiceEngineV2LocalMediaState<Options>,
operationId: VoiceEngineV2OperationId,
): VoiceEngineV2LocalMediaState<Options> {
assert.ok(state != null, 'completeUnpublish state must not be null');
assert.ok(Number.isInteger(operationId), 'completeUnpublish operationId must be an integer');
if (state.operationId !== operationId) return state;
return {
...state,
status: 'idle',
published: null,
operationId: null,
failure: null,
};
}
export function resetPublishedMedia(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'resetPublishedMedia snapshot must not be null');
assert.ok(snapshot.microphone != null, 'resetPublishedMedia snapshot.microphone must not be null');
return {
...snapshot,
microphone: {
...snapshot.microphone,
status: 'idle',
published: null,
operationId: null,
failure: null,
setEnabledOperationId: null,
localSpeakingOverride: false,
},
camera: {
...snapshot.camera,
status: 'idle',
published: null,
operationId: null,
failure: null,
},
screen: {
...snapshot.screen,
status: 'idle',
published: null,
operationId: null,
failure: null,
},
screenAudio: {
...snapshot.screenAudio,
status: 'idle',
published: null,
operationId: null,
failure: null,
},
};
}
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Command} from '../../protocol/commands';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {allocateOperation, appendTransition} from './_helpers';
import {beginCameraPublish} from './camera';
import {beginMicrophonePublish} from './microphone';
import {beginScreenPublish} from './screen';
import {beginScreenAudioPublish} from './screenAudio';
function planDesiredLocalMedia(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'planDesiredLocalMedia snapshot must not be null');
assert.ok(snapshot.capabilities != null, 'planDesiredLocalMedia snapshot.capabilities must not be null');
let transition: VoiceEngineV2Transition = {snapshot, commands: []};
transition = appendTransition(transition, beginMicrophonePublish(transition.snapshot));
transition = appendTransition(transition, beginCameraPublish(transition.snapshot));
transition = appendTransition(transition, beginScreenPublish(transition.snapshot));
transition = appendTransition(transition, beginScreenAudioPublish(transition.snapshot));
return transition;
}
function planDesiredSessionConfiguration(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'planDesiredSessionConfiguration snapshot must not be null');
assert.ok(snapshot.capabilities != null, 'planDesiredSessionConfiguration snapshot.capabilities must not be null');
let next = snapshot;
const commands: Array<VoiceEngineV2Command> = [];
if (next.outputDevice.desiredDeviceId && next.capabilities.outputDevice) {
const desiredDeviceId = next.outputDevice.desiredDeviceId;
const allocated = allocateOperation(next);
next = {
...allocated.snapshot,
outputDevice: {...allocated.snapshot.outputDevice, operationId: allocated.operationId, failure: null},
};
commands.push({
type: 'outputDevice.set',
operationId: allocated.operationId,
options: {deviceId: desiredDeviceId},
});
}
if (next.capabilities.participantVolume) {
for (const participantIdentity of Object.keys(next.participantVolumes).sort()) {
const allocated = allocateOperation(next);
next = allocated.snapshot;
commands.push({
type: 'participantVolume.set',
operationId: allocated.operationId,
options: {
participantIdentity,
volume: next.participantVolumes[participantIdentity] ?? 1,
},
});
}
}
if (next.capabilities.remoteTrackSubscription) {
for (const key of Object.keys(next.remoteTrackSubscriptions).sort()) {
const options = next.remoteTrackSubscriptions[key];
if (!options) continue;
const allocated = allocateOperation(next);
next = allocated.snapshot;
commands.push({
type: 'remoteTrackSubscription.set',
operationId: allocated.operationId,
options,
});
}
}
return {snapshot: next, commands};
}
export function planDesiredState(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'planDesiredState snapshot must not be null');
assert.ok(snapshot.capabilities != null, 'planDesiredState snapshot.capabilities must not be null');
const localMedia = planDesiredLocalMedia(snapshot);
return appendTransition(localMedia, planDesiredSessionConfiguration(localMedia.snapshot));
}
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Error} from '../../protocol/types';
import {implementationError} from './_helpers';
export function nativeZeroCopyRequiredError(
resource: 'capture' | 'frame' | 'frameSink' | 'hardwareEncoder',
): VoiceEngineV2Error {
assert.equal(typeof resource, 'string', 'nativeZeroCopyRequiredError resource must be a string');
assert.ok(resource.length > 0, 'nativeZeroCopyRequiredError resource must not be empty');
const label =
resource === 'capture'
? 'Native Electron capture'
: resource === 'frame'
? 'Native Electron capture frames'
: resource === 'frameSink'
? 'Native Electron frame sinks'
: 'Native hardware encoder usage';
return implementationError(`${label} require zero-copy transport`, 'zeroCopyScreenTransport');
}
export function unavailableZeroCopyTransportError(
resource: 'capture' | 'frameSink' | 'hardwareEncoder',
): VoiceEngineV2Error {
assert.equal(typeof resource, 'string', 'unavailableZeroCopyTransportError resource must be a string');
assert.ok(resource.length > 0, 'unavailableZeroCopyTransportError resource must not be empty');
const label =
resource === 'capture'
? 'native Electron capture'
: resource === 'frameSink'
? 'native Electron frame sinks'
: 'native hardware encoders';
return {
code: 'unsupportedCapability',
capability: 'zeroCopyScreenTransport',
message: `Voice engine v2 implementation does not support zero-copy transport for ${label}`,
};
}
@@ -0,0 +1,216 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {planVoiceEngineV2CameraEncodingChange} from '../../policies/cameraShare';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {
VoiceEngineV2CameraEncodingOptions,
VoiceEngineV2CameraOptions,
VoiceEngineV2Error,
VoiceEngineV2OperationId,
} from '../../protocol/types';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {allocateOperation, failUnpublish, invalidArgument, isConnected, unsupportedCapability} from './_helpers';
import {applyMediaFailure, applyMediaSuccess, completeUnpublish} from './_media';
type VoiceEngineV2CameraEvent = Extract<VoiceEngineV2Event, {type: `camera.${string}`}>;
function sameCameraOptions(a: VoiceEngineV2CameraOptions | null, b: VoiceEngineV2CameraOptions | null): boolean {
assert.ok(a !== undefined, 'sameCameraOptions a must not be undefined');
assert.ok(b !== undefined, 'sameCameraOptions b must not be undefined');
return (
a?.deviceId === b?.deviceId && a?.width === b?.width && a?.height === b?.height && a?.frameRate === b?.frameRate
);
}
function sameCameraPublishCommandOptions(
a: VoiceEngineV2CameraOptions | null,
b: VoiceEngineV2CameraOptions | null,
): boolean {
assert.ok(a !== undefined, 'sameCameraPublishCommandOptions a must not be undefined');
assert.ok(b !== undefined, 'sameCameraPublishCommandOptions b must not be undefined');
if (!sameCameraOptions(a, b)) return false;
return (a?.sendUpdate !== false) === (b?.sendUpdate !== false);
}
function isCameraPublishInFlight(snapshot: VoiceEngineV2Snapshot): boolean {
assert.ok(snapshot != null, 'isCameraPublishInFlight snapshot must not be null');
assert.ok(snapshot.camera != null, 'isCameraPublishInFlight snapshot.camera must not be null');
if (snapshot.camera.status !== 'publishing') return false;
return snapshot.camera.operationId !== null;
}
function isDuplicateInFlightCameraPublish(
snapshot: VoiceEngineV2Snapshot,
options: VoiceEngineV2CameraOptions,
): boolean {
assert.ok(snapshot != null, 'isDuplicateInFlightCameraPublish snapshot must not be null');
assert.ok(options != null, 'isDuplicateInFlightCameraPublish options must not be null');
if (!isCameraPublishInFlight(snapshot)) return false;
assert.ok(snapshot.camera.desired !== null, 'in-flight camera publish must have desired options');
return sameCameraPublishCommandOptions(snapshot.camera.desired, options);
}
export function beginCameraPublish(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'beginCameraPublish snapshot must not be null');
assert.ok(snapshot.camera != null, 'beginCameraPublish snapshot.camera must not be null');
const desired = snapshot.camera.desired;
if (!desired) return {snapshot, commands: []};
if (!snapshot.capabilities.camera) {
const error = unsupportedCapability('camera');
return {
snapshot: {
...snapshot,
camera: {...snapshot.camera, status: 'failed', failure: error},
lastFailure: error,
},
commands: [],
};
}
if (!isConnected(snapshot)) return {snapshot, commands: []};
if (snapshot.camera.status === 'published' && sameCameraOptions(snapshot.camera.published, desired)) {
return {snapshot, commands: []};
}
const allocated = allocateOperation(snapshot);
return {
snapshot: {
...allocated.snapshot,
camera: {
...allocated.snapshot.camera,
status: 'publishing',
operationId: allocated.operationId,
failure: null,
},
},
commands: [{type: 'camera.publish', operationId: allocated.operationId, options: desired}],
};
}
function failCamera(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
error: VoiceEngineV2Error,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'failCamera snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'failCamera operationId must be an integer');
assert.ok(error != null, 'failCamera error must not be null');
const state = snapshot.camera;
return {
snapshot: {
...snapshot,
camera: applyMediaFailure(state, operationId, error),
lastFailure: state.operationId === operationId ? error : snapshot.lastFailure,
},
commands: [],
};
}
export function beginCameraEncodingUpdate(
snapshot: VoiceEngineV2Snapshot,
options: VoiceEngineV2CameraEncodingOptions,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onCameraUpdateEncodingRequested snapshot must not be null');
assert.ok(options != null, 'onCameraUpdateEncodingRequested options must not be null');
const plan = planVoiceEngineV2CameraEncodingChange({
published: snapshot.camera.published,
desired: snapshot.camera.desired,
update: options,
});
if (plan.action === 'reject' || !plan.desired) {
const error =
plan.error ?? invalidArgument('Cannot update camera encoding without a matching published camera', 'camera');
return {
snapshot: {...snapshot, camera: {...snapshot.camera, failure: error}, lastFailure: error},
commands: [],
};
}
if (plan.action === 'noop') {
return {snapshot: {...snapshot, camera: {...snapshot.camera, desired: plan.desired}}, commands: []};
}
if (!isConnected(snapshot)) {
return {snapshot: {...snapshot, camera: {...snapshot.camera, desired: plan.desired}}, commands: []};
}
const allocated = allocateOperation({...snapshot, camera: {...snapshot.camera, desired: plan.desired}});
const command =
plan.action === 'republish'
? ({type: 'camera.publish', operationId: allocated.operationId, options: plan.desired} as const)
: ({type: 'camera.updateEncoding', operationId: allocated.operationId, options} as const);
return {
snapshot: {
...allocated.snapshot,
camera: {...allocated.snapshot.camera, status: 'publishing', operationId: allocated.operationId, failure: null},
},
commands: [command],
};
}
function beginCameraUnpublish(
snapshot: VoiceEngineV2Snapshot,
options: VoiceEngineV2CameraOptions | undefined,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'beginCameraUnpublish snapshot must not be null');
assert.ok(options === undefined || typeof options === 'object', 'beginCameraUnpublish options must be an object');
const base = {
...snapshot,
camera: {
...snapshot.camera,
desired: null,
},
};
if (!isConnected(base) || (snapshot.camera.status === 'idle' && snapshot.camera.published == null)) {
return {snapshot: base, commands: []};
}
const allocated = allocateOperation(base);
return {
snapshot: {
...allocated.snapshot,
camera: {
...allocated.snapshot.camera,
status: 'unpublishing',
operationId: allocated.operationId,
failure: null,
},
},
commands: [{type: 'camera.unpublish', operationId: allocated.operationId, options}],
};
}
export function transitionCamera(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2CameraEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionCamera snapshot must not be null');
assert.ok(event != null, 'transitionCamera event must not be null');
assert.equal(typeof event.type, 'string', 'camera event type must be a string');
assert.ok(event.type.startsWith('camera.'), 'camera reducer received unrelated event');
switch (event.type) {
case 'camera.publishRequested':
if (isDuplicateInFlightCameraPublish(snapshot, event.options)) return {snapshot, commands: []};
return beginCameraPublish({...snapshot, camera: {...snapshot.camera, desired: event.options}});
case 'camera.publishSucceeded':
return {
snapshot: {...snapshot, camera: applyMediaSuccess(snapshot.camera, event.operationId)},
commands: [],
};
case 'camera.publishFailed':
return failCamera(snapshot, event.operationId, event.error);
case 'camera.updateEncodingRequested':
return beginCameraEncodingUpdate(snapshot, event.options);
case 'camera.updateEncodingSucceeded':
return {
snapshot: {...snapshot, camera: applyMediaSuccess(snapshot.camera, event.operationId)},
commands: [],
};
case 'camera.updateEncodingFailed':
return failCamera(snapshot, event.operationId, event.error);
case 'camera.unpublishRequested':
return beginCameraUnpublish(snapshot, event.options);
case 'camera.unpublishSucceeded':
return {
snapshot: {...snapshot, camera: completeUnpublish(snapshot.camera, event.operationId)},
commands: [],
};
case 'camera.unpublishFailed':
return failUnpublish(snapshot, event.operationId, event.error, 'camera');
}
}
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {
VoiceEngineV2Error,
VoiceEngineV2HardwareEncoderCapabilities,
VoiceEngineV2OperationId,
} from '../../protocol/types';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {allocateOperation, markOperation, queueCommand, unsupportedCapability} from './_helpers';
type VoiceEngineV2CapabilitiesEvent = Extract<VoiceEngineV2Event, {type: `capabilities.${string}`}>;
export function transitionCapabilities(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2CapabilitiesEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionCapabilities snapshot must not be null');
assert.ok(event != null, 'transitionCapabilities event must not be null');
assert.equal(typeof event.type, 'string', 'capabilities event type must be a string');
assert.ok(event.type.startsWith('capabilities.'), 'capabilities reducer received unrelated event');
switch (event.type) {
case 'capabilities.changed':
return {
snapshot: {...snapshot, capabilities: event.capabilities},
commands: [],
};
case 'capabilities.hardwareEncoderQueryRequested':
return onHardwareEncoderQueryRequested(snapshot);
case 'capabilities.hardwareEncoderChanged':
return onHardwareEncoderChanged(snapshot, event.operationId, event.capabilities);
case 'capabilities.hardwareEncoderQueryFailed':
return onHardwareEncoderQueryFailed(snapshot, event.operationId, event.error);
}
}
function onHardwareEncoderQueryRequested(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onHardwareEncoderQueryRequested snapshot must not be null');
assert.ok(snapshot.capabilities != null, 'onHardwareEncoderQueryRequested snapshot.capabilities must not be null');
if (!snapshot.capabilities.hardwareEncoding) {
const error = unsupportedCapability('hardwareEncoding');
return {
snapshot: {
...snapshot,
hardwareEncoder: {...snapshot.hardwareEncoder, failure: error},
lastFailure: error,
},
commands: [],
};
}
const allocated = allocateOperation(snapshot);
return queueCommand(
{
...allocated.snapshot,
hardwareEncoder: {
...allocated.snapshot.hardwareEncoder,
operationId: allocated.operationId,
failure: null,
},
},
{type: 'capabilities.queryHardwareEncoder', operationId: allocated.operationId},
);
}
function onHardwareEncoderChanged(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId | null,
capabilities: VoiceEngineV2HardwareEncoderCapabilities,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onHardwareEncoderChanged snapshot must not be null');
assert.ok(capabilities != null, 'onHardwareEncoderChanged capabilities must not be null');
if (operationId != null && snapshot.hardwareEncoder.operationId !== operationId) {
return {snapshot, commands: []};
}
return {
snapshot: {
...(operationId != null ? markOperation(snapshot, operationId, 'succeeded') : snapshot),
hardwareEncoder: {
capabilities,
operationId: operationId === snapshot.hardwareEncoder.operationId ? null : snapshot.hardwareEncoder.operationId,
failure: null,
},
},
commands: [],
};
}
function onHardwareEncoderQueryFailed(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
error: VoiceEngineV2Error,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onHardwareEncoderQueryFailed snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onHardwareEncoderQueryFailed operationId must be an integer');
assert.ok(error != null, 'onHardwareEncoderQueryFailed error must not be null');
if (snapshot.hardwareEncoder.operationId !== operationId) return {snapshot, commands: []};
return {
snapshot: {
...markOperation(snapshot, operationId, 'failed', error),
hardwareEncoder: {...snapshot.hardwareEncoder, operationId: null, failure: error},
lastFailure: error,
},
commands: [],
};
}
@@ -0,0 +1,166 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {describe, expect, it} from 'vitest';
import type {VoiceEngineV2Event} from '../../protocol/events';
import {transitionVoiceEngineV2} from '../reducer';
import {
availableVoiceEngineV2Capabilities,
createVoiceEngineV2InitialSnapshot,
type VoiceEngineV2Snapshot,
} from '../state';
function applyEvents(snapshot: VoiceEngineV2Snapshot, events: Array<VoiceEngineV2Event>): VoiceEngineV2Snapshot {
let next = snapshot;
for (const event of events) {
next = transitionVoiceEngineV2(next, event).snapshot;
}
return next;
}
function publishedCameraOn(codec: 'av1' | 'h264' | 'vp9'): VoiceEngineV2Snapshot {
return applyEvents(createVoiceEngineV2InitialSnapshot(availableVoiceEngineV2Capabilities()), [
{type: 'connection.connectRequested', options: {url: 'wss://voice', token: 'token'}},
{type: 'connection.connectSucceeded', operationId: 1},
{type: 'camera.publishRequested', options: {deviceId: 'cam-1', codec}},
{type: 'camera.publishSucceeded', operationId: 2},
{
type: 'codecNegotiation.streamRegistered',
source: 'camera',
streamIdentity: 'cam-stream-1',
preferredCodec: codec,
},
]);
}
describe('codec negotiation reducer', () => {
it('keeps the preferred codec while no one is watching', () => {
const snapshot = publishedCameraOn('av1');
expect(snapshot.codecNegotiation.streams.camera?.negotiatedCodec).toBe('av1');
expect(snapshot.camera.published?.codec).toBe('av1');
});
it('downgrades and republishes when a viewer who cannot decode the codec starts watching', () => {
const snapshot = publishedCameraOn('av1');
const transition = transitionVoiceEngineV2(snapshot, {
type: 'codecNegotiation.viewerChanged',
source: 'camera',
viewerIdentity: 'bob',
watching: true,
supportedVideoCodecs: ['h264', 'vp8'],
});
expect(transition.commands).toEqual([
{type: 'camera.publish', operationId: 3, options: {deviceId: 'cam-1', codec: 'h264'}},
]);
expect(transition.snapshot.codecNegotiation.streams.camera?.negotiatedCodec).toBe('h264');
expect(transition.snapshot.codecNegotiation.streams.camera?.constrainedBy).toBe('bob');
expect(transition.snapshot.camera.status).toBe('publishing');
});
it('preserves the stream identity across the codec republish', () => {
const snapshot = publishedCameraOn('av1');
const downgraded = transitionVoiceEngineV2(snapshot, {
type: 'codecNegotiation.viewerChanged',
source: 'camera',
viewerIdentity: 'bob',
watching: true,
supportedVideoCodecs: ['h264'],
});
expect(downgraded.snapshot.codecNegotiation.streams.camera?.streamIdentity).toBe('cam-stream-1');
});
it('upgrades back toward the preferred codec when the constraining viewer stops watching', () => {
const downgraded = applyEvents(publishedCameraOn('av1'), [
{
type: 'codecNegotiation.viewerChanged',
source: 'camera',
viewerIdentity: 'bob',
watching: true,
supportedVideoCodecs: ['h264'],
},
{type: 'camera.publishSucceeded', operationId: 3},
]);
expect(downgraded.camera.published?.codec).toBe('h264');
const upgraded = transitionVoiceEngineV2(downgraded, {
type: 'codecNegotiation.viewerChanged',
source: 'camera',
viewerIdentity: 'bob',
watching: false,
supportedVideoCodecs: [],
});
expect(upgraded.commands).toEqual([
{type: 'camera.publish', operationId: 4, options: {deviceId: 'cam-1', codec: 'av1'}},
]);
expect(upgraded.snapshot.codecNegotiation.streams.camera?.negotiatedCodec).toBe('av1');
expect(upgraded.snapshot.codecNegotiation.streams.camera?.constrainedBy).toBeNull();
});
it('does not republish when a newly watching viewer can already decode the codec', () => {
const snapshot = publishedCameraOn('h264');
const transition = transitionVoiceEngineV2(snapshot, {
type: 'codecNegotiation.viewerChanged',
source: 'camera',
viewerIdentity: 'bob',
watching: true,
supportedVideoCodecs: ['av1', 'vp9', 'h264', 'vp8'],
});
expect(transition.commands).toEqual([]);
expect(transition.snapshot.codecNegotiation.streams.camera?.negotiatedCodec).toBe('h264');
});
it('clamps the manual override down to a current viewer that cannot decode it', () => {
const watching = transitionVoiceEngineV2(publishedCameraOn('h264'), {
type: 'codecNegotiation.viewerChanged',
source: 'camera',
viewerIdentity: 'bob',
watching: true,
supportedVideoCodecs: ['vp8'],
}).snapshot;
const settled = applyEvents(watching, [{type: 'camera.publishSucceeded', operationId: 3}]);
expect(settled.camera.published?.codec).toBe('vp8');
const overridden = transitionVoiceEngineV2(settled, {
type: 'codecNegotiation.overrideSetRequested',
source: 'camera',
codec: 'av1',
});
expect(overridden.commands).toEqual([]);
expect(overridden.snapshot.codecNegotiation.overrides.camera).toBe('av1');
expect(overridden.snapshot.codecNegotiation.streams.camera?.negotiatedCodec).toBe('vp8');
expect(overridden.snapshot.codecNegotiation.streams.camera?.constrainedBy).toBe('bob');
});
it('applies the manual override as a republish when no viewer constrains it', () => {
const transition = transitionVoiceEngineV2(publishedCameraOn('h264'), {
type: 'codecNegotiation.overrideSetRequested',
source: 'camera',
codec: 'vp9',
});
expect(transition.commands).toEqual([
{type: 'camera.publish', operationId: 3, options: {deviceId: 'cam-1', codec: 'vp9'}},
]);
expect(transition.snapshot.codecNegotiation.streams.camera?.negotiatedCodec).toBe('vp9');
});
it('clears a per-source override back to the published preferred codec', () => {
const overridden = transitionVoiceEngineV2(publishedCameraOn('h264'), {
type: 'codecNegotiation.overrideSetRequested',
source: 'camera',
codec: 'vp9',
});
const settled = applyEvents(overridden.snapshot, [{type: 'camera.publishSucceeded', operationId: 3}]);
expect(settled.camera.published?.codec).toBe('vp9');
const cleared = transitionVoiceEngineV2(settled, {
type: 'codecNegotiation.overrideSetRequested',
source: 'camera',
codec: null,
});
expect(cleared.snapshot.codecNegotiation.overrides.camera).toBeUndefined();
expect(cleared.commands).toEqual([
{type: 'camera.publish', operationId: 4, options: {deviceId: 'cam-1', codec: 'h264'}},
]);
expect(cleared.snapshot.codecNegotiation.streams.camera?.negotiatedCodec).toBe('h264');
});
});
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {
maxDecodableVoiceEngineV2VideoCodec,
planVoiceEngineV2NegotiatedVideoCodec,
type VoiceEngineV2CodecViewer,
} from '../../policies/codecNegotiation';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {
VoiceEngineV2CodecStreamNegotiation,
VoiceEngineV2LocalStreamSource,
VoiceEngineV2VideoCodec,
} from '../../protocol/types';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {beginCameraEncodingUpdate} from './camera';
import {beginScreenEncodingUpdate} from './screen';
type VoiceEngineV2CodecNegotiationEvent = Extract<VoiceEngineV2Event, {type: `codecNegotiation.${string}`}>;
function publishedCodecForSource(
snapshot: VoiceEngineV2Snapshot,
source: VoiceEngineV2LocalStreamSource,
): VoiceEngineV2VideoCodec | null {
const media = source === 'camera' ? snapshot.camera : snapshot.screen;
if (media.status !== 'published') return null;
return media.published?.codec ?? null;
}
function viewersForPolicy(stream: VoiceEngineV2CodecStreamNegotiation): Array<VoiceEngineV2CodecViewer> {
const viewers: Array<VoiceEngineV2CodecViewer> = [];
for (const identity of Object.keys(stream.viewers).sort()) {
viewers.push({identity, maxVideoCodec: stream.viewers[identity] ?? null});
}
return viewers;
}
function setStream(
snapshot: VoiceEngineV2Snapshot,
source: VoiceEngineV2LocalStreamSource,
stream: VoiceEngineV2CodecStreamNegotiation,
): VoiceEngineV2Snapshot {
return {
...snapshot,
codecNegotiation: {
...snapshot.codecNegotiation,
streams: {...snapshot.codecNegotiation.streams, [source]: stream},
},
};
}
function recomputeStream(
snapshot: VoiceEngineV2Snapshot,
source: VoiceEngineV2LocalStreamSource,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'recomputeStream snapshot must not be null');
const existing = snapshot.codecNegotiation.streams[source];
if (!existing) return {snapshot, commands: []};
const preferred = snapshot.codecNegotiation.overrides[source] ?? existing.preferredCodec;
const plan = planVoiceEngineV2NegotiatedVideoCodec({preferred, viewers: viewersForPolicy(existing)});
const updatedStream: VoiceEngineV2CodecStreamNegotiation = {
...existing,
negotiatedCodec: plan.codec,
constrainedBy: plan.constrainedBy,
};
const withStream = setStream(snapshot, source, updatedStream);
const publishedCodec = publishedCodecForSource(withStream, source);
if (publishedCodec === null) return {snapshot: withStream, commands: []};
if (publishedCodec === plan.codec) return {snapshot: withStream, commands: []};
if (source === 'camera') {
return beginCameraEncodingUpdate(withStream, {codec: plan.codec});
}
const published = withStream.screen.published;
assert.ok(published != null, 'recomputeStream screen republish requires a published screen');
return beginScreenEncodingUpdate(withStream, {
captureId: published.captureId,
width: published.width,
height: published.height,
codec: plan.codec,
});
}
function applyViewerChanged(
snapshot: VoiceEngineV2Snapshot,
source: VoiceEngineV2LocalStreamSource,
viewerIdentity: string,
watching: boolean,
supportedVideoCodecs: Array<VoiceEngineV2VideoCodec>,
): VoiceEngineV2Transition {
assert.equal(typeof viewerIdentity, 'string', 'applyViewerChanged viewerIdentity must be a string');
assert.ok(viewerIdentity.length > 0, 'applyViewerChanged viewerIdentity must not be empty');
const existing = snapshot.codecNegotiation.streams[source];
if (!existing) return {snapshot, commands: []};
const viewers = {...existing.viewers};
if (watching) {
viewers[viewerIdentity] = maxDecodableVoiceEngineV2VideoCodec(supportedVideoCodecs);
} else {
delete viewers[viewerIdentity];
}
const withViewers = setStream(snapshot, source, {...existing, viewers});
return recomputeStream(withViewers, source);
}
function registerStream(
snapshot: VoiceEngineV2Snapshot,
source: VoiceEngineV2LocalStreamSource,
streamIdentity: string,
preferredCodec: VoiceEngineV2VideoCodec,
): VoiceEngineV2Transition {
assert.equal(typeof streamIdentity, 'string', 'registerStream streamIdentity must be a string');
assert.ok(streamIdentity.length > 0, 'registerStream streamIdentity must not be empty');
const existing = snapshot.codecNegotiation.streams[source];
const stream: VoiceEngineV2CodecStreamNegotiation = {
source,
streamIdentity,
preferredCodec,
negotiatedCodec: existing?.negotiatedCodec ?? preferredCodec,
constrainedBy: existing?.constrainedBy ?? null,
viewers: existing?.viewers ?? {},
};
return recomputeStream(setStream(snapshot, source, stream), source);
}
function unregisterStream(
snapshot: VoiceEngineV2Snapshot,
source: VoiceEngineV2LocalStreamSource,
): VoiceEngineV2Transition {
if (!snapshot.codecNegotiation.streams[source]) return {snapshot, commands: []};
const streams = {...snapshot.codecNegotiation.streams};
delete streams[source];
return {snapshot: {...snapshot, codecNegotiation: {...snapshot.codecNegotiation, streams}}, commands: []};
}
export function transitionCodecNegotiation(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2CodecNegotiationEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionCodecNegotiation snapshot must not be null');
assert.ok(event != null, 'transitionCodecNegotiation event must not be null');
assert.ok(event.type.startsWith('codecNegotiation.'), 'codecNegotiation reducer received unrelated event');
switch (event.type) {
case 'codecNegotiation.overrideSetRequested': {
const overrides = {...snapshot.codecNegotiation.overrides};
if (event.codec === null || event.codec === '') {
delete overrides[event.source];
} else {
overrides[event.source] = event.codec;
}
const withOverride = {...snapshot, codecNegotiation: {...snapshot.codecNegotiation, overrides}};
return recomputeStream(withOverride, event.source);
}
case 'codecNegotiation.localCapabilityChanged':
return {
snapshot: {
...snapshot,
codecNegotiation: {...snapshot.codecNegotiation, localSupportedVideoCodecs: [...event.supportedVideoCodecs]},
},
commands: [],
};
case 'codecNegotiation.remoteCapabilityChanged':
return {
snapshot: {
...snapshot,
codecNegotiation: {
...snapshot.codecNegotiation,
remoteSupportedVideoCodecs: {
...snapshot.codecNegotiation.remoteSupportedVideoCodecs,
[event.identity]: [...event.supportedVideoCodecs],
},
},
},
commands: [],
};
case 'codecNegotiation.streamRegistered':
return registerStream(snapshot, event.source, event.streamIdentity, event.preferredCodec);
case 'codecNegotiation.streamUnregistered':
return unregisterStream(snapshot, event.source);
case 'codecNegotiation.viewerChanged':
return applyViewerChanged(
snapshot,
event.source,
event.viewerIdentity,
event.watching,
event.supportedVideoCodecs,
);
}
}
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {getVoiceEngineV2CommandTypeResourceKey} from '../../protocol/commands';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {allocateOperation, clearOperationForResource, markOperation, queueCommand} from './_helpers';
type VoiceEngineV2CommandEvent = Extract<VoiceEngineV2Event, {type: `command.${string}` | `operation.${string}`}>;
export function transitionCommand(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2CommandEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionCommand snapshot must not be null');
assert.ok(event != null, 'transitionCommand event must not be null');
assert.equal(typeof event.type, 'string', 'command event type must be a string');
assert.ok(
event.type.startsWith('command.') || event.type.startsWith('operation.'),
'command reducer received unrelated event',
);
switch (event.type) {
case 'command.succeeded': {
const resourceKey = getVoiceEngineV2CommandTypeResourceKey(event.commandType);
const marked = markOperation(snapshot, event.operationId, 'succeeded');
return {
snapshot: resourceKey ? clearOperationForResource(marked, resourceKey, event.operationId) : marked,
commands: [],
};
}
case 'command.failed': {
const resourceKey = getVoiceEngineV2CommandTypeResourceKey(event.commandType);
const marked = markOperation(snapshot, event.operationId, 'failed', event.error);
return {
snapshot: {
...(resourceKey ? clearOperationForResource(marked, resourceKey, event.operationId) : marked),
lastFailure: event.error,
},
commands: [],
};
}
case 'command.staleCompletionRejected':
return {snapshot: markOperation(snapshot, event.operationId, 'stale'), commands: []};
case 'operation.cancelRequested': {
const allocated = allocateOperation(snapshot);
return queueCommand(allocated.snapshot, {
type: 'operation.cancel',
operationId: allocated.operationId,
targetOperationId: event.operationId,
resourceKey: event.resourceKey,
reason: event.reason,
});
}
case 'operation.cancelled':
return {
snapshot: clearOperationForResource(
markOperation(snapshot, event.targetOperationId, 'cancelled'),
event.resourceKey,
event.targetOperationId,
),
commands: [],
};
}
}
@@ -0,0 +1,520 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Command} from '../../protocol/commands';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {
VoiceEngineV2ConnectOptions,
VoiceEngineV2DisconnectReason,
VoiceEngineV2Error,
VoiceEngineV2LifecycleReason,
VoiceEngineV2OperationId,
} from '../../protocol/types';
import {createVoiceEngineV2InitialSnapshot, type VoiceEngineV2Snapshot, type VoiceEngineV2Transition} from '../state';
import {allocateOperation, markOperation, queueCommand, recordQueuedCommand, unsupportedCapability} from './_helpers';
import {resetPublishedMedia} from './_media';
type VoiceEngineV2ConnectionEvent = Extract<VoiceEngineV2Event, {type: `connection.${string}`}>;
function sameConnectOptions(a: VoiceEngineV2ConnectOptions | null, b: VoiceEngineV2ConnectOptions | null): boolean {
assert.ok(a !== undefined, 'sameConnectOptions a must not be undefined');
assert.ok(b !== undefined, 'sameConnectOptions b must not be undefined');
return a?.url === b?.url && a?.token === b?.token && a?.e2eeKey === b?.e2eeKey;
}
export function resetAfterDisconnect(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'resetAfterDisconnect snapshot must not be null');
assert.ok(snapshot.capabilities != null, 'resetAfterDisconnect snapshot.capabilities must not be null');
const initial = createVoiceEngineV2InitialSnapshot(snapshot.capabilities);
return {
...initial,
nextOperationId: snapshot.nextOperationId,
connection: {
...initial.connection,
desired: null,
},
};
}
function staleCompletion(snapshot: VoiceEngineV2Snapshot, operationId: VoiceEngineV2OperationId | null): boolean {
assert.ok(snapshot != null, 'staleCompletion snapshot must not be null');
assert.ok(snapshot.connection != null, 'staleCompletion snapshot.connection must not be null');
return operationId == null || operationId !== snapshot.connection.operationId;
}
function hasGatewayVoiceStateToClear(snapshot: VoiceEngineV2Snapshot): boolean {
assert.ok(snapshot != null, 'hasGatewayVoiceStateToClear snapshot must not be null');
assert.ok(snapshot.gateway != null, 'hasGatewayVoiceStateToClear snapshot.gateway must not be null');
return (
snapshot.gateway.selfVoiceState?.channelId != null || snapshot.gateway.desiredVoiceStateWrite?.channelId != null
);
}
function gatewayVoiceStateGuildId(snapshot: VoiceEngineV2Snapshot): string | null {
assert.ok(snapshot != null, 'gatewayVoiceStateGuildId snapshot must not be null');
assert.ok(snapshot.gateway != null, 'gatewayVoiceStateGuildId snapshot.gateway must not be null');
return snapshot.gateway.selfVoiceState?.guildId ?? snapshot.gateway.desiredVoiceStateWrite?.guildId ?? null;
}
function beginConnectionConnect(
snapshot: VoiceEngineV2Snapshot,
options: VoiceEngineV2ConnectOptions,
status: 'connecting' | 'reconnecting' = 'connecting',
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'beginConnectionConnect snapshot must not be null');
assert.ok(options != null, 'beginConnectionConnect options must not be null');
assert.equal(typeof options.url, 'string', 'beginConnectionConnect options.url must be a string');
if (!snapshot.capabilities.connect) {
const error = unsupportedCapability('connect');
return {
snapshot: {
...snapshot,
connection: {...snapshot.connection, status: 'failed', failure: error},
lastFailure: error,
},
commands: [],
};
}
const allocated = allocateOperation(resetPublishedMedia(snapshot));
const command: VoiceEngineV2Command = {type: 'connection.connect', operationId: allocated.operationId, options};
return {
snapshot: recordQueuedCommand(
{
...allocated.snapshot,
connection: {
...allocated.snapshot.connection,
status,
desired: options,
operationId: allocated.operationId,
disconnectReason: null,
failure: null,
},
},
command,
),
commands: [command],
};
}
function beginConnectionDisconnect(
snapshot: VoiceEngineV2Snapshot,
reason: VoiceEngineV2DisconnectReason,
desiredAfterDisconnect: VoiceEngineV2ConnectOptions | null,
status: 'disconnecting' | 'reconnecting' = desiredAfterDisconnect ? 'reconnecting' : 'disconnecting',
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'beginConnectionDisconnect snapshot must not be null');
assert.equal(typeof reason, 'string', 'beginConnectionDisconnect reason must be a string');
const allocated = allocateOperation(resetPublishedMedia(snapshot));
const command: VoiceEngineV2Command = {
type: 'connection.disconnect',
operationId: allocated.operationId,
reason,
};
return {
snapshot: recordQueuedCommand(
{
...allocated.snapshot,
connection: {
...allocated.snapshot.connection,
status,
desired: desiredAfterDisconnect,
operationId: allocated.operationId,
disconnectReason: reason,
failure: null,
},
},
command,
),
commands: [command],
};
}
export function beginGatewayVoiceStateClear(
snapshot: VoiceEngineV2Snapshot,
guildId: string | null,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'beginGatewayVoiceStateClear snapshot must not be null');
assert.ok(snapshot.gateway != null, 'beginGatewayVoiceStateClear snapshot.gateway must not be null');
if (snapshot.gateway.operationId != null) return {snapshot, commands: []};
const allocated = allocateOperation(snapshot);
return queueCommand(
{
...allocated.snapshot,
gateway: {
...allocated.snapshot.gateway,
desiredVoiceStateWrite: null,
operationId: allocated.operationId,
failure: null,
},
},
{type: 'gateway.voiceState.clear', operationId: allocated.operationId, guildId},
);
}
function beginLifecycleTeardownCommand(
snapshot: VoiceEngineV2Snapshot,
reason: VoiceEngineV2LifecycleReason,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'beginLifecycleTeardownCommand snapshot must not be null');
assert.equal(typeof reason, 'string', 'beginLifecycleTeardownCommand reason must be a string');
const allocated = allocateOperation(snapshot);
return queueCommand(
{
...allocated.snapshot,
lifecycle: {
tearingDown: true,
reason,
operationId: allocated.operationId,
failure: null,
},
},
{type: 'lifecycle.teardown', operationId: allocated.operationId, reason},
);
}
function clearConnectionAfterDisconnect(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'clearConnectionAfterDisconnect snapshot must not be null');
assert.ok(snapshot.connection != null, 'clearConnectionAfterDisconnect snapshot.connection must not be null');
return {
...resetPublishedMedia(snapshot),
connection: {
...snapshot.connection,
status: 'idle',
active: null,
desired: null,
operationId: null,
disconnectReason: null,
failure: null,
},
liveKit: {
...snapshot.liveKit,
connectionState: 'disconnected',
roomSid: null,
roomName: null,
serverRegion: null,
},
};
}
export function planPendingConnectionTeardown(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'planPendingConnectionTeardown snapshot must not be null');
assert.ok(snapshot.lifecycle != null, 'planPendingConnectionTeardown snapshot.lifecycle must not be null');
if (snapshot.lifecycle.tearingDown) return planLifecycleTeardown(snapshot);
if (
(snapshot.connection.status === 'disconnecting' || snapshot.connection.status === 'reconnecting') &&
snapshot.connection.operationId == null
) {
return planPendingDisconnect(snapshot);
}
return {snapshot, commands: []};
}
function planLifecycleTeardown(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'planLifecycleTeardown snapshot must not be null');
assert.ok(snapshot.lifecycle != null, 'planLifecycleTeardown snapshot.lifecycle must not be null');
if (
snapshot.lifecycle.operationId != null ||
snapshot.gateway.operationId != null ||
snapshot.connection.operationId != null
) {
return {snapshot, commands: []};
}
if (hasGatewayVoiceStateToClear(snapshot)) {
return beginGatewayVoiceStateClear(snapshot, gatewayVoiceStateGuildId(snapshot));
}
if (
snapshot.connection.status !== 'idle' ||
snapshot.connection.active != null ||
snapshot.connection.desired != null
) {
return beginConnectionDisconnect(
{
...snapshot,
connection: {
...snapshot.connection,
desired: null,
disconnectReason: 'shutdown',
},
},
'shutdown',
null,
'disconnecting',
);
}
return beginLifecycleTeardownCommand(snapshot, snapshot.lifecycle.reason ?? 'appShutdown');
}
function planPendingDisconnect(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'planPendingDisconnect snapshot must not be null');
assert.ok(snapshot.connection != null, 'planPendingDisconnect snapshot.connection must not be null');
if (snapshot.connection.active == null && snapshot.connection.desired == null) {
return {snapshot: resetAfterDisconnect(snapshot), commands: []};
}
return beginConnectionDisconnect(
snapshot,
snapshot.connection.disconnectReason ?? (snapshot.connection.desired ? 'replaced' : 'user'),
snapshot.connection.desired,
snapshot.connection.desired ? 'reconnecting' : 'disconnecting',
);
}
export function transitionConnection(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2ConnectionEvent,
planAfterConnect: (snapshot: VoiceEngineV2Snapshot) => VoiceEngineV2Transition,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionConnection snapshot must not be null');
assert.ok(event != null, 'transitionConnection event must not be null');
assert.equal(typeof event.type, 'string', 'connection event type must be a string');
assert.ok(event.type.startsWith('connection.'), 'connection reducer received unrelated event');
assert.equal(typeof planAfterConnect, 'function', 'transitionConnection planAfterConnect must be a function');
switch (event.type) {
case 'connection.connectRequested':
return onConnectRequested(snapshot, event.options);
case 'connection.connectSucceeded':
return onConnectSucceeded(snapshot, event.operationId, planAfterConnect);
case 'connection.connectFailed':
return onConnectFailed(snapshot, event.operationId, event.error);
case 'connection.disconnectRequested':
return onDisconnectRequested(snapshot, event.reason);
case 'connection.disconnectSucceeded':
return onDisconnectSucceeded(snapshot, event.operationId);
case 'connection.disconnectFailed':
return onDisconnectFailed(snapshot, event.operationId, event.error);
case 'connection.remoteDisconnected':
return onRemoteDisconnected(snapshot, event.reason, event.error);
case 'connection.reconnectRequested':
return onReconnectRequested(snapshot);
case 'connection.externallyEstablished':
return onExternallyEstablished(snapshot, event.options, planAfterConnect);
}
}
function onExternallyEstablished(
snapshot: VoiceEngineV2Snapshot,
options: VoiceEngineV2ConnectOptions,
planAfterConnect: (snapshot: VoiceEngineV2Snapshot) => VoiceEngineV2Transition,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onExternallyEstablished snapshot must not be null');
assert.ok(options != null, 'onExternallyEstablished options must not be null');
assert.equal(typeof options.url, 'string', 'onExternallyEstablished options.url must be a string');
assert.equal(typeof options.token, 'string', 'onExternallyEstablished options.token must be a string');
if (snapshot.connection.status === 'connected' && sameConnectOptions(snapshot.connection.active, options)) {
return {snapshot, commands: []};
}
const connected: VoiceEngineV2Snapshot = {
...snapshot,
connection: {
...snapshot.connection,
status: 'connected',
active: options,
operationId: null,
disconnectReason: null,
failure: null,
},
};
assert.equal(connected.connection.status, 'connected', 'onExternallyEstablished must mark snapshot connected');
if (connected.lifecycle.tearingDown) return planPendingConnectionTeardown(connected);
return planAfterConnect(connected);
}
function onConnectRequested(
snapshot: VoiceEngineV2Snapshot,
options: VoiceEngineV2ConnectOptions,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onConnectRequested snapshot must not be null');
assert.ok(options != null, 'onConnectRequested options must not be null');
if (
(snapshot.connection.status === 'connected' ||
snapshot.connection.status === 'connecting' ||
snapshot.connection.status === 'reconnecting') &&
sameConnectOptions(snapshot.connection.desired ?? snapshot.connection.active, options)
) {
return {snapshot, commands: []};
}
if (snapshot.connection.status === 'connected' || snapshot.connection.active != null) {
return beginConnectionDisconnect(snapshot, 'replaced', options, 'reconnecting');
}
if (snapshot.connection.status === 'disconnecting') {
return {
snapshot: {
...snapshot,
connection: {
...snapshot.connection,
status: 'reconnecting',
desired: options,
disconnectReason: 'replaced',
failure: null,
},
},
commands: [],
};
}
return beginConnectionConnect(snapshot, options);
}
function onConnectSucceeded(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
planAfterConnect: (snapshot: VoiceEngineV2Snapshot) => VoiceEngineV2Transition,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onConnectSucceeded snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onConnectSucceeded operationId must be an integer');
if (staleCompletion(snapshot, operationId)) return {snapshot, commands: []};
const connected: VoiceEngineV2Snapshot = {
...markOperation(snapshot, operationId, 'succeeded'),
connection: {
...snapshot.connection,
status: 'connected',
active: snapshot.connection.desired,
operationId: null,
disconnectReason: null,
failure: null,
},
};
if (connected.lifecycle.tearingDown) return planPendingConnectionTeardown(connected);
return planAfterConnect(connected);
}
function onConnectFailed(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
error: VoiceEngineV2Error,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onConnectFailed snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onConnectFailed operationId must be an integer');
assert.ok(error != null, 'onConnectFailed error must not be null');
if (staleCompletion(snapshot, operationId)) return {snapshot, commands: []};
return {
snapshot: {
...markOperation(snapshot, operationId, 'failed', error),
connection: {
...snapshot.connection,
status: 'failed',
operationId: null,
failure: error,
},
lastFailure: error,
},
commands: [],
};
}
function onDisconnectRequested(
snapshot: VoiceEngineV2Snapshot,
reason: VoiceEngineV2DisconnectReason,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onDisconnectRequested snapshot must not be null');
assert.equal(typeof reason, 'string', 'onDisconnectRequested reason must be a string');
const base = {
...resetPublishedMedia(snapshot),
connection: {
...snapshot.connection,
status: 'disconnecting' as const,
desired: null,
operationId: null,
disconnectReason: reason,
failure: null,
},
};
if (hasGatewayVoiceStateToClear(base)) {
return beginGatewayVoiceStateClear(base, gatewayVoiceStateGuildId(base));
}
if (
snapshot.connection.status === 'idle' &&
snapshot.connection.active == null &&
snapshot.connection.operationId == null
) {
return {snapshot: resetAfterDisconnect(base), commands: []};
}
return beginConnectionDisconnect(base, reason, null, 'disconnecting');
}
function onDisconnectSucceeded(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onDisconnectSucceeded snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onDisconnectSucceeded operationId must be an integer');
if (staleCompletion(snapshot, operationId)) return {snapshot, commands: []};
const completed = markOperation(snapshot, operationId, 'succeeded');
if (completed.lifecycle.tearingDown) {
return planPendingConnectionTeardown(clearConnectionAfterDisconnect(completed));
}
if (completed.connection.desired) {
return beginConnectionConnect(
{
...completed,
connection: {
...completed.connection,
active: null,
operationId: null,
disconnectReason: null,
},
},
completed.connection.desired,
'reconnecting',
);
}
return {snapshot: resetAfterDisconnect(completed), commands: []};
}
function onDisconnectFailed(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
error: VoiceEngineV2Error,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onDisconnectFailed snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onDisconnectFailed operationId must be an integer');
assert.ok(error != null, 'onDisconnectFailed error must not be null');
if (staleCompletion(snapshot, operationId)) return {snapshot, commands: []};
return {
snapshot: {
...markOperation(snapshot, operationId, 'failed', error),
connection: {
...snapshot.connection,
status: 'failed',
operationId: null,
failure: error,
},
lastFailure: error,
},
commands: [],
};
}
function onRemoteDisconnected(
snapshot: VoiceEngineV2Snapshot,
reason: VoiceEngineV2DisconnectReason,
error: VoiceEngineV2Error | undefined,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onRemoteDisconnected snapshot must not be null');
assert.equal(typeof reason, 'string', 'onRemoteDisconnected reason must be a string');
const disconnected = resetAfterDisconnect(snapshot);
return {
snapshot: {
...disconnected,
connection: {
...disconnected.connection,
disconnectReason: reason,
failure: error ?? null,
},
lastFailure: error ?? snapshot.lastFailure,
},
commands: [],
};
}
function onReconnectRequested(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onReconnectRequested snapshot must not be null');
assert.ok(snapshot.connection != null, 'onReconnectRequested snapshot.connection must not be null');
const options = snapshot.connection.desired ?? snapshot.connection.active;
if (!options) return {snapshot, commands: []};
if (snapshot.connection.status === 'connecting' || snapshot.connection.status === 'reconnecting') {
return {snapshot, commands: []};
}
if (snapshot.connection.status === 'connected' || snapshot.connection.active != null) {
return beginConnectionDisconnect(snapshot, 'network', options, 'reconnecting');
}
return beginConnectionConnect(snapshot, options, 'reconnecting');
}
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {commandIfConnected} from './_helpers';
type VoiceEngineV2DataEvent = Extract<VoiceEngineV2Event, {type: `data.${string}`}>;
export function transitionData(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2DataEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionData snapshot must not be null');
assert.ok(event != null, 'transitionData event must not be null');
assert.equal(typeof event.type, 'string', 'data event type must be a string');
assert.ok(event.type.startsWith('data.'), 'data reducer received unrelated event');
switch (event.type) {
case 'data.publishRequested':
return commandIfConnected(snapshot, 'dataChannel', {type: 'data.publish', options: event.options});
case 'data.publishSucceeded':
return {snapshot, commands: []};
case 'data.publishFailed':
return {snapshot: {...snapshot, lastFailure: event.error}, commands: []};
}
}
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Command} from '../../protocol/commands';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2DeviceInventory, VoiceEngineV2Error, VoiceEngineV2OperationId} from '../../protocol/types';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {allocateOperation, appendTransition, markOperation, queueCommand} from './_helpers';
import {planMicrophoneDeviceChange} from './microphone';
type VoiceEngineV2DevicesEvent = Extract<VoiceEngineV2Event, {type: `devices.${string}`}>;
function beginDeviceSelection(
snapshot: VoiceEngineV2Snapshot,
commandType: 'devices.selectAudioInput' | 'devices.selectAudioOutput' | 'devices.selectCamera',
deviceId: string | null,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'beginDeviceSelection snapshot must not be null');
assert.equal(typeof commandType, 'string', 'beginDeviceSelection commandType must be a string');
const allocated = allocateOperation(snapshot);
const inventory = {
...allocated.snapshot.devices.inventory,
selectedAudioInputId:
commandType === 'devices.selectAudioInput' ? deviceId : allocated.snapshot.devices.inventory.selectedAudioInputId,
selectedAudioOutputId:
commandType === 'devices.selectAudioOutput'
? deviceId
: allocated.snapshot.devices.inventory.selectedAudioOutputId,
selectedCameraId:
commandType === 'devices.selectCamera' ? deviceId : allocated.snapshot.devices.inventory.selectedCameraId,
};
return queueCommand(
{
...allocated.snapshot,
devices: {...allocated.snapshot.devices, inventory, operationId: allocated.operationId, failure: null},
},
{type: commandType, operationId: allocated.operationId, deviceId} as VoiceEngineV2Command,
);
}
export function transitionDevices(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2DevicesEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionDevices snapshot must not be null');
assert.ok(event != null, 'transitionDevices event must not be null');
assert.equal(typeof event.type, 'string', 'devices event type must be a string');
assert.ok(event.type.startsWith('devices.'), 'devices reducer received unrelated event');
switch (event.type) {
case 'devices.enumerateRequested':
return onEnumerateRequested(snapshot);
case 'devices.changed':
return onChanged(snapshot, event.operationId, event.devices);
case 'devices.enumerateFailed':
return onEnumerateFailed(snapshot, event.operationId, event.error);
case 'devices.selectAudioInputRequested': {
const selection = beginDeviceSelection(snapshot, 'devices.selectAudioInput', event.deviceId);
return appendTransition(selection, planMicrophoneDeviceChange(selection.snapshot, event.deviceId));
}
case 'devices.selectAudioOutputRequested':
return beginDeviceSelection(snapshot, 'devices.selectAudioOutput', event.deviceId);
case 'devices.selectCameraRequested':
return beginDeviceSelection(snapshot, 'devices.selectCamera', event.deviceId);
}
}
function onEnumerateRequested(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onEnumerateRequested snapshot must not be null');
assert.ok(snapshot.devices != null, 'onEnumerateRequested snapshot.devices must not be null');
const allocated = allocateOperation(snapshot);
return queueCommand(
{
...allocated.snapshot,
devices: {...allocated.snapshot.devices, operationId: allocated.operationId, failure: null},
},
{type: 'devices.enumerate', operationId: allocated.operationId},
);
}
function onChanged(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId | null,
devices: VoiceEngineV2DeviceInventory,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onChanged snapshot must not be null');
assert.ok(devices != null, 'onChanged devices must not be null');
if (operationId != null && snapshot.devices.operationId !== operationId) return {snapshot, commands: []};
const previousAudioInputId = snapshot.devices.inventory.selectedAudioInputId;
const base = {
...(operationId != null ? markOperation(snapshot, operationId, 'succeeded') : snapshot),
devices: {
inventory: devices,
operationId: operationId === snapshot.devices.operationId ? null : snapshot.devices.operationId,
failure: null,
},
};
if (previousAudioInputId === devices.selectedAudioInputId) return {snapshot: base, commands: []};
return planMicrophoneDeviceChange(base, devices.selectedAudioInputId);
}
function onEnumerateFailed(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
error: VoiceEngineV2Error,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onEnumerateFailed snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onEnumerateFailed operationId must be an integer');
assert.ok(error != null, 'onEnumerateFailed error must not be null');
if (snapshot.devices.operationId !== operationId) return {snapshot, commands: []};
return {
snapshot: {
...markOperation(snapshot, operationId, 'failed', error),
devices: {...snapshot.devices, operationId: null, failure: error},
lastFailure: error,
},
commands: [],
};
}
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {allocateOperation, markOperation, queueCommand} from './_helpers';
type VoiceEngineV2E2eeEvent = Extract<VoiceEngineV2Event, {type: `e2ee.${string}`}>;
export function transitionE2ee(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2E2eeEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionE2ee snapshot must not be null');
assert.ok(event != null, 'transitionE2ee event must not be null');
assert.equal(typeof event.type, 'string', 'e2ee event type must be a string');
assert.ok(event.type.startsWith('e2ee.'), 'e2ee reducer received unrelated event');
switch (event.type) {
case 'e2ee.setEnabledRequested': {
const allocated = allocateOperation(snapshot);
return queueCommand(
{
...allocated.snapshot,
e2ee: {
...allocated.snapshot.e2ee,
status: event.enabled ? 'pendingKey' : 'disabled',
keyId: event.keyId ?? allocated.snapshot.e2ee.keyId,
failure: null,
operationId: allocated.operationId,
},
},
{type: 'e2ee.setEnabled', operationId: allocated.operationId, enabled: event.enabled, keyId: event.keyId},
);
}
case 'e2ee.enabled':
if (event.operationId != null && snapshot.e2ee.operationId !== event.operationId) {
return {snapshot, commands: []};
}
return {
snapshot: {
...(event.operationId != null ? markOperation(snapshot, event.operationId, 'succeeded') : snapshot),
e2ee: {status: 'enabled', keyId: event.keyId, failure: null, operationId: null},
},
commands: [],
};
case 'e2ee.disabled':
if (event.operationId != null && snapshot.e2ee.operationId !== event.operationId) {
return {snapshot, commands: []};
}
return {
snapshot: {
...(event.operationId != null ? markOperation(snapshot, event.operationId, 'succeeded') : snapshot),
e2ee: {status: 'disabled', keyId: null, failure: null, operationId: null},
},
commands: [],
};
case 'e2ee.failed':
if (event.operationId != null && snapshot.e2ee.operationId !== event.operationId) {
return {snapshot, commands: []};
}
return {
snapshot: {
...(event.operationId != null ? markOperation(snapshot, event.operationId, 'failed', event.error) : snapshot),
e2ee: {...snapshot.e2ee, status: 'failed', failure: event.error, operationId: null},
lastFailure: event.error,
},
commands: [],
};
}
}
@@ -0,0 +1,287 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {describe, expect, it} from 'vitest';
import type {VoiceEngineV2GatewayDesiredVoiceState, VoiceEngineV2GatewayVoiceState} from '../../protocol/types';
import {transitionVoiceEngineV2} from '../reducer';
import type {VoiceEngineV2GatewayState, VoiceEngineV2Snapshot} from '../state';
import {availableVoiceEngineV2Capabilities, createVoiceEngineV2InitialSnapshot} from '../state';
import {
deriveVoiceEngineV2DesiredGatewayVoiceState,
gatewayVoiceStateWriteMatchesReported,
reconcileGatewayVoiceState,
shouldApplyGatewayVoiceStateEcho,
} from './gateway';
function reported(overrides: Partial<VoiceEngineV2GatewayVoiceState> = {}): VoiceEngineV2GatewayVoiceState {
return {
guildId: 'guild-1',
channelId: 'channel-1',
userId: 'user-1',
sessionId: 'session-1',
selfMute: true,
selfDeaf: false,
selfVideo: false,
selfStream: false,
suppress: false,
requestToSpeakTimestamp: null,
...overrides,
};
}
function desired(
overrides: Partial<VoiceEngineV2GatewayDesiredVoiceState> = {},
): VoiceEngineV2GatewayDesiredVoiceState {
return {
guildId: 'guild-1',
channelId: 'channel-1',
selfMute: false,
selfDeaf: false,
selfVideo: false,
selfStream: false,
...overrides,
};
}
function connectedSnapshot(gatewayOverrides: Partial<VoiceEngineV2GatewayState> = {}): VoiceEngineV2Snapshot {
const base = createVoiceEngineV2InitialSnapshot(availableVoiceEngineV2Capabilities());
return {
...base,
connection: {...base.connection, status: 'connected'},
gateway: {...base.gateway, selfVoiceState: reported(), ...gatewayOverrides},
};
}
describe('reconcileGatewayVoiceState', () => {
it('emits a write when desired self-mute differs from reported (unmute drives convergence)', () => {
const snapshot = connectedSnapshot({
selfVoiceState: reported({selfMute: true}),
desiredVoiceState: desired({selfMute: false}),
});
const result = reconcileGatewayVoiceState(snapshot);
expect(result.commands).toHaveLength(1);
expect(result.commands[0]).toMatchObject({type: 'gateway.voiceState.write', options: {selfMute: false}});
expect(result.snapshot.gateway.desiredVoiceStateWrite).toMatchObject({selfMute: false});
expect(result.snapshot.gateway.operationId).not.toBeNull();
});
it('emits no write when there is no desired intent yet', () => {
const snapshot = connectedSnapshot({selfVoiceState: reported({selfMute: true}), desiredVoiceState: null});
expect(reconcileGatewayVoiceState(snapshot).commands).toEqual([]);
});
it('emits no write when desired already equals reported (converged)', () => {
const snapshot = connectedSnapshot({
selfVoiceState: reported({selfMute: false}),
desiredVoiceState: desired({selfMute: false}),
});
expect(reconcileGatewayVoiceState(snapshot).commands).toEqual([]);
});
it('emits no write while a write is already in flight (operationId set)', () => {
const snapshot = connectedSnapshot({
selfVoiceState: reported({selfMute: true}),
desiredVoiceState: desired({selfMute: false}),
operationId: 7,
});
expect(reconcileGatewayVoiceState(snapshot).commands).toEqual([]);
});
it('emits no write when the same value is already pending an echo (no optimistic re-send, S2)', () => {
const snapshot = connectedSnapshot({
selfVoiceState: reported({selfMute: true}),
desiredVoiceState: desired({selfMute: false}),
desiredVoiceStateWrite: {
guildId: 'guild-1',
channelId: 'channel-1',
selfMute: false,
selfDeaf: false,
selfVideo: false,
selfStream: false,
},
operationId: null,
});
expect(reconcileGatewayVoiceState(snapshot).commands).toEqual([]);
});
it('re-emits when desired changed while a different value was pending (no lost update)', () => {
const snapshot = connectedSnapshot({
selfVoiceState: reported({selfMute: false}),
desiredVoiceState: desired({selfMute: true}),
desiredVoiceStateWrite: {
guildId: 'guild-1',
channelId: 'channel-1',
selfMute: false,
selfDeaf: false,
selfVideo: false,
selfStream: false,
},
operationId: null,
});
const result = reconcileGatewayVoiceState(snapshot);
expect(result.commands).toHaveLength(1);
expect(result.commands[0]).toMatchObject({type: 'gateway.voiceState.write', options: {selfMute: true}});
});
it('emits no write when disconnected, but the recorded intent re-asserts on connect', () => {
const base = createVoiceEngineV2InitialSnapshot(availableVoiceEngineV2Capabilities());
const disconnected: VoiceEngineV2Snapshot = {
...base,
connection: {...base.connection, status: 'idle'},
gateway: {
...base.gateway,
selfVoiceState: reported({selfMute: true}),
desiredVoiceState: desired({selfMute: false}),
},
};
expect(reconcileGatewayVoiceState(disconnected).commands).toEqual([]);
});
it('emits a write to establish sync when the server state is not yet known', () => {
const snapshot = connectedSnapshot({selfVoiceState: null, desiredVoiceState: desired({selfMute: false})});
expect(deriveVoiceEngineV2DesiredGatewayVoiceState(snapshot)).toMatchObject({
channelId: 'channel-1',
selfMute: false,
});
const result = reconcileGatewayVoiceState(snapshot);
expect(result.commands).toHaveLength(1);
expect(result.commands[0]).toMatchObject({type: 'gateway.voiceState.write', options: {selfMute: false}});
});
it('emits no write when the desired channel is null (not in a channel)', () => {
const snapshot = connectedSnapshot({selfVoiceState: null, desiredVoiceState: desired({channelId: null})});
expect(deriveVoiceEngineV2DesiredGatewayVoiceState(snapshot)).toBeNull();
expect(reconcileGatewayVoiceState(snapshot).commands).toEqual([]);
});
it('clears the pending write when the server echoes the desired value (convergence)', () => {
const snapshot = connectedSnapshot({
selfVoiceState: reported({selfMute: true}),
desiredVoiceState: desired({selfMute: false}),
desiredVoiceStateWrite: {
guildId: 'guild-1',
channelId: 'channel-1',
selfMute: false,
selfDeaf: false,
selfVideo: false,
selfStream: false,
},
});
const echoed = transitionVoiceEngineV2(snapshot, {
type: 'gateway.voiceStateUpdated',
voiceState: reported({selfMute: false}),
});
expect(echoed.snapshot.gateway.selfVoiceState).toMatchObject({selfMute: false});
expect(echoed.snapshot.gateway.desiredVoiceStateWrite).toBeNull();
});
it('keeps the pending write when the echo does not yet match the desired value', () => {
const snapshot = connectedSnapshot({
selfVoiceState: reported({selfMute: true}),
desiredVoiceState: desired({selfMute: false}),
desiredVoiceStateWrite: {
guildId: 'guild-1',
channelId: 'channel-1',
selfMute: false,
selfDeaf: false,
selfVideo: false,
selfStream: false,
},
});
const echoed = transitionVoiceEngineV2(snapshot, {
type: 'gateway.voiceStateUpdated',
voiceState: reported({selfMute: true}),
});
expect(echoed.snapshot.gateway.desiredVoiceStateWrite).toMatchObject({selfMute: false});
});
it('converges across the full unmute lifecycle: desired change, write, success, echo (L1)', () => {
let snapshot = connectedSnapshot({selfVoiceState: reported({selfMute: true})});
const first = transitionVoiceEngineV2(snapshot, {
type: 'gateway.desiredVoiceStateChanged',
desired: desired({selfMute: false}),
});
expect(first.commands).toHaveLength(1);
expect(first.commands[0]).toMatchObject({type: 'gateway.voiceState.write', options: {selfMute: false}});
snapshot = first.snapshot;
const operationId = snapshot.gateway.operationId;
expect(operationId).not.toBeNull();
snapshot = transitionVoiceEngineV2(snapshot, {type: 'gateway.voiceStateReconcileRequested'}).snapshot;
expect(snapshot.gateway.operationId).toBe(operationId);
snapshot = transitionVoiceEngineV2(snapshot, {
type: 'gateway.voiceStateWriteSucceeded',
operationId: operationId as number,
}).snapshot;
expect(snapshot.gateway.operationId).toBeNull();
snapshot = transitionVoiceEngineV2(snapshot, {
type: 'gateway.voiceStateUpdated',
voiceState: reported({selfMute: false}),
}).snapshot;
expect(
gatewayVoiceStateWriteMatchesReported(
deriveVoiceEngineV2DesiredGatewayVoiceState(snapshot)!,
snapshot.gateway.selfVoiceState!,
),
).toBe(true);
expect(transitionVoiceEngineV2(snapshot, {type: 'gateway.voiceStateReconcileRequested'}).commands).toEqual([]);
});
it('retries after a failed write (clears pending so the next reconcile re-emits, L2)', () => {
let snapshot = connectedSnapshot({
selfVoiceState: reported({selfMute: true}),
desiredVoiceState: desired({selfMute: false}),
});
const first = transitionVoiceEngineV2(snapshot, {type: 'gateway.voiceStateReconcileRequested'});
snapshot = first.snapshot;
const operationId = snapshot.gateway.operationId as number;
snapshot = transitionVoiceEngineV2(snapshot, {
type: 'gateway.voiceStateWriteFailed',
operationId,
error: {code: 'implementationError', message: 'boom'},
}).snapshot;
expect(snapshot.gateway.desiredVoiceStateWrite).toBeNull();
expect(snapshot.gateway.operationId).toBeNull();
const retry = transitionVoiceEngineV2(snapshot, {type: 'gateway.voiceStateReconcileRequested'});
expect(retry.commands).toHaveLength(1);
expect(retry.commands[0]).toMatchObject({type: 'gateway.voiceState.write', options: {selfMute: false}});
});
});
describe('shouldApplyGatewayVoiceStateEcho', () => {
it('applies the echo when there is no pending local write', () => {
const snapshot = connectedSnapshot({desiredVoiceStateWrite: null});
expect(shouldApplyGatewayVoiceStateEcho(snapshot, reported({selfMute: true}))).toBe(true);
});
it('applies the echo when it matches the pending local write', () => {
const snapshot = connectedSnapshot({
desiredVoiceStateWrite: {
guildId: 'guild-1',
channelId: 'channel-1',
selfMute: false,
selfDeaf: false,
selfVideo: false,
selfStream: false,
},
});
expect(shouldApplyGatewayVoiceStateEcho(snapshot, reported({selfMute: false}))).toBe(true);
});
it('rejects a stale echo that differs from the pending local write', () => {
const snapshot = connectedSnapshot({
desiredVoiceStateWrite: {
guildId: 'guild-1',
channelId: 'channel-1',
selfMute: false,
selfDeaf: false,
selfVideo: false,
selfStream: false,
},
});
expect(shouldApplyGatewayVoiceStateEcho(snapshot, reported({selfMute: true}))).toBe(false);
});
});
@@ -0,0 +1,266 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {
VoiceEngineV2Error,
VoiceEngineV2GatewayDesiredVoiceState,
VoiceEngineV2GatewayVoiceState,
VoiceEngineV2GatewayVoiceStateWrite,
VoiceEngineV2OperationId,
} from '../../protocol/types';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {allocateOperation, isConnected, markOperation, queueCommand} from './_helpers';
import {beginGatewayVoiceStateClear, planPendingConnectionTeardown} from './connection';
type VoiceEngineV2GatewayEvent = Extract<VoiceEngineV2Event, {type: `gateway.${string}` | 'livekit.roomStateChanged'}>;
export function transitionGateway(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2GatewayEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionGateway snapshot must not be null');
assert.ok(event != null, 'transitionGateway event must not be null');
assert.equal(typeof event.type, 'string', 'gateway event type must be a string');
assert.ok(
event.type.startsWith('gateway.') || event.type === 'livekit.roomStateChanged',
'gateway reducer received unrelated event',
);
switch (event.type) {
case 'gateway.desiredVoiceStateChanged':
return onDesiredVoiceStateChanged(snapshot, event.desired);
case 'gateway.voiceStateReconcileRequested':
return reconcileGatewayVoiceState(snapshot);
case 'gateway.voiceStateWriteRequested':
return onWriteRequested(snapshot, event.options);
case 'gateway.voiceStateWriteSucceeded':
return onWriteSucceeded(snapshot, event.operationId);
case 'gateway.voiceStateWriteFailed':
return onWriteFailed(snapshot, event.operationId, event.error);
case 'gateway.voiceStateClearRequested':
return beginGatewayVoiceStateClear(snapshot, event.guildId);
case 'gateway.voiceStateClearSucceeded':
return onClearSucceeded(snapshot, event.operationId);
case 'gateway.voiceStateClearFailed':
return onClearFailed(snapshot, event.operationId, event.error);
case 'gateway.voiceStateUpdated':
return onVoiceStateUpdated(snapshot, event.voiceState);
case 'gateway.voiceServerUpdated':
return {
snapshot: {...snapshot, gateway: {...snapshot.gateway, voiceServer: event.voiceServer}},
commands: [],
};
case 'livekit.roomStateChanged':
return {
snapshot: {
...snapshot,
liveKit: {...snapshot.liveKit, ...event.room, failure: null},
},
commands: [],
};
}
}
function onWriteRequested(
snapshot: VoiceEngineV2Snapshot,
options: VoiceEngineV2GatewayVoiceStateWrite,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onWriteRequested snapshot must not be null');
assert.ok(options != null, 'onWriteRequested options must not be null');
const allocated = allocateOperation(snapshot);
return queueCommand(
{
...allocated.snapshot,
gateway: {
...allocated.snapshot.gateway,
desiredVoiceStateWrite: options,
operationId: allocated.operationId,
failure: null,
},
},
{
type: 'gateway.voiceState.write',
operationId: allocated.operationId,
options,
},
);
}
function onWriteSucceeded(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onWriteSucceeded snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onWriteSucceeded operationId must be an integer');
if (snapshot.gateway.operationId !== operationId) return {snapshot, commands: []};
return planPendingConnectionTeardown({
...markOperation(snapshot, operationId, 'succeeded'),
gateway: {...snapshot.gateway, operationId: null, failure: null},
});
}
function onWriteFailed(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
error: VoiceEngineV2Error,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onWriteFailed snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onWriteFailed operationId must be an integer');
assert.ok(error != null, 'onWriteFailed error must not be null');
if (snapshot.gateway.operationId !== operationId) return {snapshot, commands: []};
return {
snapshot: {
...markOperation(snapshot, operationId, 'failed', error),
gateway: {...snapshot.gateway, desiredVoiceStateWrite: null, operationId: null, failure: error},
lastFailure: error,
},
commands: [],
};
}
function onClearSucceeded(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onClearSucceeded snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onClearSucceeded operationId must be an integer');
if (snapshot.gateway.operationId !== operationId) return {snapshot, commands: []};
const cleared: VoiceEngineV2Snapshot = {
...markOperation(snapshot, operationId, 'succeeded'),
gateway: {
...snapshot.gateway,
desiredVoiceStateWrite: null,
selfVoiceState: null,
operationId: null,
failure: null,
},
};
return planPendingConnectionTeardown(cleared);
}
function onClearFailed(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
error: VoiceEngineV2Error,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onClearFailed snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onClearFailed operationId must be an integer');
assert.ok(error != null, 'onClearFailed error must not be null');
if (snapshot.gateway.operationId !== operationId) return {snapshot, commands: []};
return {
snapshot: {
...markOperation(snapshot, operationId, 'failed', error),
gateway: {...snapshot.gateway, operationId: null, failure: error},
lastFailure: error,
},
commands: [],
};
}
function onVoiceStateUpdated(
snapshot: VoiceEngineV2Snapshot,
voiceState: VoiceEngineV2GatewayVoiceState | null,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onVoiceStateUpdated snapshot must not be null');
assert.ok(snapshot.gateway != null, 'onVoiceStateUpdated snapshot.gateway must not be null');
const pending = snapshot.gateway.desiredVoiceStateWrite;
const converged = voiceState != null && pending != null && gatewayVoiceStateWriteMatchesReported(pending, voiceState);
if (converged) {
assert.ok(pending != null, 'onVoiceStateUpdated converged requires a pending write');
}
return {
snapshot: {
...snapshot,
gateway: {
...snapshot.gateway,
selfVoiceState: voiceState,
desiredVoiceStateWrite: converged ? null : pending,
},
},
commands: [],
};
}
function onDesiredVoiceStateChanged(
snapshot: VoiceEngineV2Snapshot,
desired: VoiceEngineV2GatewayDesiredVoiceState,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onDesiredVoiceStateChanged snapshot must not be null');
assert.ok(desired != null, 'onDesiredVoiceStateChanged desired must not be null');
assert.equal(typeof desired.selfMute, 'boolean', 'onDesiredVoiceStateChanged selfMute must be a boolean');
return reconcileGatewayVoiceState({
...snapshot,
gateway: {...snapshot.gateway, desiredVoiceState: desired},
});
}
export function deriveVoiceEngineV2DesiredGatewayVoiceState(
snapshot: VoiceEngineV2Snapshot,
): VoiceEngineV2GatewayVoiceStateWrite | null {
assert.ok(snapshot != null, 'deriveVoiceEngineV2DesiredGatewayVoiceState snapshot must not be null');
assert.ok(snapshot.gateway != null, 'deriveVoiceEngineV2DesiredGatewayVoiceState snapshot.gateway must not be null');
const desired = snapshot.gateway.desiredVoiceState;
if (desired == null) return null;
if (desired.channelId == null) return null;
return {
guildId: desired.guildId,
channelId: desired.channelId,
selfMute: desired.selfMute,
selfDeaf: desired.selfDeaf,
selfVideo: desired.selfVideo,
selfStream: desired.selfStream,
};
}
export function shouldApplyGatewayVoiceStateEcho(
snapshot: VoiceEngineV2Snapshot,
echo: VoiceEngineV2GatewayVoiceState,
): boolean {
assert.ok(snapshot != null, 'shouldApplyGatewayVoiceStateEcho snapshot must not be null');
assert.ok(echo != null, 'shouldApplyGatewayVoiceStateEcho echo must not be null');
const pending = snapshot.gateway.desiredVoiceStateWrite;
if (pending == null) return true;
return gatewayVoiceStateWriteMatchesReported(pending, echo);
}
export function gatewayVoiceStateWriteMatchesReported(
write: VoiceEngineV2GatewayVoiceStateWrite,
reported: VoiceEngineV2GatewayVoiceState,
): boolean {
assert.ok(write != null, 'gatewayVoiceStateWriteMatchesReported write must not be null');
assert.ok(reported != null, 'gatewayVoiceStateWriteMatchesReported reported must not be null');
if (write.selfMute !== reported.selfMute) return false;
if (write.selfDeaf !== reported.selfDeaf) return false;
if ((write.selfVideo ?? false) !== reported.selfVideo) return false;
if ((write.selfStream ?? false) !== reported.selfStream) return false;
return true;
}
function gatewayVoiceStateWriteValuesEqual(
a: VoiceEngineV2GatewayVoiceStateWrite,
b: VoiceEngineV2GatewayVoiceStateWrite,
): boolean {
assert.ok(a != null, 'gatewayVoiceStateWriteValuesEqual a must not be null');
assert.ok(b != null, 'gatewayVoiceStateWriteValuesEqual b must not be null');
if (a.guildId !== b.guildId) return false;
if (a.channelId !== b.channelId) return false;
if (a.selfMute !== b.selfMute) return false;
if (a.selfDeaf !== b.selfDeaf) return false;
if ((a.selfVideo ?? false) !== (b.selfVideo ?? false)) return false;
if ((a.selfStream ?? false) !== (b.selfStream ?? false)) return false;
return true;
}
export function reconcileGatewayVoiceState(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'reconcileGatewayVoiceState snapshot must not be null');
assert.ok(snapshot.gateway != null, 'reconcileGatewayVoiceState snapshot.gateway must not be null');
if (!isConnected(snapshot)) return {snapshot, commands: []};
const desired = deriveVoiceEngineV2DesiredGatewayVoiceState(snapshot);
if (desired == null) return {snapshot, commands: []};
const reported = snapshot.gateway.selfVoiceState;
if (reported != null && gatewayVoiceStateWriteMatchesReported(desired, reported)) return {snapshot, commands: []};
if (snapshot.gateway.operationId != null) return {snapshot, commands: []};
const pending = snapshot.gateway.desiredVoiceStateWrite;
if (pending != null && gatewayVoiceStateWriteValuesEqual(pending, desired)) return {snapshot, commands: []};
return onWriteRequested(snapshot, desired);
}
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {allocateOperation, recordFailure} from './_helpers';
type VoiceEngineV2ImplementationEvent = Extract<VoiceEngineV2Event, {type: `implementation.${string}`}>;
export function transitionImplementation(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2ImplementationEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionImplementation snapshot must not be null');
assert.ok(event != null, 'transitionImplementation event must not be null');
assert.equal(typeof event.type, 'string', 'implementation event type must be a string');
assert.ok(event.type.startsWith('implementation.'), 'implementation reducer received unrelated event');
switch (event.type) {
case 'implementation.prewarmRequested': {
const allocated = allocateOperation(snapshot);
return {
snapshot: allocated.snapshot,
commands: [{type: 'implementation.prewarm', operationId: allocated.operationId}],
};
}
case 'implementation.prewarmSucceeded':
return {snapshot, commands: []};
case 'implementation.prewarmFailed':
return recordFailure(snapshot, event.error);
}
}
@@ -0,0 +1,155 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {
VoiceEngineV2InboundVideoFrame,
VoiceEngineV2InboundVideoFrameStats,
VoiceEngineV2InboundVideoTrackSubscription,
} from '../../protocol/types';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
type VoiceEngineV2InboundVideoEvent = Extract<VoiceEngineV2Event, {type: `inboundVideo.${string}`}>;
export function removeInboundVideoTrack(snapshot: VoiceEngineV2Snapshot, trackSid: string): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'removeInboundVideoTrack snapshot must not be null');
assert.equal(typeof trackSid, 'string', 'removeInboundVideoTrack trackSid must be a string');
if (!snapshot.inboundVideo.tracks[trackSid]) return snapshot;
const tracks = {...snapshot.inboundVideo.tracks};
delete tracks[trackSid];
return {
...snapshot,
inboundVideo: {
...snapshot.inboundVideo,
tracks,
},
};
}
function upsertInboundVideoTrack(
snapshot: VoiceEngineV2Snapshot,
track: VoiceEngineV2InboundVideoTrackSubscription,
): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'upsertInboundVideoTrack snapshot must not be null');
assert.ok(track != null, 'upsertInboundVideoTrack track must not be null');
assert.equal(typeof track.trackSid, 'string', 'track.trackSid must be a string');
const existing = snapshot.inboundVideo.tracks[track.trackSid];
return {
...snapshot,
inboundVideo: {
...snapshot.inboundVideo,
tracks: {
...snapshot.inboundVideo.tracks,
[track.trackSid]: {
participantSid: track.participantSid,
...(track.participantIdentity ? {participantIdentity: track.participantIdentity} : {}),
trackSid: track.trackSid,
source: track.source,
width: track.width ?? existing?.width ?? 0,
height: track.height ?? existing?.height ?? 0,
frameCount: existing?.frameCount ?? 0,
lastFrameTimestampUs: existing?.lastFrameTimestampUs ?? null,
lastFrameByteLength: existing?.lastFrameByteLength ?? null,
},
},
},
};
}
function applyInboundVideoFrame(
snapshot: VoiceEngineV2Snapshot,
frame: VoiceEngineV2InboundVideoFrame,
): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'applyInboundVideoFrame snapshot must not be null');
assert.ok(frame != null, 'applyInboundVideoFrame frame must not be null');
assert.equal(typeof frame.trackSid, 'string', 'frame.trackSid must be a string');
const track = snapshot.inboundVideo.tracks[frame.trackSid];
if (!track) {
return {
...snapshot,
inboundVideo: {
...snapshot.inboundVideo,
droppedFrameCount: snapshot.inboundVideo.droppedFrameCount + 1,
},
};
}
return {
...snapshot,
inboundVideo: {
...snapshot.inboundVideo,
tracks: {
...snapshot.inboundVideo.tracks,
[frame.trackSid]: {
...track,
participantSid: frame.participantSid,
...(frame.participantIdentity ? {participantIdentity: frame.participantIdentity} : {}),
width: frame.width,
height: frame.height,
frameCount: track.frameCount + 1,
lastFrameTimestampUs: frame.timestampUs,
lastFrameByteLength: frame.byteLength ?? null,
},
},
},
};
}
function applyInboundVideoFrameStats(
snapshot: VoiceEngineV2Snapshot,
stats: VoiceEngineV2InboundVideoFrameStats,
): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'applyInboundVideoFrameStats snapshot must not be null');
assert.ok(stats != null, 'applyInboundVideoFrameStats stats must not be null');
assert.equal(typeof stats.trackSid, 'string', 'stats.trackSid must be a string');
assert.ok(Number.isInteger(stats.frameCount), 'stats.frameCount must be an integer');
assert.ok(stats.frameCount >= 0, 'stats.frameCount must be non-negative');
const track = snapshot.inboundVideo.tracks[stats.trackSid];
if (!track) {
return {
...snapshot,
inboundVideo: {
...snapshot.inboundVideo,
droppedFrameCount: snapshot.inboundVideo.droppedFrameCount + 1,
},
};
}
return {
...snapshot,
inboundVideo: {
...snapshot.inboundVideo,
tracks: {
...snapshot.inboundVideo.tracks,
[stats.trackSid]: {
...track,
participantSid: stats.participantSid,
...(stats.participantIdentity ? {participantIdentity: stats.participantIdentity} : {}),
width: stats.width,
height: stats.height,
frameCount: stats.frameCount,
lastFrameTimestampUs: stats.lastFrameTimestampUs,
lastFrameByteLength: stats.lastFrameByteLength,
},
},
},
};
}
export function transitionInboundVideo(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2InboundVideoEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionInboundVideo snapshot must not be null');
assert.ok(event != null, 'transitionInboundVideo event must not be null');
assert.equal(typeof event.type, 'string', 'inboundVideo event type must be a string');
assert.ok(event.type.startsWith('inboundVideo.'), 'inboundVideo reducer received unrelated event');
switch (event.type) {
case 'inboundVideo.trackSubscribed':
return {snapshot: upsertInboundVideoTrack(snapshot, event.track), commands: []};
case 'inboundVideo.trackUnsubscribed':
return {snapshot: removeInboundVideoTrack(snapshot, event.trackSid), commands: []};
case 'inboundVideo.frameReceived':
return {snapshot: applyInboundVideoFrame(snapshot, event.frame), commands: []};
case 'inboundVideo.frameStats':
return {snapshot: applyInboundVideoFrameStats(snapshot, event.stats), commands: []};
}
}
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {markOperation} from './_helpers';
import {resetPublishedMedia} from './_media';
import {planPendingConnectionTeardown, resetAfterDisconnect} from './connection';
type VoiceEngineV2LifecycleEvent = Extract<VoiceEngineV2Event, {type: `lifecycle.${string}`}>;
export function transitionLifecycle(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2LifecycleEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionLifecycle snapshot must not be null');
assert.ok(event != null, 'transitionLifecycle event must not be null');
assert.equal(typeof event.type, 'string', 'lifecycle event type must be a string');
assert.ok(event.type.startsWith('lifecycle.'), 'lifecycle reducer received unrelated event');
switch (event.type) {
case 'lifecycle.teardownRequested':
return planPendingConnectionTeardown({
...resetPublishedMedia(snapshot),
connection: {
...snapshot.connection,
desired: null,
disconnectReason: 'shutdown',
failure: null,
},
lifecycle: {
tearingDown: true,
reason: event.reason,
operationId: null,
failure: null,
},
});
case 'lifecycle.teardownSucceeded':
if (snapshot.lifecycle.operationId !== event.operationId) return {snapshot, commands: []};
return {
snapshot: {
...markOperation(resetAfterDisconnect(snapshot), event.operationId, 'succeeded'),
lifecycle: {tearingDown: false, reason: null, operationId: null, failure: null},
},
commands: [],
};
case 'lifecycle.teardownFailed':
if (snapshot.lifecycle.operationId !== event.operationId) return {snapshot, commands: []};
return {
snapshot: {
...markOperation(snapshot, event.operationId, 'failed', event.error),
lifecycle: {...snapshot.lifecycle, tearingDown: true, operationId: null, failure: event.error},
lastFailure: event.error,
},
commands: [],
};
}
}
@@ -0,0 +1,528 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {
getVoiceEngineV2MicrophoneOperationFailureAction,
type VoiceEngineV2MicrophoneOperationFailureAction,
} from '../../policies/microphoneFailureAction';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {
VoiceEngineV2AudioControls,
VoiceEngineV2Error,
VoiceEngineV2MicrophoneOptions,
VoiceEngineV2OperationId,
VoiceEngineV2PermissionResult,
} from '../../protocol/types';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {
allocateOperation,
beginUnpublish,
failUnpublish,
isConnected,
markOperation,
unsupportedCapability,
} from './_helpers';
import {applyMediaFailure, completeUnpublish} from './_media';
type VoiceEngineV2MicrophoneEvent = Extract<
VoiceEngineV2Event,
{
type: `microphone.${string}` | 'localAudio.muteRequested' | 'localAudio.deafenRequested' | 'audioControls.changed';
}
>;
function sameMicrophoneOptions(
a: VoiceEngineV2MicrophoneOptions | null,
b: VoiceEngineV2MicrophoneOptions | null,
): boolean {
assert.ok(a !== undefined, 'sameMicrophoneOptions a must not be undefined');
assert.ok(b !== undefined, 'sameMicrophoneOptions b must not be undefined');
return (
a?.deviceId === b?.deviceId &&
a?.echoCancellation === b?.echoCancellation &&
a?.noiseSuppression === b?.noiseSuppression &&
a?.autoGainControl === b?.autoGainControl &&
a?.deepFilter === b?.deepFilter &&
a?.deepFilterNoiseReductionLevel === b?.deepFilterNoiseReductionLevel &&
a?.maxBitrateBps === b?.maxBitrateBps
);
}
function isMicrophonePublished(snapshot: VoiceEngineV2Snapshot): boolean {
assert.ok(snapshot != null, 'isMicrophonePublished snapshot must not be null');
assert.ok(snapshot.microphone != null, 'isMicrophonePublished snapshot.microphone must not be null');
return snapshot.microphone.status === 'published' && snapshot.microphone.published != null;
}
function computeEffectiveMicrophoneEnabled(audioControls: VoiceEngineV2AudioControls): boolean {
assert.ok(audioControls != null, 'computeEffectiveMicrophoneEnabled audioControls must not be null');
assert.equal(typeof audioControls.mode, 'string', 'audioControls.mode must be a string');
if (audioControls.locallyMuted || audioControls.locallyDeafened || audioControls.mutedByPermission) return false;
if (audioControls.mode === 'pushToTalk') return audioControls.pushToTalkActive;
if (audioControls.mode === 'pushToMute') return !audioControls.pushToMuteActive;
return true;
}
function computeLocalSpeakingOverride(
snapshot: VoiceEngineV2Snapshot,
effectiveMicrophoneEnabled: boolean,
): boolean | null {
assert.ok(snapshot != null, 'computeLocalSpeakingOverride snapshot must not be null');
assert.equal(typeof effectiveMicrophoneEnabled, 'boolean', 'effectiveMicrophoneEnabled must be a boolean');
const hasPublication = isMicrophonePublished(snapshot);
const effectivelyMuted =
!hasPublication ||
!effectiveMicrophoneEnabled ||
snapshot.audioControls.locallyMuted ||
snapshot.audioControls.locallyDeafened ||
snapshot.audioControls.mutedByPermission;
if (snapshot.audioControls.mode === 'pushToTalk') return !effectivelyMuted;
if (snapshot.audioControls.mode === 'pushToMute' && snapshot.audioControls.pushToMuteActive) return false;
if (effectivelyMuted) return false;
return null;
}
function refreshMicrophoneDerivedState(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'refreshMicrophoneDerivedState snapshot must not be null');
assert.ok(snapshot.audioControls != null, 'refreshMicrophoneDerivedState snapshot.audioControls must not be null');
const enabled = computeEffectiveMicrophoneEnabled(snapshot.audioControls);
return {
...snapshot,
microphone: {
...snapshot.microphone,
enabled,
localSpeakingOverride: computeLocalSpeakingOverride(snapshot, enabled),
},
};
}
function planMicrophoneEnabled(snapshot: VoiceEngineV2Snapshot, forceCommand = false): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'planMicrophoneEnabled snapshot must not be null');
assert.equal(typeof forceCommand, 'boolean', 'planMicrophoneEnabled forceCommand must be a boolean');
const enabled = computeEffectiveMicrophoneEnabled(snapshot.audioControls);
const previousEnabled = snapshot.microphone.enabled;
const refreshed = refreshMicrophoneDerivedState(snapshot);
if (!isConnected(refreshed) || !isMicrophonePublished(refreshed) || !refreshed.capabilities.microphone) {
return {snapshot: refreshed, commands: []};
}
if (!forceCommand && previousEnabled === enabled) return {snapshot: refreshed, commands: []};
if (forceCommand && enabled) return {snapshot: refreshed, commands: []};
const allocated = allocateOperation(refreshed);
return {
snapshot: {
...allocated.snapshot,
microphone: {
...allocated.snapshot.microphone,
setEnabledOperationId: allocated.operationId,
failure: null,
},
},
commands: [{type: 'microphone.setEnabled', operationId: allocated.operationId, enabled}],
};
}
function setLocalAudioMute(snapshot: VoiceEngineV2Snapshot, muted: boolean): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'setLocalAudioMute snapshot must not be null');
assert.equal(typeof muted, 'boolean', 'setLocalAudioMute muted must be a boolean');
const current = snapshot.audioControls;
const controls: VoiceEngineV2AudioControls = {
...current,
preferredLocallyMuted: muted,
hasUserSetMute: true,
locallyMuted: current.mutedByPermission ? true : muted,
locallyDeafened: current.locallyDeafened && muted ? current.locallyDeafened : false,
hasUserSetDeaf: current.locallyDeafened && !muted ? true : current.hasUserSetDeaf,
shouldUnmuteOnUndeafen: current.locallyDeafened && !muted ? false : current.shouldUnmuteOnUndeafen,
};
return planMicrophoneEnabled({...snapshot, audioControls: controls});
}
function setLocalAudioDeafen(snapshot: VoiceEngineV2Snapshot, deafened: boolean): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'setLocalAudioDeafen snapshot must not be null');
assert.equal(typeof deafened, 'boolean', 'setLocalAudioDeafen deafened must be a boolean');
const current = snapshot.audioControls;
const controls: VoiceEngineV2AudioControls = deafened
? {
...current,
locallyMuted: true,
locallyDeafened: true,
hasUserSetDeaf: true,
shouldUnmuteOnUndeafen: !current.locallyMuted && !current.mutedByPermission,
}
: {
...current,
locallyMuted: current.shouldUnmuteOnUndeafen && !current.mutedByPermission ? false : current.locallyMuted,
preferredLocallyMuted:
current.shouldUnmuteOnUndeafen && !current.mutedByPermission ? false : current.preferredLocallyMuted,
locallyDeafened: false,
hasUserSetDeaf: true,
shouldUnmuteOnUndeafen: false,
};
return planMicrophoneEnabled({...snapshot, audioControls: controls});
}
export function applyMicrophonePermissionResult(
snapshot: VoiceEngineV2Snapshot,
result: VoiceEngineV2PermissionResult,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'applyMicrophonePermissionResult snapshot must not be null');
assert.ok(result != null, 'applyMicrophonePermissionResult result must not be null');
if (result.name !== 'microphone') return {snapshot, commands: []};
if (result.status === 'denied' || result.status === 'restricted') {
return planMicrophoneEnabled({
...snapshot,
audioControls: {
...snapshot.audioControls,
locallyMuted: true,
mutedByPermission: true,
},
});
}
if (result.status !== 'granted') return {snapshot: refreshMicrophoneDerivedState(snapshot), commands: []};
const locallyMuted = snapshot.audioControls.hasUserSetMute ? snapshot.audioControls.preferredLocallyMuted : false;
return planMicrophoneEnabled({
...snapshot,
audioControls: {
...snapshot.audioControls,
locallyMuted,
mutedByPermission: false,
},
});
}
function microphoneOptionsWithAudioInputDevice(
options: VoiceEngineV2MicrophoneOptions,
deviceId: string | null,
): VoiceEngineV2MicrophoneOptions {
assert.ok(options != null, 'microphoneOptionsWithAudioInputDevice options must not be null');
assert.ok(deviceId === null || typeof deviceId === 'string', 'deviceId must be string or null');
if (deviceId == null) {
const {deviceId: _deviceId, ...rest} = options;
return rest;
}
return {...options, deviceId};
}
function nativeAudioDeviceModuleNotReadyError(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Error {
assert.ok(snapshot != null, 'nativeAudioDeviceModuleNotReadyError snapshot must not be null');
assert.ok(
snapshot.nativeAudioDeviceModule.status === 'failed',
'nativeAudioDeviceModuleNotReadyError requires failed ADM status',
);
return {
code: 'deviceUnavailable',
capability: 'microphone',
message: snapshot.nativeAudioDeviceModule.detail ?? 'Native audio device module failed to become ready',
};
}
function planNativeAudioDeviceModuleGatedMicrophone(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition | null {
assert.ok(snapshot != null, 'planNativeAudioDeviceModuleGatedMicrophone snapshot must not be null');
assert.ok(snapshot.nativeAudioDeviceModule != null, 'snapshot.nativeAudioDeviceModule must not be null');
switch (snapshot.nativeAudioDeviceModule.status) {
case 'unsupported':
case 'ready':
return null;
case 'unknown':
case 'warming':
return {
snapshot: {...snapshot, microphone: {...snapshot.microphone, status: 'idle', failure: null}},
commands: [],
};
case 'failed': {
const error = nativeAudioDeviceModuleNotReadyError(snapshot);
return {
snapshot: {
...snapshot,
microphone: {...snapshot.microphone, status: 'failed', published: null, operationId: null, failure: error},
lastFailure: error,
},
commands: [],
};
}
}
}
export function planMicrophoneDeviceChange(
snapshot: VoiceEngineV2Snapshot,
deviceId: string | null,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'planMicrophoneDeviceChange snapshot must not be null');
assert.ok(deviceId === null || typeof deviceId === 'string', 'deviceId must be string or null');
const current = snapshot.microphone.desired ?? snapshot.microphone.published;
if (!current) return {snapshot, commands: []};
const desired = microphoneOptionsWithAudioInputDevice(current, deviceId);
if (sameMicrophoneOptions(snapshot.microphone.desired, desired)) return {snapshot, commands: []};
return beginMicrophonePublish({
...snapshot,
microphone: {
...snapshot.microphone,
desired,
},
});
}
function isMicrophoneFailureDuringReconnect(snapshot: VoiceEngineV2Snapshot): boolean {
assert.ok(snapshot != null, 'isMicrophoneFailureDuringReconnect snapshot must not be null');
assert.ok(snapshot.connection != null, 'isMicrophoneFailureDuringReconnect snapshot.connection must not be null');
if (snapshot.connection.status === 'reconnecting') return true;
return snapshot.liveKit.connectionState === 'reconnecting';
}
function microphoneFailureAction(
snapshot: VoiceEngineV2Snapshot,
error: VoiceEngineV2Error,
requestedEnabled: boolean,
): VoiceEngineV2MicrophoneOperationFailureAction {
assert.ok(error != null, 'microphoneFailureAction error must not be null');
assert.equal(typeof requestedEnabled, 'boolean', 'requestedEnabled must be a boolean');
return getVoiceEngineV2MicrophoneOperationFailureAction(
{
ok: false,
error: {
code: error.code,
message: error.message,
capability: typeof error.capability === 'string' ? error.capability : undefined,
},
},
requestedEnabled,
{reconnecting: isMicrophoneFailureDuringReconnect(snapshot)},
);
}
function applyMicrophoneFailureRecovery(
snapshot: VoiceEngineV2Snapshot,
error: VoiceEngineV2Error,
requestedEnabled: boolean,
): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'applyMicrophoneFailureRecovery snapshot must not be null');
assert.ok(error != null, 'applyMicrophoneFailureRecovery error must not be null');
const action = microphoneFailureAction(snapshot, error, requestedEnabled);
if (action !== 'mute') return snapshot;
return refreshMicrophoneDerivedState({
...snapshot,
audioControls: {
...snapshot.audioControls,
locallyMuted: true,
preferredLocallyMuted: true,
hasUserSetMute: true,
},
});
}
export function beginMicrophonePublish(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'beginMicrophonePublish snapshot must not be null');
assert.ok(snapshot.microphone != null, 'beginMicrophonePublish snapshot.microphone must not be null');
const desired = snapshot.microphone.desired;
if (!desired) return {snapshot, commands: []};
if (!snapshot.capabilities.microphone) {
const error = unsupportedCapability('microphone');
return {
snapshot: {
...snapshot,
microphone: {...snapshot.microphone, status: 'failed', failure: error},
lastFailure: error,
},
commands: [],
};
}
if (!isConnected(snapshot)) return {snapshot, commands: []};
const gated = planNativeAudioDeviceModuleGatedMicrophone(snapshot);
if (gated) return gated;
if (snapshot.microphone.status === 'publishing') return {snapshot, commands: []};
if (snapshot.microphone.status === 'published' && sameMicrophoneOptions(snapshot.microphone.published, desired)) {
return {snapshot, commands: []};
}
const allocated = allocateOperation(snapshot);
return {
snapshot: {
...allocated.snapshot,
microphone: {
...allocated.snapshot.microphone,
status: 'publishing',
operationId: allocated.operationId,
failure: null,
},
},
commands: [{type: 'microphone.publish', operationId: allocated.operationId, options: desired}],
};
}
export function transitionMicrophone(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2MicrophoneEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionMicrophone snapshot must not be null');
assert.ok(event != null, 'transitionMicrophone event must not be null');
assert.equal(typeof event.type, 'string', 'microphone event type must be a string');
assert.ok(
event.type.startsWith('microphone.') ||
event.type === 'localAudio.muteRequested' ||
event.type === 'localAudio.deafenRequested' ||
event.type === 'audioControls.changed',
'microphone reducer received unrelated event',
);
switch (event.type) {
case 'microphone.publishRequested':
return beginMicrophonePublish({
...snapshot,
microphone: {...snapshot.microphone, desired: event.options},
});
case 'microphone.publishSucceeded':
return onPublishSucceeded(snapshot, event.operationId);
case 'microphone.publishFailed':
return onPublishFailed(snapshot, event.operationId, event.error);
case 'microphone.unpublishRequested':
return beginUnpublish(snapshot, 'microphone', 'microphone.unpublish');
case 'microphone.unpublishSucceeded':
return onUnpublishSucceeded(snapshot, event.operationId);
case 'microphone.unpublishFailed':
return failUnpublish(snapshot, event.operationId, event.error, 'microphone');
case 'microphone.setEnabledRequested':
return onSetEnabledRequested(snapshot, event.enabled);
case 'microphone.setEnabledSucceeded':
return onSetEnabledSucceeded(snapshot, event.operationId);
case 'microphone.setEnabledFailed':
return onSetEnabledFailed(snapshot, event.operationId, event.error);
case 'localAudio.muteRequested':
return setLocalAudioMute(snapshot, event.muted);
case 'localAudio.deafenRequested':
return setLocalAudioDeafen(snapshot, event.deafened);
case 'audioControls.changed':
return planMicrophoneEnabled({
...snapshot,
audioControls: {...snapshot.audioControls, ...event.controls},
});
}
}
function onPublishSucceeded(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onPublishSucceeded snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onPublishSucceeded operationId must be an integer');
if (snapshot.microphone.operationId !== operationId) return {snapshot, commands: []};
return planMicrophoneEnabled(
{
...markOperation(snapshot, operationId, 'succeeded'),
microphone: {
...snapshot.microphone,
status: snapshot.microphone.desired ? 'published' : 'idle',
published: snapshot.microphone.desired,
operationId: null,
failure: null,
},
},
true,
);
}
function onPublishFailed(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
error: VoiceEngineV2Error,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onPublishFailed snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onPublishFailed operationId must be an integer');
assert.ok(error != null, 'onPublishFailed error must not be null');
if (snapshot.microphone.operationId !== operationId) return {snapshot, commands: []};
const failed = {
...markOperation(snapshot, operationId, 'failed', error),
microphone: {
...applyMediaFailure(snapshot.microphone, operationId, error),
enabled: snapshot.microphone.enabled,
localSpeakingOverride: snapshot.microphone.localSpeakingOverride,
setEnabledOperationId: snapshot.microphone.setEnabledOperationId,
},
lastFailure: error,
};
return {
snapshot: applyMicrophoneFailureRecovery(failed, error, snapshot.microphone.enabled),
commands: [],
};
}
function onUnpublishSucceeded(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onUnpublishSucceeded snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onUnpublishSucceeded operationId must be an integer');
return {
snapshot: {
...snapshot,
microphone: {
...completeUnpublish(snapshot.microphone, operationId),
enabled: snapshot.microphone.enabled,
localSpeakingOverride: false,
setEnabledOperationId: snapshot.microphone.setEnabledOperationId,
},
},
commands: [],
};
}
function onSetEnabledRequested(snapshot: VoiceEngineV2Snapshot, enabled: boolean): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onSetEnabledRequested snapshot must not be null');
assert.equal(typeof enabled, 'boolean', 'onSetEnabledRequested enabled must be a boolean');
const base = {
...snapshot,
microphone: {
...snapshot.microphone,
enabled,
},
};
if (!isConnected(base) || !base.capabilities.microphone) return {snapshot: base, commands: []};
if (!isMicrophonePublished(base) && base.microphone.status !== 'publishing') return {snapshot: base, commands: []};
const allocated = allocateOperation(base);
return {
snapshot: {
...allocated.snapshot,
microphone: {
...allocated.snapshot.microphone,
setEnabledOperationId: allocated.operationId,
},
},
commands: [{type: 'microphone.setEnabled', operationId: allocated.operationId, enabled}],
};
}
function onSetEnabledSucceeded(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onSetEnabledSucceeded snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onSetEnabledSucceeded operationId must be an integer');
if (snapshot.microphone.setEnabledOperationId !== operationId) return {snapshot, commands: []};
return {
snapshot: {
...markOperation(snapshot, operationId, 'succeeded'),
microphone: {...snapshot.microphone, setEnabledOperationId: null, failure: null},
},
commands: [],
};
}
function onSetEnabledFailed(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
error: VoiceEngineV2Error,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onSetEnabledFailed snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onSetEnabledFailed operationId must be an integer');
assert.ok(error != null, 'onSetEnabledFailed error must not be null');
if (snapshot.microphone.setEnabledOperationId !== operationId) return {snapshot, commands: []};
return {
snapshot: applyMicrophoneFailureRecovery(
{
...markOperation(snapshot, operationId, 'failed', error),
microphone: {...snapshot.microphone, setEnabledOperationId: null, failure: error},
lastFailure: error,
},
error,
snapshot.microphone.enabled,
),
commands: [],
};
}
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {beginMicrophonePublish} from './microphone';
type VoiceEngineV2NativeAudioDeviceModuleEvent = Extract<
VoiceEngineV2Event,
{type: 'nativeAudioDeviceModule.statusChanged'}
>;
export function transitionNativeAudioDeviceModule(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2NativeAudioDeviceModuleEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionNativeAudioDeviceModule snapshot must not be null');
assert.ok(event != null, 'transitionNativeAudioDeviceModule event must not be null');
assert.equal(event.type, 'nativeAudioDeviceModule.statusChanged', 'unexpected ADM event type');
assert.equal(typeof event.status, 'string', 'ADM status must be a string');
const next: VoiceEngineV2Snapshot = {
...snapshot,
nativeAudioDeviceModule: {
status: event.status,
detail: event.detail ?? null,
},
};
return beginMicrophonePublish(next);
}
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {allocateOperation, queueCommand} from './_helpers';
type VoiceEngineV2NativeAudioTapEvent = Extract<VoiceEngineV2Event, {type: `nativeAudioTap.${string}`}>;
export function transitionNativeAudioTap(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2NativeAudioTapEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionNativeAudioTap snapshot must not be null');
assert.ok(event != null, 'transitionNativeAudioTap event must not be null');
assert.equal(typeof event.type, 'string', 'nativeAudioTap event type must be a string');
assert.ok(event.type.startsWith('nativeAudioTap.'), 'nativeAudioTap reducer received unrelated event');
switch (event.type) {
case 'nativeAudioTap.startRequested': {
const allocated = allocateOperation(snapshot);
return queueCommand(
{
...allocated.snapshot,
nativeAudioTap: {
...allocated.snapshot.nativeAudioTap,
taps: {...allocated.snapshot.nativeAudioTap.taps, [event.options.tapId]: event.options},
operationIds: {
...allocated.snapshot.nativeAudioTap.operationIds,
[event.options.tapId]: allocated.operationId,
},
failure: null,
},
},
{type: 'nativeAudioTap.start', operationId: allocated.operationId, options: event.options},
);
}
case 'nativeAudioTap.stopRequested': {
const allocated = allocateOperation(snapshot);
return queueCommand(
{
...allocated.snapshot,
nativeAudioTap: {
...allocated.snapshot.nativeAudioTap,
operationIds: {
...allocated.snapshot.nativeAudioTap.operationIds,
[event.tapId]: allocated.operationId,
},
},
},
{type: 'nativeAudioTap.stop', operationId: allocated.operationId, tapId: event.tapId},
);
}
}
}
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Command} from '../../protocol/commands';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {
VoiceEngineV2Error,
VoiceEngineV2NativeCaptureOptions,
VoiceEngineV2OperationId,
} from '../../protocol/types';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {allocateOperation, markOperation, queueCommand} from './_helpers';
import {nativeZeroCopyRequiredError, unavailableZeroCopyTransportError} from './_zeroCopy';
type VoiceEngineV2NativeCaptureEvent = Extract<VoiceEngineV2Event, {type: `nativeCapture.${string}`}>;
function failWithZeroCopyRequired(
snapshot: VoiceEngineV2Snapshot,
resource: 'capture' | 'frame',
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'failWithZeroCopyRequired snapshot must not be null');
assert.equal(typeof resource, 'string', 'failWithZeroCopyRequired resource must be a string');
const error = nativeZeroCopyRequiredError(resource);
return {
snapshot: {
...snapshot,
nativeCapture: {...snapshot.nativeCapture, failure: error},
lastFailure: error,
},
commands: [],
};
}
function failWithUnavailableTransport(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'failWithUnavailableTransport snapshot must not be null');
assert.ok(snapshot.nativeCapture != null, 'failWithUnavailableTransport snapshot.nativeCapture must not be null');
const error = unavailableZeroCopyTransportError('capture');
return {
snapshot: {
...snapshot,
nativeCapture: {...snapshot.nativeCapture, failure: error},
lastFailure: error,
},
commands: [],
};
}
function onStartOrUpdate(
snapshot: VoiceEngineV2Snapshot,
options: VoiceEngineV2NativeCaptureOptions,
commandType: 'nativeCapture.start' | 'nativeCapture.update',
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onStartOrUpdate snapshot must not be null');
assert.ok(options != null, 'onStartOrUpdate options must not be null');
assert.equal(typeof options.captureId, 'string', 'options.captureId must be a string');
if (options.zeroCopyRequired !== true) return failWithZeroCopyRequired(snapshot, 'capture');
if (!snapshot.capabilities.zeroCopyScreenTransport) return failWithUnavailableTransport(snapshot);
const allocated = allocateOperation(snapshot);
return queueCommand(
{
...allocated.snapshot,
nativeCapture: {
...allocated.snapshot.nativeCapture,
captures: {
...allocated.snapshot.nativeCapture.captures,
[options.captureId]: options,
},
operationIds: {
...allocated.snapshot.nativeCapture.operationIds,
[options.captureId]: allocated.operationId,
},
failure: null,
},
},
{type: commandType, operationId: allocated.operationId, options} as VoiceEngineV2Command,
);
}
function onStopRequested(snapshot: VoiceEngineV2Snapshot, captureId: string): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onStopRequested snapshot must not be null');
assert.equal(typeof captureId, 'string', 'onStopRequested captureId must be a string');
assert.ok(captureId.length > 0, 'onStopRequested captureId must not be empty');
const allocated = allocateOperation(snapshot);
return queueCommand(
{
...allocated.snapshot,
nativeCapture: {
...allocated.snapshot.nativeCapture,
operationIds: {
...allocated.snapshot.nativeCapture.operationIds,
[captureId]: allocated.operationId,
},
failure: null,
},
},
{type: 'nativeCapture.stop', operationId: allocated.operationId, captureId},
);
}
function completeNativeCaptureOperation(
snapshot: VoiceEngineV2Snapshot,
captureId: string,
operationId: VoiceEngineV2OperationId | null,
error: VoiceEngineV2Error | null,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'completeNativeCaptureOperation snapshot must not be null');
assert.equal(typeof captureId, 'string', 'completeNativeCaptureOperation captureId must be a string');
if (operationId != null && snapshot.nativeCapture.operationIds[captureId] !== operationId) {
return {snapshot, commands: []};
}
const operationIds = {...snapshot.nativeCapture.operationIds};
if (operationId != null) delete operationIds[captureId];
return {
snapshot: {
...(operationId != null ? markOperation(snapshot, operationId, error ? 'failed' : 'succeeded', error) : snapshot),
nativeCapture: {
...snapshot.nativeCapture,
operationIds,
failure: error,
},
lastFailure: error ?? snapshot.lastFailure,
},
commands: [],
};
}
function stopNativeCapture(
snapshot: VoiceEngineV2Snapshot,
captureId: string,
operationId: VoiceEngineV2OperationId | null,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'stopNativeCapture snapshot must not be null');
assert.equal(typeof captureId, 'string', 'stopNativeCapture captureId must be a string');
if (operationId != null && snapshot.nativeCapture.operationIds[captureId] !== operationId) {
return {snapshot, commands: []};
}
const captures = {...snapshot.nativeCapture.captures};
const operationIds = {...snapshot.nativeCapture.operationIds};
delete captures[captureId];
delete operationIds[captureId];
return {
snapshot: {
...(operationId != null ? markOperation(snapshot, operationId, 'succeeded') : snapshot),
nativeCapture: {
...snapshot.nativeCapture,
captures,
operationIds,
failure: null,
},
},
commands: [],
};
}
export function transitionNativeCapture(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2NativeCaptureEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionNativeCapture snapshot must not be null');
assert.ok(event != null, 'transitionNativeCapture event must not be null');
assert.equal(typeof event.type, 'string', 'nativeCapture event type must be a string');
assert.ok(event.type.startsWith('nativeCapture.'), 'nativeCapture reducer received unrelated event');
switch (event.type) {
case 'nativeCapture.startRequested':
return onStartOrUpdate(snapshot, event.options, 'nativeCapture.start');
case 'nativeCapture.updateRequested':
return onStartOrUpdate(snapshot, event.options, 'nativeCapture.update');
case 'nativeCapture.stopRequested':
return onStopRequested(snapshot, event.captureId);
case 'nativeCapture.started':
return completeNativeCaptureOperation(snapshot, event.captureId, event.operationId, null);
case 'nativeCapture.stopped':
return stopNativeCapture(snapshot, event.captureId, event.operationId);
case 'nativeCapture.failed':
return completeNativeCaptureOperation(snapshot, event.captureId, event.operationId, event.error);
case 'nativeCapture.frame':
if (event.frame.zeroCopy !== true) return failWithZeroCopyRequired(snapshot, 'frame');
return {snapshot, commands: []};
}
}
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2NativeFrameSinkOptions} from '../../protocol/types';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {allocateOperation, queueCommand} from './_helpers';
import {nativeZeroCopyRequiredError, unavailableZeroCopyTransportError} from './_zeroCopy';
type VoiceEngineV2NativeFrameSinkEvent = Extract<VoiceEngineV2Event, {type: `nativeFrameSink.${string}`}>;
export function transitionNativeFrameSink(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2NativeFrameSinkEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionNativeFrameSink snapshot must not be null');
assert.ok(event != null, 'transitionNativeFrameSink event must not be null');
assert.equal(typeof event.type, 'string', 'nativeFrameSink event type must be a string');
assert.ok(event.type.startsWith('nativeFrameSink.'), 'nativeFrameSink reducer received unrelated event');
switch (event.type) {
case 'nativeFrameSink.attachRequested':
return onAttachRequested(snapshot, event.options);
case 'nativeFrameSink.detachRequested':
return onDetachRequested(snapshot, event.sinkId);
}
}
function onAttachRequested(
snapshot: VoiceEngineV2Snapshot,
options: VoiceEngineV2NativeFrameSinkOptions,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onAttachRequested snapshot must not be null');
assert.ok(options != null, 'onAttachRequested options must not be null');
assert.equal(typeof options.sinkId, 'string', 'options.sinkId must be a string');
if (options.zeroCopyRequired !== true) {
const error = nativeZeroCopyRequiredError('frameSink');
return {
snapshot: {
...snapshot,
nativeFrameSink: {...snapshot.nativeFrameSink, failure: error},
lastFailure: error,
},
commands: [],
};
}
if (!snapshot.capabilities.zeroCopyScreenTransport) {
const error = unavailableZeroCopyTransportError('frameSink');
return {
snapshot: {
...snapshot,
nativeFrameSink: {...snapshot.nativeFrameSink, failure: error},
lastFailure: error,
},
commands: [],
};
}
const allocated = allocateOperation(snapshot);
return queueCommand(
{
...allocated.snapshot,
nativeFrameSink: {
...allocated.snapshot.nativeFrameSink,
sinks: {...allocated.snapshot.nativeFrameSink.sinks, [options.sinkId]: options},
operationIds: {
...allocated.snapshot.nativeFrameSink.operationIds,
[options.sinkId]: allocated.operationId,
},
failure: null,
},
},
{type: 'nativeFrameSink.attach', operationId: allocated.operationId, options},
);
}
function onDetachRequested(snapshot: VoiceEngineV2Snapshot, sinkId: string): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onDetachRequested snapshot must not be null');
assert.equal(typeof sinkId, 'string', 'onDetachRequested sinkId must be a string');
assert.ok(sinkId.length > 0, 'onDetachRequested sinkId must not be empty');
const allocated = allocateOperation(snapshot);
const sinks = {...allocated.snapshot.nativeFrameSink.sinks};
delete sinks[sinkId];
return queueCommand(
{
...allocated.snapshot,
nativeFrameSink: {
...allocated.snapshot.nativeFrameSink,
sinks,
operationIds: {
...allocated.snapshot.nativeFrameSink.operationIds,
[sinkId]: allocated.operationId,
},
},
},
{type: 'nativeFrameSink.detach', operationId: allocated.operationId, sinkId},
);
}
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {commandIfConnected} from './_helpers';
type VoiceEngineV2OutputDeviceEvent = Extract<VoiceEngineV2Event, {type: `outputDevice.${string}`}>;
export function transitionOutputDevice(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2OutputDeviceEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionOutputDevice snapshot must not be null');
assert.ok(event != null, 'transitionOutputDevice event must not be null');
assert.equal(typeof event.type, 'string', 'outputDevice event type must be a string');
assert.ok(event.type.startsWith('outputDevice.'), 'outputDevice reducer received unrelated event');
switch (event.type) {
case 'outputDevice.setRequested': {
const base = {
...snapshot,
outputDevice: {...snapshot.outputDevice, desiredDeviceId: event.options.deviceId},
};
return commandIfConnected(base, 'outputDevice', {
type: 'outputDevice.set',
options: event.options,
});
}
case 'outputDevice.setSucceeded':
if (snapshot.outputDevice.operationId !== event.operationId) return {snapshot, commands: []};
return {
snapshot: {
...snapshot,
outputDevice: {
...snapshot.outputDevice,
activeDeviceId: snapshot.outputDevice.desiredDeviceId,
operationId: null,
failure: null,
},
},
commands: [],
};
case 'outputDevice.setFailed':
if (snapshot.outputDevice.operationId !== event.operationId) return {snapshot, commands: []};
return {
snapshot: {
...snapshot,
outputDevice: {...snapshot.outputDevice, operationId: null, failure: event.error},
lastFailure: event.error,
},
commands: [],
};
}
}
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {commandIfConnected} from './_helpers';
type VoiceEngineV2ParticipantVolumeEvent = Extract<VoiceEngineV2Event, {type: `participantVolume.${string}`}>;
export function transitionParticipantVolume(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2ParticipantVolumeEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionParticipantVolume snapshot must not be null');
assert.ok(event != null, 'transitionParticipantVolume event must not be null');
assert.equal(typeof event.type, 'string', 'participantVolume event type must be a string');
assert.ok(event.type.startsWith('participantVolume.'), 'participantVolume reducer received unrelated event');
switch (event.type) {
case 'participantVolume.setRequested': {
if (snapshot.participantVolumes[event.options.participantIdentity] === event.options.volume) {
return {snapshot, commands: []};
}
const base = {
...snapshot,
participantVolumes: {
...snapshot.participantVolumes,
[event.options.participantIdentity]: event.options.volume,
},
};
return commandIfConnected(base, 'participantVolume', {
type: 'participantVolume.set',
options: event.options,
});
}
case 'participantVolume.setSucceeded':
return {snapshot, commands: []};
case 'participantVolume.setFailed':
return {snapshot: {...snapshot, lastFailure: event.error}, commands: []};
}
}
@@ -0,0 +1,111 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {
VoiceEngineV2Error,
VoiceEngineV2OperationId,
VoiceEngineV2PermissionName,
VoiceEngineV2PermissionResult,
} from '../../protocol/types';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {allocateOperation, markOperation, queueCommand} from './_helpers';
import {applyMicrophonePermissionResult} from './microphone';
type VoiceEngineV2PermissionsEvent = Extract<VoiceEngineV2Event, {type: `permissions.${string}`}>;
function beginPermissionOperation(
snapshot: VoiceEngineV2Snapshot,
commandType: 'permissions.check' | 'permissions.request',
name: VoiceEngineV2PermissionName,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'beginPermissionOperation snapshot must not be null');
assert.equal(typeof commandType, 'string', 'beginPermissionOperation commandType must be a string');
assert.equal(typeof name, 'string', 'beginPermissionOperation name must be a string');
const allocated = allocateOperation(snapshot);
return queueCommand(
{
...allocated.snapshot,
permissions: {
...allocated.snapshot.permissions,
operationIds: {
...allocated.snapshot.permissions.operationIds,
[String(name)]: allocated.operationId,
},
failure: null,
},
},
{type: commandType, operationId: allocated.operationId, name},
);
}
export function transitionPermissions(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2PermissionsEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionPermissions snapshot must not be null');
assert.ok(event != null, 'transitionPermissions event must not be null');
assert.equal(typeof event.type, 'string', 'permissions event type must be a string');
assert.ok(event.type.startsWith('permissions.'), 'permissions reducer received unrelated event');
switch (event.type) {
case 'permissions.checkRequested':
return beginPermissionOperation(snapshot, 'permissions.check', event.name);
case 'permissions.requestRequested':
return beginPermissionOperation(snapshot, 'permissions.request', event.name);
case 'permissions.result':
return onResult(snapshot, event.operationId, event.result);
case 'permissions.failed':
return onFailed(snapshot, event.operationId, event.name, event.error);
}
}
function onResult(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId | null,
result: VoiceEngineV2PermissionResult,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onResult snapshot must not be null');
assert.ok(result != null, 'onResult result must not be null');
assert.equal(typeof result.name, 'string', 'result.name must be a string');
if (operationId != null && snapshot.permissions.operationIds[String(result.name)] !== operationId) {
return {snapshot, commands: []};
}
const operationIds = {...snapshot.permissions.operationIds};
if (operationId != null) delete operationIds[String(result.name)];
return applyMicrophonePermissionResult(
{
...(operationId != null ? markOperation(snapshot, operationId, 'succeeded') : snapshot),
permissions: {
results: {
...snapshot.permissions.results,
[String(result.name)]: result,
},
operationIds,
failure: null,
},
},
result,
);
}
function onFailed(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
name: VoiceEngineV2PermissionName,
error: VoiceEngineV2Error,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onFailed snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'onFailed operationId must be an integer');
assert.ok(error != null, 'onFailed error must not be null');
if (snapshot.permissions.operationIds[String(name)] !== operationId) return {snapshot, commands: []};
const operationIds = {...snapshot.permissions.operationIds};
delete operationIds[String(name)];
return {
snapshot: {
...markOperation(snapshot, operationId, 'failed', error),
permissions: {...snapshot.permissions, operationIds, failure: error},
lastFailure: error,
},
commands: [],
};
}
@@ -0,0 +1,226 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {
VoiceEngineV2RemoteTrackSubscriptionOptions,
VoiceEngineV2Track,
VoiceEngineV2WatchedStream,
VoiceEngineV2WatchedStreamKey,
} from '../../protocol/types';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {appendTransition, commandIfConnected} from './_helpers';
type VoiceEngineV2RemoteTrackSubscriptionEvent = Extract<
VoiceEngineV2Event,
{
type: `remoteTrackSubscription.${string}` | `watchedStream.${string}` | `watchedStreams.${string}`;
}
>;
function sameRemoteTrackSubscriptionOptions(
a: VoiceEngineV2RemoteTrackSubscriptionOptions,
b: VoiceEngineV2RemoteTrackSubscriptionOptions,
): boolean {
assert.ok(a != null, 'sameRemoteTrackSubscriptionOptions a must not be null');
assert.ok(b != null, 'sameRemoteTrackSubscriptionOptions b must not be null');
return (
a.participantIdentity === b.participantIdentity &&
a.source === b.source &&
a.subscribed === b.subscribed &&
a.enabled === b.enabled &&
a.quality === b.quality
);
}
function subscriptionKey(options: VoiceEngineV2RemoteTrackSubscriptionOptions): string {
assert.ok(options != null, 'subscriptionKey options must not be null');
assert.equal(typeof options.participantIdentity, 'string', 'options.participantIdentity must be a string');
return `${options.participantIdentity}:${options.source}`;
}
function watchedStreamKey(stream: VoiceEngineV2WatchedStreamKey): string {
assert.ok(stream != null, 'watchedStreamKey stream must not be null');
assert.equal(typeof stream.participantIdentity, 'string', 'stream.participantIdentity must be a string');
return `${stream.participantIdentity}:${stream.source}`;
}
function findMatchingPublishedTrack(
snapshot: VoiceEngineV2Snapshot,
stream: VoiceEngineV2WatchedStreamKey,
): VoiceEngineV2Track | null {
assert.ok(snapshot != null, 'findMatchingPublishedTrack snapshot must not be null');
assert.ok(stream != null, 'findMatchingPublishedTrack stream must not be null');
return (
Object.values(snapshot.room.tracks).find(
(track) => track.participantIdentity === stream.participantIdentity && track.source === stream.source,
) ?? null
);
}
function watchedStreamToSubscriptionOptions(
stream: VoiceEngineV2WatchedStream,
): VoiceEngineV2RemoteTrackSubscriptionOptions {
assert.ok(stream != null, 'watchedStreamToSubscriptionOptions stream must not be null');
assert.equal(typeof stream.participantIdentity, 'string', 'stream.participantIdentity must be a string');
return {
participantIdentity: stream.participantIdentity,
source: stream.source,
subscribed: stream.enabled,
enabled: stream.enabled,
...(stream.quality ? {quality: stream.quality} : {}),
};
}
function applyRemoteTrackSubscriptionRequest(
snapshot: VoiceEngineV2Snapshot,
options: VoiceEngineV2RemoteTrackSubscriptionOptions,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'applyRemoteTrackSubscriptionRequest snapshot must not be null');
assert.ok(options != null, 'applyRemoteTrackSubscriptionRequest options must not be null');
const current = snapshot.remoteTrackSubscriptions[subscriptionKey(options)];
if (current && sameRemoteTrackSubscriptionOptions(current, options)) return {snapshot, commands: []};
const base = {
...snapshot,
remoteTrackSubscriptions: {
...snapshot.remoteTrackSubscriptions,
[subscriptionKey(options)]: options,
},
};
return commandIfConnected(base, 'remoteTrackSubscription', {
type: 'remoteTrackSubscription.set',
options,
});
}
function applyWatchedStream(
snapshot: VoiceEngineV2Snapshot,
stream: VoiceEngineV2WatchedStream,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'applyWatchedStream snapshot must not be null');
assert.ok(stream != null, 'applyWatchedStream stream must not be null');
const existingTrack = stream.trackSid ? null : findMatchingPublishedTrack(snapshot, stream);
const desired: VoiceEngineV2WatchedStream = {
...stream,
trackSid: stream.trackSid ?? existingTrack?.trackSid ?? null,
};
const base = {
...snapshot,
watchedStreams: {
...snapshot.watchedStreams,
[watchedStreamKey(desired)]: desired,
},
};
return applyRemoteTrackSubscriptionRequest(base, watchedStreamToSubscriptionOptions(desired));
}
function applyUnwatchedStream(
snapshot: VoiceEngineV2Snapshot,
stream: VoiceEngineV2WatchedStreamKey,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'applyUnwatchedStream snapshot must not be null');
assert.ok(stream != null, 'applyUnwatchedStream stream must not be null');
const key = watchedStreamKey(stream);
const previous = snapshot.watchedStreams[key];
const watchedStreams = {...snapshot.watchedStreams};
delete watchedStreams[key];
const base = {...snapshot, watchedStreams};
return applyRemoteTrackSubscriptionRequest(base, {
participantIdentity: stream.participantIdentity,
source: stream.source,
subscribed: false,
enabled: false,
...(previous?.quality ? {quality: previous.quality} : {}),
});
}
function replaceWatchedStreams(
snapshot: VoiceEngineV2Snapshot,
streams: Array<VoiceEngineV2WatchedStream>,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'replaceWatchedStreams snapshot must not be null');
assert.ok(Array.isArray(streams), 'replaceWatchedStreams streams must be an array');
const desiredKeys = new Set(streams.map(watchedStreamKey));
let transition: VoiceEngineV2Transition = {snapshot, commands: []};
for (const key of Object.keys(snapshot.watchedStreams).sort()) {
if (desiredKeys.has(key)) continue;
const previous = snapshot.watchedStreams[key];
if (!previous) continue;
transition = appendTransition(
transition,
applyUnwatchedStream(transition.snapshot, {
participantIdentity: previous.participantIdentity,
source: previous.source,
}),
);
}
for (const stream of [...streams].sort((a, b) => watchedStreamKey(a).localeCompare(watchedStreamKey(b)))) {
transition = appendTransition(transition, applyWatchedStream(transition.snapshot, stream));
}
return transition;
}
export function syncWatchedStreamTrack(
snapshot: VoiceEngineV2Snapshot,
track: VoiceEngineV2Track,
): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'syncWatchedStreamTrack snapshot must not be null');
assert.ok(track != null, 'syncWatchedStreamTrack track must not be null');
const key = watchedStreamKey({participantIdentity: track.participantIdentity, source: track.source});
const stream = snapshot.watchedStreams[key];
if (!stream || stream.trackSid === track.trackSid) return snapshot;
return {
...snapshot,
watchedStreams: {
...snapshot.watchedStreams,
[key]: {...stream, trackSid: track.trackSid},
},
};
}
export function clearWatchedStreamTrack(snapshot: VoiceEngineV2Snapshot, trackSid: string): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'clearWatchedStreamTrack snapshot must not be null');
assert.equal(typeof trackSid, 'string', 'clearWatchedStreamTrack trackSid must be a string');
let changed = false;
const watchedStreams: Record<string, VoiceEngineV2WatchedStream> = {};
for (const key of Object.keys(snapshot.watchedStreams).sort()) {
const stream = snapshot.watchedStreams[key];
if (!stream) continue;
if (stream.trackSid === trackSid) {
changed = true;
watchedStreams[key] = {...stream, trackSid: null};
} else {
watchedStreams[key] = stream;
}
}
return changed ? {...snapshot, watchedStreams} : snapshot;
}
export function transitionRemoteTrackSubscription(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2RemoteTrackSubscriptionEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionRemoteTrackSubscription snapshot must not be null');
assert.ok(event != null, 'transitionRemoteTrackSubscription event must not be null');
assert.equal(typeof event.type, 'string', 'remoteTrackSubscription event type must be a string');
assert.ok(
event.type.startsWith('remoteTrackSubscription.') ||
event.type.startsWith('watchedStream.') ||
event.type.startsWith('watchedStreams.'),
'remoteTrackSubscription reducer received unrelated event',
);
switch (event.type) {
case 'remoteTrackSubscription.setRequested':
return applyRemoteTrackSubscriptionRequest(snapshot, event.options);
case 'remoteTrackSubscription.setSucceeded':
return {snapshot, commands: []};
case 'remoteTrackSubscription.setFailed':
return {snapshot: {...snapshot, lastFailure: event.error}, commands: []};
case 'watchedStream.watchRequested':
return applyWatchedStream(snapshot, event.stream);
case 'watchedStream.unwatchRequested':
return applyUnwatchedStream(snapshot, event.stream);
case 'watchedStreams.replaced':
return replaceWatchedStreams(snapshot, event.streams);
}
}
@@ -0,0 +1,179 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2Participant, VoiceEngineV2Track} from '../../protocol/types';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {removeInboundVideoTrack} from './inboundVideo';
import {clearWatchedStreamTrack, syncWatchedStreamTrack} from './remoteTrackSubscription';
type VoiceEngineV2RoomEvent = Extract<VoiceEngineV2Event, {type: `room.${string}`}>;
function participantMatches(participant: VoiceEngineV2Participant, identity?: string, sid?: string): boolean {
assert.ok(participant != null, 'participantMatches participant must not be null');
assert.equal(typeof participant.identity, 'string', 'participant.identity must be a string');
return (
(identity !== undefined && participant.identity === identity) || (sid !== undefined && participant.sid === sid)
);
}
function trackMatchesParticipant(track: VoiceEngineV2Track, identities: Set<string>, sid?: string): boolean {
assert.ok(track != null, 'trackMatchesParticipant track must not be null');
assert.ok(identities instanceof Set, 'trackMatchesParticipant identities must be a Set');
return identities.has(track.participantIdentity) || (sid !== undefined && track.participantSid === sid);
}
function removeParticipantRoomState(
snapshot: VoiceEngineV2Snapshot,
participantIdentity?: string,
participantSid?: string,
): VoiceEngineV2Snapshot {
assert.ok(snapshot != null, 'removeParticipantRoomState snapshot must not be null');
assert.ok(snapshot.room != null, 'removeParticipantRoomState snapshot.room must not be null');
const identities = new Set<string>();
if (participantIdentity) identities.add(participantIdentity);
const participants = {...snapshot.room.participants};
for (const identity of Object.keys(snapshot.room.participants).sort()) {
const participant = snapshot.room.participants[identity];
if (!participant) continue;
if (!participantMatches(participant, participantIdentity, participantSid)) continue;
identities.add(identity);
delete participants[identity];
}
const tracks: Record<string, VoiceEngineV2Track> = {};
const inboundTracks = {...snapshot.inboundVideo.tracks};
for (const trackSid of Object.keys(snapshot.room.tracks).sort()) {
const track = snapshot.room.tracks[trackSid];
if (!track) continue;
if (trackMatchesParticipant(track, identities, participantSid)) {
delete inboundTracks[trackSid];
continue;
}
tracks[trackSid] = track;
}
for (const trackSid of Object.keys(snapshot.inboundVideo.tracks).sort()) {
const track = snapshot.inboundVideo.tracks[trackSid];
if (!track) continue;
if (
(participantSid && track.participantSid === participantSid) ||
(track.participantIdentity && identities.has(track.participantIdentity))
) {
delete inboundTracks[trackSid];
}
}
return {
...snapshot,
room: {...snapshot.room, participants, tracks},
inboundVideo: {...snapshot.inboundVideo, tracks: inboundTracks},
};
}
function onParticipantJoined(
snapshot: VoiceEngineV2Snapshot,
participant: VoiceEngineV2Participant,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onParticipantJoined snapshot must not be null');
assert.ok(participant != null, 'onParticipantJoined participant must not be null');
assert.equal(typeof participant.identity, 'string', 'participant.identity must be a string');
return {
snapshot: {
...snapshot,
room: {
...snapshot.room,
participants: {
...snapshot.room.participants,
[participant.identity]: participant,
},
},
},
commands: [],
};
}
function onTrackPublished(snapshot: VoiceEngineV2Snapshot, track: VoiceEngineV2Track): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onTrackPublished snapshot must not be null');
assert.ok(track != null, 'onTrackPublished track must not be null');
assert.equal(typeof track.trackSid, 'string', 'track.trackSid must be a string');
return {
snapshot: syncWatchedStreamTrack(
{
...snapshot,
room: {
...snapshot.room,
tracks: {
...snapshot.room.tracks,
[track.trackSid]: track,
},
},
},
track,
),
commands: [],
};
}
function onTrackUnpublished(snapshot: VoiceEngineV2Snapshot, trackSid: string): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onTrackUnpublished snapshot must not be null');
assert.equal(typeof trackSid, 'string', 'onTrackUnpublished trackSid must be a string');
const tracks = {...snapshot.room.tracks};
delete tracks[trackSid];
return {
snapshot: removeInboundVideoTrack(
clearWatchedStreamTrack({...snapshot, room: {...snapshot.room, tracks}}, trackSid),
trackSid,
),
commands: [],
};
}
function onTrackMuteChanged(
snapshot: VoiceEngineV2Snapshot,
trackSid: string,
muted: boolean,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onTrackMuteChanged snapshot must not be null');
assert.equal(typeof trackSid, 'string', 'onTrackMuteChanged trackSid must be a string');
assert.equal(typeof muted, 'boolean', 'onTrackMuteChanged muted must be a boolean');
const track = snapshot.room.tracks[trackSid];
if (!track) return {snapshot, commands: []};
return {
snapshot: {
...snapshot,
room: {
...snapshot.room,
tracks: {
...snapshot.room.tracks,
[trackSid]: {...track, muted},
},
},
},
commands: [],
};
}
export function transitionRoom(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2RoomEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionRoom snapshot must not be null');
assert.ok(event != null, 'transitionRoom event must not be null');
assert.equal(typeof event.type, 'string', 'room event type must be a string');
assert.ok(event.type.startsWith('room.'), 'room reducer received unrelated event');
switch (event.type) {
case 'room.participantJoined':
return onParticipantJoined(snapshot, event.participant);
case 'room.participantLeft':
return {
snapshot: removeParticipantRoomState(snapshot, event.participantIdentity, event.participantSid),
commands: [],
};
case 'room.trackPublished':
return onTrackPublished(snapshot, event.track);
case 'room.trackUnpublished':
return onTrackUnpublished(snapshot, event.trackSid);
case 'room.trackMuted':
return onTrackMuteChanged(snapshot, event.trackSid, true);
case 'room.trackUnmuted':
return onTrackMuteChanged(snapshot, event.trackSid, false);
}
}
@@ -0,0 +1,220 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {planVoiceEngineV2ScreenEncodingChange} from '../../policies/screenShare';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {
VoiceEngineV2Error,
VoiceEngineV2OperationId,
VoiceEngineV2ScreenEncodingOptions,
VoiceEngineV2ScreenOptions,
} from '../../protocol/types';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {
allocateOperation,
beginUnpublish,
failUnpublish,
invalidArgument,
isConnected,
unsupportedCapability,
} from './_helpers';
import {applyMediaFailure, applyMediaSuccess, completeUnpublish} from './_media';
import {nativeZeroCopyRequiredError, unavailableZeroCopyTransportError} from './_zeroCopy';
type VoiceEngineV2ScreenEvent = Extract<VoiceEngineV2Event, {type: `screen.${string}`}>;
function sameScreenOptions(a: VoiceEngineV2ScreenOptions | null, b: VoiceEngineV2ScreenOptions | null): boolean {
assert.ok(a !== undefined, 'sameScreenOptions a must not be undefined');
assert.ok(b !== undefined, 'sameScreenOptions b must not be undefined');
return (
a?.captureId === b?.captureId &&
a?.width === b?.width &&
a?.height === b?.height &&
a?.codec === b?.codec &&
a?.hardwareEncoding === b?.hardwareEncoding &&
a?.zeroCopyRequired === b?.zeroCopyRequired &&
a?.maxBitrateBps === b?.maxBitrateBps &&
a?.maxFramerate === b?.maxFramerate &&
a?.adaptiveSend === b?.adaptiveSend &&
a?.minVideoFps === b?.minVideoFps &&
a?.maxAudioBufferMs === b?.maxAudioBufferMs &&
a?.pacing === b?.pacing
);
}
function validateScreenPublish(
snapshot: VoiceEngineV2Snapshot,
desired: VoiceEngineV2ScreenOptions,
): VoiceEngineV2Error | null {
assert.ok(snapshot != null, 'validateScreenPublish snapshot must not be null');
assert.ok(desired != null, 'validateScreenPublish desired must not be null');
if (desired.zeroCopyRequired === true && !snapshot.capabilities.zeroCopyScreenTransport) {
return unavailableZeroCopyTransportError('capture');
}
if (desired.hardwareEncoding === true) {
if (!snapshot.capabilities.hardwareEncoding) {
return unsupportedCapability('hardwareEncoding');
}
if (desired.zeroCopyRequired !== true) {
return nativeZeroCopyRequiredError('hardwareEncoder');
}
if (!snapshot.capabilities.zeroCopyScreenTransport) {
return unavailableZeroCopyTransportError('hardwareEncoder');
}
}
return null;
}
export function beginScreenPublish(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'beginScreenPublish snapshot must not be null');
assert.ok(snapshot.screen != null, 'beginScreenPublish snapshot.screen must not be null');
const desired = snapshot.screen.desired;
if (!desired) return {snapshot, commands: []};
if (!snapshot.capabilities.screen) {
const error = unsupportedCapability('screen');
return {
snapshot: {
...snapshot,
screen: {...snapshot.screen, status: 'failed', failure: error},
lastFailure: error,
},
commands: [],
};
}
const validationError = validateScreenPublish(snapshot, desired);
if (validationError) {
return {
snapshot: {
...snapshot,
screen: {...snapshot.screen, status: 'failed', failure: validationError},
lastFailure: validationError,
},
commands: [],
};
}
if (!isConnected(snapshot)) return {snapshot, commands: []};
if (snapshot.screen.status === 'published' && sameScreenOptions(snapshot.screen.published, desired)) {
return {snapshot, commands: []};
}
const allocated = allocateOperation(snapshot);
return {
snapshot: {
...allocated.snapshot,
screen: {
...allocated.snapshot.screen,
status: 'publishing',
operationId: allocated.operationId,
failure: null,
},
},
commands: [{type: 'screen.publish', operationId: allocated.operationId, options: desired}],
};
}
function failScreen(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
error: VoiceEngineV2Error,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'failScreen snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'failScreen operationId must be an integer');
assert.ok(error != null, 'failScreen error must not be null');
const state = snapshot.screen;
return {
snapshot: {
...snapshot,
screen: applyMediaFailure(state, operationId, error),
lastFailure: state.operationId === operationId ? error : snapshot.lastFailure,
},
commands: [],
};
}
export function beginScreenEncodingUpdate(
snapshot: VoiceEngineV2Snapshot,
options: VoiceEngineV2ScreenEncodingOptions,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'onUpdateEncodingRequested snapshot must not be null');
assert.ok(options != null, 'onUpdateEncodingRequested options must not be null');
const plan = planVoiceEngineV2ScreenEncodingChange({
published: snapshot.screen.published,
desired: snapshot.screen.desired,
update: options,
});
if (plan.action === 'reject' || !plan.desired) {
const error =
plan.error ?? invalidArgument('Cannot update screen encoding without a matching published screen', 'screen');
return {
snapshot: {...snapshot, screen: {...snapshot.screen, failure: error}, lastFailure: error},
commands: [],
};
}
const validationError = validateScreenPublish(snapshot, plan.desired);
if (validationError) {
return {
snapshot: {
...snapshot,
screen: {...snapshot.screen, status: 'failed', desired: plan.desired, failure: validationError},
lastFailure: validationError,
},
commands: [],
};
}
if (plan.action === 'noop') {
return {snapshot: {...snapshot, screen: {...snapshot.screen, desired: plan.desired}}, commands: []};
}
if (!isConnected(snapshot)) {
return {snapshot: {...snapshot, screen: {...snapshot.screen, desired: plan.desired}}, commands: []};
}
const allocated = allocateOperation({...snapshot, screen: {...snapshot.screen, desired: plan.desired}});
const command =
plan.action === 'republish'
? ({type: 'screen.publish', operationId: allocated.operationId, options: plan.desired} as const)
: ({type: 'screen.updateEncoding', operationId: allocated.operationId, options} as const);
return {
snapshot: {
...allocated.snapshot,
screen: {...allocated.snapshot.screen, status: 'publishing', operationId: allocated.operationId},
},
commands: [command],
};
}
export function transitionScreen(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2ScreenEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionScreen snapshot must not be null');
assert.ok(event != null, 'transitionScreen event must not be null');
assert.equal(typeof event.type, 'string', 'screen event type must be a string');
assert.ok(event.type.startsWith('screen.'), 'screen reducer received unrelated event');
switch (event.type) {
case 'screen.publishRequested':
return beginScreenPublish({...snapshot, screen: {...snapshot.screen, desired: event.options}});
case 'screen.publishSucceeded':
return {
snapshot: {...snapshot, screen: applyMediaSuccess(snapshot.screen, event.operationId)},
commands: [],
};
case 'screen.publishFailed':
return failScreen(snapshot, event.operationId, event.error);
case 'screen.updateEncodingRequested':
return beginScreenEncodingUpdate(snapshot, event.options);
case 'screen.updateEncodingSucceeded':
return {
snapshot: {...snapshot, screen: applyMediaSuccess(snapshot.screen, event.operationId)},
commands: [],
};
case 'screen.updateEncodingFailed':
return failScreen(snapshot, event.operationId, event.error);
case 'screen.unpublishRequested':
return beginUnpublish(snapshot, 'screen', 'screen.unpublish');
case 'screen.unpublishSucceeded':
return {
snapshot: {...snapshot, screen: completeUnpublish(snapshot.screen, event.operationId)},
commands: [],
};
case 'screen.unpublishFailed':
return failUnpublish(snapshot, event.operationId, event.error, 'screen');
}
}
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2Error, VoiceEngineV2OperationId, VoiceEngineV2ScreenAudioOptions} from '../../protocol/types';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {
allocateOperation,
beginUnpublish,
failUnpublish,
invalidArgument,
isConnected,
unsupportedCapability,
} from './_helpers';
import {applyMediaFailure, applyMediaSuccess, completeUnpublish} from './_media';
type VoiceEngineV2ScreenAudioEvent = Extract<VoiceEngineV2Event, {type: `screenAudio.${string}`}>;
function sameScreenAudioOptions(
a: VoiceEngineV2ScreenAudioOptions | null,
b: VoiceEngineV2ScreenAudioOptions | null,
): boolean {
assert.ok(a !== undefined, 'sameScreenAudioOptions a must not be undefined');
assert.ok(b !== undefined, 'sameScreenAudioOptions b must not be undefined');
return (
a?.sampleRate === b?.sampleRate &&
a?.numChannels === b?.numChannels &&
a?.route === b?.route &&
a?.captureId === b?.captureId &&
a?.tapId === b?.tapId
);
}
function validateScreenAudioPublish(
snapshot: VoiceEngineV2Snapshot,
desired: VoiceEngineV2ScreenAudioOptions,
): VoiceEngineV2Error | null {
assert.ok(snapshot != null, 'validateScreenAudioPublish snapshot must not be null');
assert.ok(desired != null, 'validateScreenAudioPublish desired must not be null');
if (desired.route !== 'native') return null;
if (!snapshot.capabilities.nativeAudioTaps) {
return unsupportedCapability('nativeAudioTaps');
}
if (!desired.captureId && !desired.tapId) {
return invalidArgument('Native screen audio routing requires a capture id or tap id', 'screenAudio');
}
return null;
}
export function beginScreenAudioPublish(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'beginScreenAudioPublish snapshot must not be null');
assert.ok(snapshot.screenAudio != null, 'beginScreenAudioPublish snapshot.screenAudio must not be null');
const desired = snapshot.screenAudio.desired;
if (!desired) return {snapshot, commands: []};
if (!snapshot.capabilities.screenAudio) {
const error = unsupportedCapability('screenAudio');
return {
snapshot: {
...snapshot,
screenAudio: {...snapshot.screenAudio, status: 'failed', failure: error},
lastFailure: error,
},
commands: [],
};
}
const validationError = validateScreenAudioPublish(snapshot, desired);
if (validationError) {
return {
snapshot: {
...snapshot,
screenAudio: {...snapshot.screenAudio, status: 'failed', failure: validationError},
lastFailure: validationError,
},
commands: [],
};
}
if (!isConnected(snapshot)) return {snapshot, commands: []};
if (snapshot.screenAudio.status === 'published' && sameScreenAudioOptions(snapshot.screenAudio.published, desired)) {
return {snapshot, commands: []};
}
const allocated = allocateOperation(snapshot);
return {
snapshot: {
...allocated.snapshot,
screenAudio: {
...allocated.snapshot.screenAudio,
status: 'publishing',
operationId: allocated.operationId,
failure: null,
},
},
commands: [{type: 'screenAudio.publish', operationId: allocated.operationId, options: desired}],
};
}
function failScreenAudio(
snapshot: VoiceEngineV2Snapshot,
operationId: VoiceEngineV2OperationId,
error: VoiceEngineV2Error,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'failScreenAudio snapshot must not be null');
assert.ok(Number.isInteger(operationId), 'failScreenAudio operationId must be an integer');
assert.ok(error != null, 'failScreenAudio error must not be null');
const state = snapshot.screenAudio;
return {
snapshot: {
...snapshot,
screenAudio: applyMediaFailure(state, operationId, error),
lastFailure: state.operationId === operationId ? error : snapshot.lastFailure,
},
commands: [],
};
}
export function transitionScreenAudio(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2ScreenAudioEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionScreenAudio snapshot must not be null');
assert.ok(event != null, 'transitionScreenAudio event must not be null');
assert.equal(typeof event.type, 'string', 'screenAudio event type must be a string');
assert.ok(event.type.startsWith('screenAudio.'), 'screenAudio reducer received unrelated event');
switch (event.type) {
case 'screenAudio.publishRequested':
return beginScreenAudioPublish({
...snapshot,
screenAudio: {...snapshot.screenAudio, desired: event.options},
});
case 'screenAudio.publishSucceeded':
return {
snapshot: {...snapshot, screenAudio: applyMediaSuccess(snapshot.screenAudio, event.operationId)},
commands: [],
};
case 'screenAudio.publishFailed':
return failScreenAudio(snapshot, event.operationId, event.error);
case 'screenAudio.unpublishRequested':
return beginUnpublish(snapshot, 'screenAudio', 'screenAudio.unpublish');
case 'screenAudio.unpublishSucceeded':
return {
snapshot: {...snapshot, screenAudio: completeUnpublish(snapshot.screenAudio, event.operationId)},
commands: [],
};
case 'screenAudio.unpublishFailed':
return failUnpublish(snapshot, event.operationId, event.error, 'screenAudio');
}
}
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2SourceLifecycleTransitionedEvent} from '../../protocol/events';
import type {VoiceEngineV2DiagnosticEntry} from '../../protocol/types';
import type {SourceFault, SourceLifecycleState} from '../../source_isolation/SourceLifecycleState';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {allocateOperation, appendDiagnostic, queueCommand} from './_helpers';
const SOURCE_LIFECYCLE_DIAGNOSTICS_CODE = 'sourceFailed';
type VoiceEngineV2SourceLifecyclesEvent = VoiceEngineV2SourceLifecycleTransitionedEvent;
function assertNeverLifecycleKind(kind: never): never {
assert.fail(`unhandled source lifecycle kind: ${JSON.stringify(kind)}`);
}
function rebuildSourceLifecycleState(event: VoiceEngineV2SourceLifecycleTransitionedEvent): SourceLifecycleState {
assert.ok(event != null, 'rebuildSourceLifecycleState event must not be null');
assert.equal(typeof event.since, 'bigint', 'sourceLifecycle.transitioned.since must be a bigint');
assert.ok(event.since >= 0n, 'sourceLifecycle.transitioned.since must be non-negative');
assert.ok(Number.isInteger(event.attempts), 'sourceLifecycle.transitioned.attempts must be integer');
assert.ok(event.attempts >= 0, 'sourceLifecycle.transitioned.attempts must be non-negative');
if (event.kind === 'active') {
return {kind: 'active', since: event.since};
}
if (event.kind === 'reconnecting') {
assert.ok(event.fault !== null, 'reconnecting transition requires a fault');
return {kind: 'reconnecting', since: event.since, attempts: event.attempts, lastFault: event.fault};
}
if (event.kind === 'failed') {
assert.ok(event.fault !== null, 'failed transition requires a fault');
return {kind: 'failed', since: event.since, finalFault: event.fault, totalAttempts: event.attempts};
}
return assertNeverLifecycleKind(event.kind);
}
export function transitionSourceLifecycles(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2SourceLifecyclesEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionSourceLifecycles snapshot must not be null');
assert.ok(event != null, 'transitionSourceLifecycles event must not be null');
assert.equal(typeof event.sourceId, 'string', 'sourceLifecycle.sourceId must be a string');
assert.ok(event.sourceId.length > 0, 'sourceLifecycle.sourceId must not be empty');
const nextState = rebuildSourceLifecycleState(event);
const base: VoiceEngineV2Snapshot = {
...snapshot,
sourceLifecycles: {...snapshot.sourceLifecycles, [event.sourceId]: nextState},
};
if (event.kind !== 'failed') return {snapshot: base, commands: []};
const fault: SourceFault | null = event.fault;
assert.ok(fault !== null, 'failed transition requires a fault');
const allocated = allocateOperation(base);
const diagnosticEntry: VoiceEngineV2DiagnosticEntry = {
id: `sourceLifecycle.failed.${event.sourceId}.${allocated.operationId}`,
atMs: event.atMs,
level: 'error',
code: SOURCE_LIFECYCLE_DIAGNOSTICS_CODE,
message: `source ${event.sourceId} failed with fault ${fault}`,
detail: {sourceId: event.sourceId, fault, attempts: event.attempts},
};
return queueCommand(
{...allocated.snapshot, diagnostics: appendDiagnostic(allocated.snapshot.diagnostics, diagnosticEntry)},
{type: 'diagnostics.log', operationId: allocated.operationId, entry: diagnosticEntry},
);
}
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {commandIfConnected} from './_helpers';
type VoiceEngineV2StatsEvent = Extract<
VoiceEngineV2Event,
{type: 'stats.collectRequested' | 'stats.collected' | 'stats.collectFailed'}
>;
export function transitionStats(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2StatsEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionStats snapshot must not be null');
assert.ok(event != null, 'transitionStats event must not be null');
assert.equal(typeof event.type, 'string', 'stats event type must be a string');
assert.ok(event.type.startsWith('stats.'), 'stats reducer received unrelated event');
switch (event.type) {
case 'stats.collectRequested':
return commandIfConnected(snapshot, 'stats', {type: 'stats.collect'});
case 'stats.collected':
if (snapshot.statsOperationId !== event.operationId) return {snapshot, commands: []};
return {
snapshot: {...snapshot, stats: event.stats, statsOperationId: null, statsFailure: null},
commands: [],
};
case 'stats.collectFailed':
if (snapshot.statsOperationId !== event.operationId) return {snapshot, commands: []};
return {
snapshot: {...snapshot, statsOperationId: null, statsFailure: event.error, lastFailure: event.error},
commands: [],
};
}
}
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import type {VoiceEngineV2Event} from '../../protocol/events';
import type {VoiceEngineV2Snapshot, VoiceEngineV2Transition} from '../state';
import {allocateOperation, appendDiagnostic, markOperation, queueCommand} from './_helpers';
type VoiceEngineV2UtilityPortsEvent = Extract<VoiceEngineV2Event, {type: `timer.${string}` | `diagnostics.${string}`}>;
export function transitionUtilityPorts(
snapshot: VoiceEngineV2Snapshot,
event: VoiceEngineV2UtilityPortsEvent,
): VoiceEngineV2Transition {
assert.ok(snapshot != null, 'transitionUtilityPorts snapshot must not be null');
assert.ok(event != null, 'transitionUtilityPorts event must not be null');
assert.equal(typeof event.type, 'string', 'utilityPorts event type must be a string');
assert.ok(
event.type.startsWith('timer.') || event.type.startsWith('diagnostics.'),
'utilityPorts reducer received unrelated event',
);
switch (event.type) {
case 'timer.scheduleRequested': {
const allocated = allocateOperation(snapshot);
return queueCommand(allocated.snapshot, {
type: 'timer.schedule',
operationId: allocated.operationId,
options: event.options,
});
}
case 'timer.cancelRequested': {
const allocated = allocateOperation(snapshot);
return queueCommand(allocated.snapshot, {
type: 'timer.cancel',
operationId: allocated.operationId,
timerId: event.timerId,
});
}
case 'timer.fired':
return {snapshot, commands: []};
case 'diagnostics.logRequested': {
const allocated = allocateOperation(snapshot);
return queueCommand(
{...allocated.snapshot, diagnostics: appendDiagnostic(allocated.snapshot.diagnostics, event.entry)},
{type: 'diagnostics.log', operationId: allocated.operationId, entry: event.entry},
);
}
case 'diagnostics.logged':
return {
snapshot: {
...(event.operationId != null ? markOperation(snapshot, event.operationId, 'succeeded') : snapshot),
diagnostics: appendDiagnostic(snapshot.diagnostics, event.entry),
},
commands: [],
};
}
}
@@ -0,0 +1,263 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {
hasVoiceEngineV2NativeNvencEncoder,
hasVoiceEngineV2ZeroCopyNativeInput,
summarizeVoiceEngineV2Stats,
type VoiceEngineV2StatsNetworkSummary,
type VoiceEngineV2StatsSummary,
} from '../policies';
import type {
VoiceEngineV2AudioInputDevice,
VoiceEngineV2AudioOutputDevice,
VoiceEngineV2CameraDevice,
VoiceEngineV2Capabilities,
VoiceEngineV2ConnectionModel,
VoiceEngineV2DeviceInventory,
VoiceEngineV2DiagnosticEntry,
VoiceEngineV2E2eeState,
VoiceEngineV2Error,
VoiceEngineV2HardwareEncoderCapabilities,
VoiceEngineV2InboundVideoTrack,
VoiceEngineV2LocalStreamSource,
VoiceEngineV2MediaModel,
VoiceEngineV2Model,
VoiceEngineV2Participant,
VoiceEngineV2PermissionResult,
VoiceEngineV2Stats,
VoiceEngineV2StreamNegotiationProjection,
VoiceEngineV2Track,
VoiceEngineV2WatchedStream,
} from '../protocol/types';
import type {SourceLifecycleState} from '../source_isolation/SourceLifecycleState';
import type {VoiceEngineV2Snapshot} from './state';
export interface VoiceEngineV2ParticipantProjection {
participants: Array<VoiceEngineV2Participant>;
tracks: Array<VoiceEngineV2Track>;
inboundVideoTracks: Array<VoiceEngineV2InboundVideoTrack>;
}
export interface VoiceEngineV2DeviceProjection {
devices: VoiceEngineV2DeviceInventory;
permissions: Record<string, VoiceEngineV2PermissionResult>;
selectedAudioInput: VoiceEngineV2AudioInputDevice | null;
selectedAudioOutput: VoiceEngineV2AudioOutputDevice | null;
selectedCamera: VoiceEngineV2CameraDevice | null;
}
export interface VoiceEngineV2StatsProjection {
stats: VoiceEngineV2Stats | null;
summary: VoiceEngineV2StatsSummary | null;
failure: VoiceEngineV2Error | null;
}
export interface VoiceEngineV2StatsPresentationNetworkSummary extends VoiceEngineV2StatsNetworkSummary {
droppedVideoFrameCallbacks?: number;
}
export interface VoiceEngineV2StatsPresentationProjection extends VoiceEngineV2StatsSummary {
network: VoiceEngineV2StatsPresentationNetworkSummary;
}
export interface VoiceEngineV2CapabilitiesProjection {
capabilities: VoiceEngineV2Capabilities;
hardwareEncoderCapabilities: VoiceEngineV2HardwareEncoderCapabilities | null;
hardwareEncoderFailure: VoiceEngineV2Error | null;
hasZeroCopyNativeInput: boolean;
hasNativeNvencH264: boolean;
hasNativeNvencH265: boolean;
}
export interface VoiceEngineV2DiagnosticsProjection {
entries: Array<VoiceEngineV2DiagnosticEntry>;
lastFailure: VoiceEngineV2Error | null;
}
export function selectVoiceEngineV2E2eeProjection(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2E2eeState {
return {
status: snapshot.e2ee.status,
keyId: snapshot.e2ee.keyId,
failure: snapshot.e2ee.failure,
};
}
export function selectVoiceEngineV2ConnectionProjection(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2ConnectionModel {
const status = snapshot.connection.status;
return {
status,
connected: status === 'connected',
connecting: status === 'connecting',
reconnecting: status === 'reconnecting',
failed: status === 'failed',
gateway: {
selfVoiceState: snapshot.gateway.selfVoiceState,
voiceServer: snapshot.gateway.voiceServer,
},
liveKit: {
connectionState: snapshot.liveKit.connectionState,
roomSid: snapshot.liveKit.roomSid,
roomName: snapshot.liveKit.roomName,
serverRegion: snapshot.liveKit.serverRegion,
},
};
}
export function selectVoiceEngineV2MediaProjection(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2MediaModel {
return {
microphone: snapshot.microphone.status,
camera: snapshot.camera.status,
screen: snapshot.screen.status,
screenAudio: snapshot.screenAudio.status,
audio: snapshot.audioControls,
effectiveMicrophoneEnabled: snapshot.microphone.enabled,
localSpeakingOverride: snapshot.microphone.localSpeakingOverride,
e2ee: selectVoiceEngineV2E2eeProjection(snapshot),
screenCaptureId: snapshot.screen.published?.captureId ?? snapshot.screen.desired?.captureId ?? null,
};
}
export function selectVoiceEngineV2ParticipantProjection(
snapshot: VoiceEngineV2Snapshot,
): VoiceEngineV2ParticipantProjection {
return {
participants: Object.values(snapshot.room.participants),
tracks: Object.values(snapshot.room.tracks),
inboundVideoTracks: Object.values(snapshot.inboundVideo.tracks),
};
}
export function selectVoiceEngineV2WatchedStreams(snapshot: VoiceEngineV2Snapshot): Array<VoiceEngineV2WatchedStream> {
return Object.values(snapshot.watchedStreams);
}
export function selectVoiceEngineV2StreamNegotiation(
snapshot: VoiceEngineV2Snapshot,
source: VoiceEngineV2LocalStreamSource,
): VoiceEngineV2StreamNegotiationProjection | null {
const stream = snapshot.codecNegotiation.streams[source];
if (!stream) return null;
const media = source === 'camera' ? snapshot.camera : snapshot.screen;
const publishedCodec = media.status === 'published' ? (media.published?.codec ?? null) : null;
const renegotiating =
media.status === 'publishing' || (publishedCodec !== null && publishedCodec !== stream.negotiatedCodec);
return {
source,
streamIdentity: stream.streamIdentity,
negotiatedCodec: stream.negotiatedCodec,
preferredCodec: stream.preferredCodec,
constrainedBy: stream.constrainedBy,
renegotiating,
viewerCount: Object.keys(stream.viewers).length,
};
}
export function selectVoiceEngineV2DeviceProjection(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2DeviceProjection {
const {inventory} = snapshot.devices;
return {
devices: inventory,
permissions: snapshot.permissions.results,
selectedAudioInput:
inventory.audioInputs.find((device) => device.deviceId === inventory.selectedAudioInputId) ?? null,
selectedAudioOutput:
inventory.audioOutputs.find((device) => device.deviceId === inventory.selectedAudioOutputId) ?? null,
selectedCamera: inventory.cameras.find((device) => device.deviceId === inventory.selectedCameraId) ?? null,
};
}
export function selectVoiceEngineV2StatsSummary(stats: VoiceEngineV2Stats | null): VoiceEngineV2StatsSummary | null {
return stats ? summarizeVoiceEngineV2Stats(stats) : null;
}
export function selectVoiceEngineV2StatsPresentationProjection(
stats: VoiceEngineV2Stats | null,
): VoiceEngineV2StatsPresentationProjection | null {
const summary = selectVoiceEngineV2StatsSummary(stats);
if (!stats || !summary) return null;
const droppedVideoFrameCallbacks = stats.droppedVideoFrameCallbacks ?? stats.droppedNativeVideoFrames;
if (droppedVideoFrameCallbacks == null) return summary;
return {
...summary,
network: {
...summary.network,
droppedVideoFrameCallbacks,
},
};
}
export function selectVoiceEngineV2StatsProjection(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2StatsProjection {
return {
stats: snapshot.stats,
summary: selectVoiceEngineV2StatsSummary(snapshot.stats),
failure: snapshot.statsFailure,
};
}
export function selectVoiceEngineV2CapabilitiesProjection(
snapshot: VoiceEngineV2Snapshot,
): VoiceEngineV2CapabilitiesProjection {
const hardwareEncoderCapabilities = snapshot.hardwareEncoder.capabilities;
return {
capabilities: snapshot.capabilities,
hardwareEncoderCapabilities,
hardwareEncoderFailure: snapshot.hardwareEncoder.failure,
hasZeroCopyNativeInput: hasVoiceEngineV2ZeroCopyNativeInput(hardwareEncoderCapabilities),
hasNativeNvencH264: hasVoiceEngineV2NativeNvencEncoder(hardwareEncoderCapabilities, 'h264'),
hasNativeNvencH265: hasVoiceEngineV2NativeNvencEncoder(hardwareEncoderCapabilities, 'h265'),
};
}
export function selectVoiceEngineV2DiagnosticsProjection(
snapshot: VoiceEngineV2Snapshot,
): VoiceEngineV2DiagnosticsProjection {
return {
entries: snapshot.diagnostics,
lastFailure: snapshot.lastFailure,
};
}
export function selectVoiceEngineV2SourceLifecycle(
snapshot: VoiceEngineV2Snapshot,
sourceId: string,
): SourceLifecycleState | null {
if (typeof sourceId !== 'string') throw new TypeError('sourceId must be a string');
if (sourceId.length === 0) throw new RangeError('sourceId must not be empty');
return snapshot.sourceLifecycles[sourceId] ?? null;
}
export function selectVoiceEngineV2FailedSourceIds(snapshot: VoiceEngineV2Snapshot): ReadonlyArray<string> {
if (snapshot === null || typeof snapshot !== 'object') throw new TypeError('snapshot must be an object');
const lifecycles = snapshot.sourceLifecycles;
if (lifecycles === null || typeof lifecycles !== 'object') throw new TypeError('sourceLifecycles must be a record');
const result: Array<string> = [];
for (const sourceId of Object.keys(lifecycles).sort()) {
const state = lifecycles[sourceId];
if (state && state.kind === 'failed') result.push(sourceId);
}
return result;
}
export function selectVoiceEngineV2Model(snapshot: VoiceEngineV2Snapshot): VoiceEngineV2Model {
const status = snapshot.connection.status;
const participants = selectVoiceEngineV2ParticipantProjection(snapshot);
const devices = selectVoiceEngineV2DeviceProjection(snapshot);
return {
connection: selectVoiceEngineV2ConnectionProjection(snapshot),
media: selectVoiceEngineV2MediaProjection(snapshot),
canPublishMedia: status === 'connected',
hasActiveLocalMedia:
snapshot.microphone.status === 'published' ||
snapshot.camera.status === 'published' ||
snapshot.screen.status === 'published' ||
snapshot.screenAudio.status === 'published',
participants: participants.participants,
tracks: participants.tracks,
watchedStreams: selectVoiceEngineV2WatchedStreams(snapshot),
inboundVideoTracks: participants.inboundVideoTracks,
devices: devices.devices,
permissions: devices.permissions,
stats: snapshot.stats,
diagnostics: snapshot.diagnostics,
tearingDown: snapshot.lifecycle.tearingDown,
};
}

Some files were not shown because too many files have changed in this diff Show More