refactor(voice): remove screen share codec renegotiation (#2417)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-03 04:46:11 +02:00
1 parent f5f60c66e7
commit c97bc53342
80 files changed
+1360 -4726

No files matched your search

@@ -25,7 +25,6 @@ export {
selectVoiceEngineV2StatsPresentationProjection,
selectVoiceEngineV2StatsProjection,
selectVoiceEngineV2StatsSummary,
selectVoiceEngineV2StreamNegotiation,
selectVoiceEngineV2WatchedStreams,
} from './selectors';
export type {
@@ -6,7 +6,6 @@ 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';
@@ -137,13 +136,6 @@ export function dispatchSessionEvent(
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':
@@ -105,7 +105,7 @@ function failCamera(
};
}
export function beginCameraEncodingUpdate(
function beginCameraEncodingUpdate(
snapshot: VoiceEngineV2Snapshot,
options: VoiceEngineV2CameraEncodingOptions,
): VoiceEngineV2Transition {
@@ -1,166 +0,0 @@
// 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');
});
});
@@ -1,187 +0,0 @@
// 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,
);
}
}
@@ -130,7 +130,7 @@ function failScreen(
};
}
export function beginScreenEncodingUpdate(
function beginScreenEncodingUpdate(
snapshot: VoiceEngineV2Snapshot,
options: VoiceEngineV2ScreenEncodingOptions,
): VoiceEngineV2Transition {
@@ -19,13 +19,11 @@ import type {
VoiceEngineV2Error,
VoiceEngineV2HardwareEncoderCapabilities,
VoiceEngineV2InboundVideoTrack,
VoiceEngineV2LocalStreamSource,
VoiceEngineV2MediaModel,
VoiceEngineV2Model,
VoiceEngineV2Participant,
VoiceEngineV2PermissionResult,
VoiceEngineV2Stats,
VoiceEngineV2StreamNegotiationProjection,
VoiceEngineV2Track,
VoiceEngineV2WatchedStream,
} from '../protocol/types';
@@ -131,27 +129,6 @@ export function selectVoiceEngineV2WatchedStreams(snapshot: VoiceEngineV2Snapsho
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 {
@@ -9,7 +9,6 @@ import type {
VoiceEngineV2AudioControls,
VoiceEngineV2CameraOptions,
VoiceEngineV2Capabilities,
VoiceEngineV2CodecNegotiationState,
VoiceEngineV2ConnectionStatus,
VoiceEngineV2ConnectOptions,
VoiceEngineV2DeviceInventory,
@@ -170,7 +169,6 @@ export interface VoiceEngineV2Snapshot {
participantVolumes: Record<string, number>;
remoteTrackSubscriptions: Record<string, VoiceEngineV2RemoteTrackSubscriptionOptions>;
watchedStreams: Record<string, VoiceEngineV2WatchedStream>;
codecNegotiation: VoiceEngineV2CodecNegotiationState;
stats: VoiceEngineV2Stats | null;
statsOperationId: VoiceEngineV2OperationId | null;
statsFailure: VoiceEngineV2Error | null;
@@ -338,12 +336,6 @@ export function createVoiceEngineV2InitialSnapshot(
participantVolumes: {},
remoteTrackSubscriptions: {},
watchedStreams: {},
codecNegotiation: {
overrides: {},
localSupportedVideoCodecs: [],
remoteSupportedVideoCodecs: {},
streams: {},
},
stats: null,
statsOperationId: null,
statsFailure: null,