mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
refactor(voice): remove screen share codec renegotiation (#2417)
This commit is contained in:
1 parent
f5f60c66e7
commit
c97bc53342
80 files changed
+1360
-4726
No files matched your search
@@ -1,104 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {describe, expect, it} from 'vitest';
|
||||
import {
|
||||
isMoreEfficientVoiceEngineV2VideoCodec,
|
||||
maxDecodableVoiceEngineV2VideoCodec,
|
||||
planVoiceEngineV2NegotiatedVideoCodec,
|
||||
VOICE_ENGINE_V2_VIDEO_CODEC_PREFERENCE,
|
||||
voiceEngineV2VideoCodecRank,
|
||||
worseVoiceEngineV2VideoCodec,
|
||||
} from './codecNegotiation';
|
||||
|
||||
describe('voice engine v2 codec negotiation policy', () => {
|
||||
it('orders codecs most-efficient to most-compatible', () => {
|
||||
expect(VOICE_ENGINE_V2_VIDEO_CODEC_PREFERENCE).toEqual(['av1', 'h265', 'vp9', 'h264', 'vp8']);
|
||||
expect(voiceEngineV2VideoCodecRank('av1')).toBeLessThan(voiceEngineV2VideoCodecRank('h265'));
|
||||
expect(voiceEngineV2VideoCodecRank('h265')).toBeLessThan(voiceEngineV2VideoCodecRank('vp9'));
|
||||
expect(voiceEngineV2VideoCodecRank('vp9')).toBeLessThan(voiceEngineV2VideoCodecRank('h264'));
|
||||
expect(voiceEngineV2VideoCodecRank('h264')).toBeLessThan(voiceEngineV2VideoCodecRank('vp8'));
|
||||
});
|
||||
|
||||
it('ranks unknown and empty codecs past the floor', () => {
|
||||
expect(voiceEngineV2VideoCodecRank('')).toBe(VOICE_ENGINE_V2_VIDEO_CODEC_PREFERENCE.length);
|
||||
expect(voiceEngineV2VideoCodecRank(undefined)).toBe(VOICE_ENGINE_V2_VIDEO_CODEC_PREFERENCE.length);
|
||||
});
|
||||
|
||||
it('compares efficiency and compatibility', () => {
|
||||
expect(isMoreEfficientVoiceEngineV2VideoCodec('av1', 'h264')).toBe(true);
|
||||
expect(isMoreEfficientVoiceEngineV2VideoCodec('vp8', 'vp9')).toBe(false);
|
||||
expect(worseVoiceEngineV2VideoCodec('av1', 'h264')).toBe('h264');
|
||||
expect(worseVoiceEngineV2VideoCodec('vp8', 'vp9')).toBe('vp8');
|
||||
});
|
||||
|
||||
it('computes a viewer max-decodable codec from its supported set', () => {
|
||||
expect(maxDecodableVoiceEngineV2VideoCodec(['h264', 'vp8', 'vp9'])).toBe('vp9');
|
||||
expect(maxDecodableVoiceEngineV2VideoCodec(['vp8', 'av1', 'h264'])).toBe('av1');
|
||||
expect(maxDecodableVoiceEngineV2VideoCodec([])).toBeNull();
|
||||
expect(maxDecodableVoiceEngineV2VideoCodec(['', 'totally-unknown' as never])).toBeNull();
|
||||
});
|
||||
|
||||
it('uses the preferred codec when no viewer constrains it', () => {
|
||||
const plan = planVoiceEngineV2NegotiatedVideoCodec({
|
||||
preferred: 'av1',
|
||||
viewers: [
|
||||
{identity: 'alice', maxVideoCodec: 'av1'},
|
||||
{identity: 'bob', maxVideoCodec: 'av1'},
|
||||
],
|
||||
});
|
||||
expect(plan.codec).toBe('av1');
|
||||
expect(plan.reason).toBe('preferred');
|
||||
expect(plan.constrainedBy).toBeNull();
|
||||
});
|
||||
|
||||
it('clamps down to the worst current viewer when one cannot decode the preferred codec', () => {
|
||||
const plan = planVoiceEngineV2NegotiatedVideoCodec({
|
||||
preferred: 'av1',
|
||||
viewers: [
|
||||
{identity: 'alice', maxVideoCodec: 'av1'},
|
||||
{identity: 'bob', maxVideoCodec: 'h264'},
|
||||
],
|
||||
});
|
||||
expect(plan.codec).toBe('h264');
|
||||
expect(plan.reason).toBe('clampedToViewer');
|
||||
expect(plan.constrainedBy).toBe('bob');
|
||||
});
|
||||
|
||||
it('is order-independent and clamps to the single worst viewer', () => {
|
||||
const viewers = [
|
||||
{identity: 'alice', maxVideoCodec: 'vp9' as const},
|
||||
{identity: 'carol', maxVideoCodec: 'h264' as const},
|
||||
{identity: 'bob', maxVideoCodec: 'h265' as const},
|
||||
];
|
||||
const forward = planVoiceEngineV2NegotiatedVideoCodec({preferred: 'av1', viewers});
|
||||
const reversed = planVoiceEngineV2NegotiatedVideoCodec({preferred: 'av1', viewers: [...viewers].reverse()});
|
||||
expect(forward.codec).toBe('h264');
|
||||
expect(forward.constrainedBy).toBe('carol');
|
||||
expect(reversed.codec).toBe('h264');
|
||||
expect(reversed.constrainedBy).toBe('carol');
|
||||
});
|
||||
|
||||
it('does not constrain on viewers with unknown capability (optimistic until they report)', () => {
|
||||
const plan = planVoiceEngineV2NegotiatedVideoCodec({
|
||||
preferred: 'av1',
|
||||
viewers: [
|
||||
{identity: 'alice', maxVideoCodec: null},
|
||||
{identity: 'bob', maxVideoCodec: 'vp9'},
|
||||
],
|
||||
});
|
||||
expect(plan.codec).toBe('vp9');
|
||||
expect(plan.constrainedBy).toBe('bob');
|
||||
});
|
||||
|
||||
it('never upgrades beyond the preferred codec even if every viewer supports better', () => {
|
||||
const plan = planVoiceEngineV2NegotiatedVideoCodec({
|
||||
preferred: 'h264',
|
||||
viewers: [
|
||||
{identity: 'alice', maxVideoCodec: 'av1'},
|
||||
{identity: 'bob', maxVideoCodec: 'av1'},
|
||||
],
|
||||
});
|
||||
expect(plan.codec).toBe('h264');
|
||||
expect(plan.reason).toBe('preferred');
|
||||
});
|
||||
});
|
||||
@@ -1,101 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
import type {VoiceEngineV2VideoCodec} from '../protocol/types';
|
||||
|
||||
export const VOICE_ENGINE_V2_VIDEO_CODEC_PREFERENCE: ReadonlyArray<VoiceEngineV2VideoCodec> = [
|
||||
'av1',
|
||||
'h265',
|
||||
'vp9',
|
||||
'h264',
|
||||
'vp8',
|
||||
];
|
||||
|
||||
export const VOICE_ENGINE_V2_VIDEO_CODEC_FLOOR: VoiceEngineV2VideoCodec = 'vp8';
|
||||
|
||||
const VIDEO_CODEC_RANK: ReadonlyMap<VoiceEngineV2VideoCodec, number> = new Map(
|
||||
VOICE_ENGINE_V2_VIDEO_CODEC_PREFERENCE.map((codec, index) => [codec, index]),
|
||||
);
|
||||
|
||||
function isKnownVoiceEngineV2VideoCodec(codec: VoiceEngineV2VideoCodec | null | undefined): boolean {
|
||||
if (codec == null) return false;
|
||||
if (codec === '') return false;
|
||||
return VIDEO_CODEC_RANK.has(codec);
|
||||
}
|
||||
|
||||
export function voiceEngineV2VideoCodecRank(codec: VoiceEngineV2VideoCodec | null | undefined): number {
|
||||
if (!isKnownVoiceEngineV2VideoCodec(codec)) return VOICE_ENGINE_V2_VIDEO_CODEC_PREFERENCE.length;
|
||||
const rank = VIDEO_CODEC_RANK.get(codec as VoiceEngineV2VideoCodec);
|
||||
assert.ok(rank !== undefined, 'voiceEngineV2VideoCodecRank known codec must have a rank');
|
||||
return rank;
|
||||
}
|
||||
|
||||
export function isMoreEfficientVoiceEngineV2VideoCodec(
|
||||
candidate: VoiceEngineV2VideoCodec,
|
||||
reference: VoiceEngineV2VideoCodec,
|
||||
): boolean {
|
||||
return voiceEngineV2VideoCodecRank(candidate) < voiceEngineV2VideoCodecRank(reference);
|
||||
}
|
||||
|
||||
export function worseVoiceEngineV2VideoCodec(
|
||||
a: VoiceEngineV2VideoCodec,
|
||||
b: VoiceEngineV2VideoCodec,
|
||||
): VoiceEngineV2VideoCodec {
|
||||
return voiceEngineV2VideoCodecRank(b) > voiceEngineV2VideoCodecRank(a) ? b : a;
|
||||
}
|
||||
|
||||
export function maxDecodableVoiceEngineV2VideoCodec(
|
||||
supported: ReadonlyArray<VoiceEngineV2VideoCodec>,
|
||||
): VoiceEngineV2VideoCodec | null {
|
||||
assert.ok(Array.isArray(supported), 'maxDecodableVoiceEngineV2VideoCodec supported must be an array');
|
||||
let best: VoiceEngineV2VideoCodec | null = null;
|
||||
for (const codec of supported) {
|
||||
if (!isKnownVoiceEngineV2VideoCodec(codec)) continue;
|
||||
if (best === null || isMoreEfficientVoiceEngineV2VideoCodec(codec, best)) {
|
||||
best = codec;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
export interface VoiceEngineV2CodecViewer {
|
||||
identity: string;
|
||||
maxVideoCodec: VoiceEngineV2VideoCodec | null;
|
||||
}
|
||||
|
||||
export type VoiceEngineV2NegotiatedCodecReason = 'preferred' | 'clampedToViewer';
|
||||
|
||||
export interface VoiceEngineV2NegotiatedCodecPlan {
|
||||
codec: VoiceEngineV2VideoCodec;
|
||||
reason: VoiceEngineV2NegotiatedCodecReason;
|
||||
constrainedBy: string | null;
|
||||
}
|
||||
|
||||
interface VoiceEngineV2NegotiatedCodecInput {
|
||||
preferred: VoiceEngineV2VideoCodec;
|
||||
viewers: ReadonlyArray<VoiceEngineV2CodecViewer>;
|
||||
}
|
||||
|
||||
export function planVoiceEngineV2NegotiatedVideoCodec(
|
||||
input: VoiceEngineV2NegotiatedCodecInput,
|
||||
): VoiceEngineV2NegotiatedCodecPlan {
|
||||
assert.ok(input != null, 'planVoiceEngineV2NegotiatedVideoCodec input must not be null');
|
||||
assert.ok(Array.isArray(input.viewers), 'planVoiceEngineV2NegotiatedVideoCodec viewers must be an array');
|
||||
const preferred = isKnownVoiceEngineV2VideoCodec(input.preferred)
|
||||
? input.preferred
|
||||
: VOICE_ENGINE_V2_VIDEO_CODEC_FLOOR;
|
||||
let codec = preferred;
|
||||
let reason: VoiceEngineV2NegotiatedCodecReason = 'preferred';
|
||||
let constrainedBy: string | null = null;
|
||||
for (const viewer of input.viewers) {
|
||||
if (viewer.maxVideoCodec === null) continue;
|
||||
if (!isKnownVoiceEngineV2VideoCodec(viewer.maxVideoCodec)) continue;
|
||||
if (!isMoreEfficientVoiceEngineV2VideoCodec(codec, viewer.maxVideoCodec)) {
|
||||
continue;
|
||||
}
|
||||
codec = viewer.maxVideoCodec;
|
||||
reason = 'clampedToViewer';
|
||||
constrainedBy = viewer.identity;
|
||||
}
|
||||
return {codec, reason, constrainedBy};
|
||||
}
|
||||
@@ -7,20 +7,6 @@ export type {
|
||||
VoiceEngineV2CameraEncodingPlanReason,
|
||||
} from './cameraShare';
|
||||
export {applyVoiceEngineV2CameraEncodingOptions, planVoiceEngineV2CameraEncodingChange} from './cameraShare';
|
||||
export type {
|
||||
VoiceEngineV2CodecViewer,
|
||||
VoiceEngineV2NegotiatedCodecPlan,
|
||||
VoiceEngineV2NegotiatedCodecReason,
|
||||
} from './codecNegotiation';
|
||||
export {
|
||||
isMoreEfficientVoiceEngineV2VideoCodec,
|
||||
maxDecodableVoiceEngineV2VideoCodec,
|
||||
planVoiceEngineV2NegotiatedVideoCodec,
|
||||
VOICE_ENGINE_V2_VIDEO_CODEC_FLOOR,
|
||||
VOICE_ENGINE_V2_VIDEO_CODEC_PREFERENCE,
|
||||
voiceEngineV2VideoCodecRank,
|
||||
worseVoiceEngineV2VideoCodec,
|
||||
} from './codecNegotiation';
|
||||
export {
|
||||
hasVoiceEngineV2NativeHardwareEncoder,
|
||||
hasVoiceEngineV2NativeNvencEncoder,
|
||||
|
||||
Reference in new issue
Block a user