diff --git a/src/handlers/ortc/utils.ts b/src/handlers/ortc/utils.ts index 33c98c67..0521ed14 100644 --- a/src/handlers/ortc/utils.ts +++ b/src/handlers/ortc/utils.ts @@ -75,7 +75,12 @@ export function applyForcedRtpExtensions( const uri = extenCap.uri as RtpHeaderExtensionUri; const enabled: boolean | undefined = forcedRtpExtensions[uri]; - if (enabled === true) { + // MID is mandatory when BUNDLE is used and cannot be disabled by this API. + // Some browsers may report it as stopped after negotiation, but reject the + // entire list if that direction is submitted again. + if (uri === 'urn:ietf:params:rtp-hdrext:sdes:mid') { + extenCap.direction = 'sendrecv'; + } else if (enabled === true) { extenCap.direction = 'sendrecv'; } else if (enabled === false) { extenCap.direction = 'stopped'; diff --git a/src/test/test.ts b/src/test/test.ts index 6b508dee..94b57656 100644 --- a/src/test/test.ts +++ b/src/test/test.ts @@ -7,6 +7,7 @@ import { FakeMediaStreamTrack } from 'fake-mediastreamtrack'; import * as mediasoupClient from '../index'; import { UnsupportedError, InvalidStateError } from '../errors'; import * as utils from '../utils'; +import { applyForcedRtpExtensions } from '../handlers/ortc/utils'; import type { RtpCapabilities } from '../RtpParameters'; import { FakeHandler } from '../handlers/FakeHandler'; import * as fakeParameters from './fakeParameters'; @@ -1762,3 +1763,40 @@ describe('detectDevice() and detectDeviceAsync() assign proper handler based on ); } }); + +describe('applyForcedRtpExtensions()', () => { + test('keeps the mandatory MID extension enabled when forcing another extension', () => { + const midUri = 'urn:ietf:params:rtp-hdrext:sdes:mid'; + const absCaptureTimeUri = + 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time'; + const capabilities: RTCRtpHeaderExtensionCapability[] = [ + { uri: midUri, direction: 'stopped' }, + { uri: absCaptureTimeUri, direction: 'recvonly' }, + ]; + let submittedCapabilities: RTCRtpHeaderExtensionCapability[] | undefined; + const transceiver = { + getHeaderExtensionsToNegotiate: () => capabilities, + setHeaderExtensionsToNegotiate: ( + nextCapabilities: RTCRtpHeaderExtensionCapability[] + ) => { + const midExtension = nextCapabilities.find(({ uri }) => uri === midUri); + + if (midExtension?.direction !== 'sendrecv') { + throw new Error('Attempted to stop a mandatory extension.'); + } + + submittedCapabilities = nextCapabilities; + }, + } as unknown as RTCRtpTransceiver; + + expect( + applyForcedRtpExtensions(transceiver, { + [absCaptureTimeUri]: true, + }) + ).toBe(true); + expect(submittedCapabilities).toEqual([ + { uri: midUri, direction: 'sendrecv' }, + { uri: absCaptureTimeUri, direction: 'sendrecv' }, + ]); + }); +});