feat: Add experimental rewrite of go2rtc live provider (MSE/WebRTC/MP4/MJPEG) (#2580)

- Closes #2556
- Closes #2450

**Key intended features:**
 - go2rtc compatible
- 100% test coverage to significantly improve ability to test, maintain
and work around browser weirdnesses (e.g. Safari).
 - Written from the ground up in the style of the rest of the project.

**To use:**
 - Change `live_provider` from `go2rtc` to `go2rtc-experimental`.
This commit is contained in:
Dermot Duffy
2026-07-14 14:22:41 -07:00
committed by GitHub
parent 5662e48c22
commit c02c692f68
125 changed files with 9608 additions and 807 deletions
+18 -14
View File
@@ -99,7 +99,7 @@ describe('hasAudio', () => {
createMockReceiver('video'),
createMockReceiver('audio', false),
]);
expect(hasAudio(createMockVideo(), pc, '')).toBe(true);
expect(hasAudio(createMockVideo(), { pc })).toBe(true);
});
it('should not detect audio when audio receiver is muted', () => {
@@ -107,19 +107,19 @@ describe('hasAudio', () => {
createMockReceiver('video'),
createMockReceiver('audio', true),
]);
expect(hasAudio(createMockVideo(), pc, '')).toBe(false);
expect(hasAudio(createMockVideo(), { pc })).toBe(false);
});
it('should not detect audio when only video receivers exist', () => {
const pc = createMockPeerConnection([createMockReceiver('video')]);
expect(hasAudio(createMockVideo(), pc, '')).toBe(false);
expect(hasAudio(createMockVideo(), { pc })).toBe(false);
});
it('should fall back to mayHaveAudio when no receivers yet', () => {
const pc = createMockPeerConnection([]);
// Empty receivers means connection not established, falls back to mayHaveAudio
// With no properties set on video, mayHaveAudio returns true (generous default)
expect(hasAudio(createMockVideo(), pc, '')).toBe(true);
expect(hasAudio(createMockVideo(), { pc })).toBe(true);
});
it('should ignore receivers when connection is not established', () => {
@@ -127,47 +127,51 @@ describe('hasAudio', () => {
// Stale connection with muted receiver should fall through to
// mayHaveAudio (generous default returns true)
expect(hasAudio(createMockVideo(), pc, '')).toBe(true);
expect(hasAudio(createMockVideo(), { pc })).toBe(true);
});
it('should fall through to mseCodecs when connection is not established', () => {
const pc = createMockPeerConnection([createMockReceiver('audio', true)], 'new');
// Stale WebRTC connection but MSE has audio codecs
expect(hasAudio(createMockVideo(), pc, 'avc1.640029,flac')).toBe(true);
expect(hasAudio(createMockVideo(), { pc, mseCodecs: 'avc1.640029,flac' })).toBe(
true,
);
});
});
describe('MSE codec detection', () => {
it('should detect audio when mseCodecs contains mp4a', () => {
expect(hasAudio(createMockVideo(), null, 'avc1.640029,mp4a.40.2')).toBe(true);
expect(hasAudio(createMockVideo(), { mseCodecs: 'avc1.640029,mp4a.40.2' })).toBe(
true,
);
});
it('should detect audio when mseCodecs contains opus', () => {
expect(hasAudio(createMockVideo(), null, 'avc1.640029,opus')).toBe(true);
expect(hasAudio(createMockVideo(), { mseCodecs: 'avc1.640029,opus' })).toBe(true);
});
it('should detect audio when mseCodecs contains flac', () => {
expect(hasAudio(createMockVideo(), null, 'avc1.640029,flac')).toBe(true);
expect(hasAudio(createMockVideo(), { mseCodecs: 'avc1.640029,flac' })).toBe(true);
});
it('should not detect audio when mseCodecs contains only video codecs', () => {
expect(hasAudio(createMockVideo(), null, 'avc1.640029,hvc1.1.6.L153.B0')).toBe(
false,
);
expect(
hasAudio(createMockVideo(), { mseCodecs: 'avc1.640029,hvc1.1.6.L153.B0' }),
).toBe(false);
});
});
describe('fallback to mayHaveAudio', () => {
it('should fall back to mayHaveAudio when no SDP or mseCodecs', () => {
// With no properties set, mayHaveAudio returns true (generous default)
expect(hasAudio(createMockVideo(), null, '')).toBe(true);
expect(hasAudio(createMockVideo())).toBe(true);
});
it('should use mayHaveAudio when mozHasAudio is false', () => {
const video = createMockVideo();
video.mozHasAudio = false;
expect(hasAudio(video, null, '')).toBe(false);
expect(hasAudio(video)).toBe(false);
});
});
});