feat(voice): ship the screen share delivery rework to everyone (#2931)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-24 14:57:40 +02:00
1 parent 88038a1d5b
commit a0825e77c4
53 files changed
+171 -1806

No files matched your search

@@ -52,7 +52,6 @@ export interface InternalRoomOptions {
singlePeerConnection: boolean;
subscriberVideoCodecExclusions?: Array<VideoCodec>;
screenShareDelivery?: boolean;
h264HardwareProfiles?: ReadonlySet<string>;
dataStream?: RoomDataStreamOptions;
}
@@ -73,66 +73,34 @@ describe('applyVideoStartBitrate', () => {
}
it('adds a start bitrate to a non-SVC codec section', () => {
for (const screenShareDelivery of [false, true]) {
const media = videoMedia('camera-track', [
{payload: 96, config: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f'},
]);
expect(applyVideoStartBitrate(media, 'camera-track', 'H264', 1000, false, screenShareDelivery)).toBe(96);
expect(media.fmtp[0]?.config).toBe(
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f;x-google-start-bitrate=900',
);
}
});
it('caps camera start bitrates but not screen share start bitrates while screen share delivery is off', () => {
const camera = videoMedia('camera-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
applyVideoStartBitrate(camera, 'camera-track', 'H264', 3000);
expect(camera.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=1000');
const screen = videoMedia('screen-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
applyVideoStartBitrate(screen, 'screen-track', 'H264', 6000, true);
expect(screen.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=5400');
const media = videoMedia('camera-track', [
{payload: 96, config: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f'},
]);
expect(applyVideoStartBitrate(media, 'camera-track', 'H264', 1000, false)).toBe(96);
expect(media.fmtp[0]?.config).toBe(
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f;x-google-start-bitrate=900',
);
});
it('caps camera and screen share start bitrates at their own ceilings', () => {
const camera = videoMedia('camera-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
applyVideoStartBitrate(camera, 'camera-track', 'H264', 3000, false, true);
applyVideoStartBitrate(camera, 'camera-track', 'H264', 3000, false);
expect(camera.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=1000');
const screen = videoMedia('screen-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
applyVideoStartBitrate(screen, 'screen-track', 'H264', 6000, true, true);
applyVideoStartBitrate(screen, 'screen-track', 'H264', 6000, true);
expect(screen.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=1500');
});
it('leaves a small screen share start bitrate on the floor while screen share delivery is off', () => {
const screen = videoMedia('screen-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
applyVideoStartBitrate(screen, 'screen-track', 'H264', 300, true);
expect(screen.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=270');
});
it('keeps a screen share start bitrate above the frame dropper cliff', () => {
const screen = videoMedia('screen-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
applyVideoStartBitrate(screen, 'screen-track', 'H264', 300, true, true);
applyVideoStartBitrate(screen, 'screen-track', 'H264', 300, true);
expect(screen.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=600');
});
it('stamps only the lead payload type while screen share delivery is off', () => {
const media = multiPayloadScreenMedia();
expect(applyVideoStartBitrate(media, 'screen-track', 'H264', 6000, true)).toBe(116);
expect(media.fmtp.find((fmtp) => fmtp.payload === 116)?.config).toBe(
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f;x-google-start-bitrate=5400',
);
expect(media.fmtp.find((fmtp) => fmtp.payload === 102)?.config).toBe(
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f',
);
expect(media.fmtp.find((fmtp) => fmtp.payload === 108)?.config).toBe(
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',
);
});
it('stamps every payload type the codec is offered under, not only the lead one', () => {
const media = multiPayloadScreenMedia();
expect(applyVideoStartBitrate(media, 'screen-track', 'H264', 6000, true, true)).toBe(116);
expect(applyVideoStartBitrate(media, 'screen-track', 'H264', 6000, true)).toBe(116);
for (const fmtp of media.fmtp) {
expect(fmtp.config).toContain('x-google-start-bitrate=1500');
}
@@ -140,47 +108,35 @@ describe('applyVideoStartBitrate', () => {
});
it('only touches the fmtp line for the matching payload', () => {
for (const screenShareDelivery of [false, true]) {
const media = videoMedia(
'screen-track',
[
{payload: 96, config: 'profile-level-id=42e01f'},
{payload: 98, config: 'profile-id=0'},
],
[
{payload: 96, codec: 'H264'},
{payload: 98, codec: 'VP9'},
],
);
applyVideoStartBitrate(media, 'screen-track', 'VP9', 1500, true, screenShareDelivery);
expect(media.fmtp[0]?.config).toBe('profile-level-id=42e01f');
expect(media.fmtp[1]?.config).toBe('profile-id=0;x-google-start-bitrate=1350');
}
});
it('never appends a second start bitrate while screen share delivery is off', () => {
const media = videoMedia('camera-track', [
{payload: 96, config: 'profile-level-id=42e01f;x-google-start-bitrate=900'},
]);
applyVideoStartBitrate(media, 'camera-track', 'H264', 2000);
expect(media.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=900');
const media = videoMedia(
'screen-track',
[
{payload: 96, config: 'profile-level-id=42e01f'},
{payload: 98, config: 'profile-id=0'},
],
[
{payload: 96, codec: 'H264'},
{payload: 98, codec: 'VP9'},
],
);
applyVideoStartBitrate(media, 'screen-track', 'VP9', 1500, true);
expect(media.fmtp[0]?.config).toBe('profile-level-id=42e01f');
expect(media.fmtp[1]?.config).toBe('profile-id=0;x-google-start-bitrate=1350');
});
it('replaces a start bitrate an earlier offer wrote instead of keeping it', () => {
const media = videoMedia('camera-track', [
{payload: 96, config: 'profile-level-id=42e01f;x-google-start-bitrate=900'},
]);
applyVideoStartBitrate(media, 'camera-track', 'H264', 2000, false, true);
applyVideoStartBitrate(media, 'camera-track', 'H264', 2000, false);
expect(media.fmtp[0]?.config).toBe('profile-level-id=42e01f;x-google-start-bitrate=1000');
});
it('leaves other tracks and missing codecs alone', () => {
for (const screenShareDelivery of [false, true]) {
const media = videoMedia('camera-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
expect(applyVideoStartBitrate(media, 'other-track', 'H264', 2000, false, screenShareDelivery)).toBeUndefined();
expect(applyVideoStartBitrate(media, 'camera-track', 'AV1', 2000, false, screenShareDelivery)).toBe(0);
expect(media.fmtp[0]?.config).toBe('profile-level-id=42e01f');
}
const media = videoMedia('camera-track', [{payload: 96, config: 'profile-level-id=42e01f'}]);
expect(applyVideoStartBitrate(media, 'other-track', 'H264', 2000, false)).toBeUndefined();
expect(applyVideoStartBitrate(media, 'camera-track', 'AV1', 2000, false)).toBe(0);
expect(media.fmtp[0]?.config).toBe('profile-level-id=42e01f');
});
});
@@ -49,7 +49,6 @@ export function applyVideoStartBitrate(
codec: string,
maxbr: number,
isScreenShare = false,
screenShareDelivery = false,
): number | undefined {
if (!media.msid?.includes(cid)) {
return undefined;
@@ -65,25 +64,10 @@ export function applyVideoStartBitrate(
const calculatedStartBitrate = Math.round(maxbr * startBitrateMultiplier);
let startBitrate = Math.min(calculatedStartBitrate, maxStartBitrateKbps);
if (isScreenShare) {
startBitrate = screenShareDelivery
? Math.max(minScreenShareStartBitrateKbps, Math.min(calculatedStartBitrate, maxScreenShareStartBitrateKbps))
: calculatedStartBitrate;
}
if (!screenShareDelivery) {
const codecPayload = codecPayloads[0];
const fmtp = media.fmtp.find((entry) => entry.payload === codecPayload);
if (fmtp) {
if (!fmtp.config.includes(startBitrateParameter)) {
fmtp.config += `;${startBitrateParameter}=${startBitrate}`;
}
} else {
media.fmtp.push({
payload: codecPayload,
config: `${startBitrateParameter}=${startBitrate}`,
});
}
return codecPayload;
startBitrate = Math.max(
minScreenShareStartBitrateKbps,
Math.min(calculatedStartBitrate, maxScreenShareStartBitrateKbps),
);
}
for (const payload of codecPayloads) {
@@ -143,8 +127,6 @@ export default class PCTransport extends (EventEmitter as new () => TypedEmitter
excludedVideoDecoderMimeTypes: Set<string> = new Set();
private screenShareDelivery: boolean;
onOffer?: (offer: RTCSessionDescriptionInit, offerId: number) => void;
onIceCandidate?: (candidate: RTCIceCandidate) => void;
@@ -161,10 +143,9 @@ export default class PCTransport extends (EventEmitter as new () => TypedEmitter
onTrack?: (ev: RTCTrackEvent) => void;
constructor(config?: RTCConfiguration, loggerOptions: LoggerOptions = {}, screenShareDelivery: boolean = false) {
constructor(config?: RTCConfiguration, loggerOptions: LoggerOptions = {}) {
super();
this.loggerOptions = loggerOptions;
this.screenShareDelivery = screenShareDelivery;
this.log = getLogger(loggerOptions.loggerName ?? LoggerNames.PCTransport, () => this.logContext);
this.iceLog = getLogger(LoggerNames.ICE, () => this.logContext);
this.config = config;
@@ -412,7 +393,6 @@ export default class PCTransport extends (EventEmitter as new () => TypedEmitter
trackbr.codec,
trackbr.maxbr,
trackbr.isScreenShare,
this.screenShareDelivery,
);
if (codecPayload === undefined) {
return false;
@@ -471,10 +451,7 @@ export default class PCTransport extends (EventEmitter as new () => TypedEmitter
for (const transceiver of this.getTransceivers()) {
if (transceiver.receiver.track?.kind !== 'video') continue;
if ((transceiver as {stopped?: boolean}).stopped) continue;
const receives = this.screenShareDelivery
? transceiver.direction === 'recvonly'
: transceiver.direction === 'recvonly' || transceiver.direction === 'sendrecv';
if (!receives) continue;
if (transceiver.direction !== 'recvonly') continue;
if (typeof transceiver.setCodecPreferences !== 'function') continue;
try {
transceiver.setCodecPreferences(allowed);
@@ -98,7 +98,6 @@ export class PCTransportManager {
loggerOptions: LoggerOptions,
rtcConfig?: RTCConfiguration,
subscriberVideoCodecExclusions?: Array<VideoCodec>,
screenShareDelivery: boolean = false,
) {
this.loggerOptions = loggerOptions;
this.log = getLogger(loggerOptions.loggerName ?? LoggerNames.PCManager, () => this.logContext);
@@ -106,10 +105,10 @@ export class PCTransportManager {
this.isPublisherConnectionRequired = mode !== 'subscriber-primary';
this.isSubscriberConnectionRequired = mode === 'subscriber-primary';
this.publisher = new PCTransport(rtcConfig, loggerOptions, screenShareDelivery);
this.publisher = new PCTransport(rtcConfig, loggerOptions);
this._mode = mode;
if (mode !== 'publisher-only') {
this.subscriber = new PCTransport(rtcConfig, loggerOptions, screenShareDelivery);
this.subscriber = new PCTransport(rtcConfig, loggerOptions);
this.subscriber.onConnectionStateChange = this.updateState;
this.subscriber.onIceConnectionStateChange = this.updateState;
this.subscriber.onSignalingStatechange = this.updateState;
@@ -127,7 +126,7 @@ export class PCTransportManager {
};
}
const receivingTransport = screenShareDelivery ? (this.subscriber ?? this.publisher) : this.subscriber;
const receivingTransport = this.subscriber ?? this.publisher;
if (receivingTransport) {
for (const codec of subscriberVideoCodecExclusions ?? []) {
receivingTransport.excludedVideoDecoderMimeTypes.add(`video/${codec}`);
@@ -25,41 +25,20 @@ describe('selectPublisherCodecPreferences', () => {
const mainLine = 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f';
const highLine = 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f';
it('keeps Main and Baseline ahead of Constrained Baseline while screen share delivery is off', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const baseline = codec('video/H264', baselineLine);
const highProfile = codec('video/H264', highLine);
const rtx = codec('video/rtx');
const preferences = selectPublisherCodecPreferences('h264', [constrainedBaseline, rtx, baseline, highProfile]);
expect(preferences).toEqual([highProfile, baseline, constrainedBaseline, rtx]);
});
it('offers High first, then the one profile this server always registers, then the ones it registers nowhere', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const baseline = codec('video/H264', baselineLine);
const highProfile = codec('video/H264', highLine);
const rtx = codec('video/rtx');
const preferences = selectPublisherCodecPreferences(
'h264',
[constrainedBaseline, rtx, baseline, highProfile],
true,
);
const preferences = selectPublisherCodecPreferences('h264', [constrainedBaseline, rtx, baseline, highProfile]);
expect(preferences).toEqual([highProfile, constrainedBaseline, baseline, rtx]);
});
it('leads with Main and then Baseline while screen share delivery is off', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const mainProfile = codec('video/H264', mainLine);
const baseline = codec('video/H264', baselineLine);
const preferences = selectPublisherCodecPreferences('h264', [mainProfile, baseline, constrainedBaseline]);
expect(preferences).toEqual([mainProfile, baseline, constrainedBaseline]);
});
it('never leads with Main or Baseline, which this server registers nowhere and deletes from the answer', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const mainProfile = codec('video/H264', mainLine);
const baseline = codec('video/H264', baselineLine);
const preferences = selectPublisherCodecPreferences('h264', [mainProfile, baseline, constrainedBaseline], true);
const preferences = selectPublisherCodecPreferences('h264', [mainProfile, baseline, constrainedBaseline]);
expect(preferences).toEqual([constrainedBaseline, mainProfile, baseline]);
});
@@ -75,21 +54,8 @@ describe('selectPublisherCodecPreferences', () => {
];
}
it('sorts the capabilities Chromium reports by the old table while screen share delivery is off', () => {
const preferences = selectPublisherCodecPreferences('h264', chromiumCapabilities());
expect(preferences.map((entry) => entry.sdpFmtpLine)).toEqual([
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640034',
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f',
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f',
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',
'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f',
'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f',
'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f',
]);
});
it('offers High first out of the capabilities Chromium reports, so the only hardware profile this server registers wins', () => {
const preferences = selectPublisherCodecPreferences('h264', chromiumCapabilities(), true);
const preferences = selectPublisherCodecPreferences('h264', chromiumCapabilities());
expect(preferences.map((entry) => entry.sdpFmtpLine)).toEqual([
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640034',
'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',
@@ -116,7 +82,7 @@ describe('selectPublisherCodecPreferences', () => {
return {constrainedBaseline, mainProfile, highProfileLevel31, highProfileLevel51, constrainedHigh};
}
it('ranks High, Constrained High, Main and Baseline above Constrained Baseline while screen share delivery is off', () => {
it('ranks High and Constrained High above Constrained Baseline, whatever level each one reports', () => {
const {constrainedBaseline, mainProfile, highProfileLevel31, highProfileLevel51, constrainedHigh} = levelSpread();
const preferences = selectPublisherCodecPreferences('h264', [
mainProfile,
@@ -125,22 +91,6 @@ describe('selectPublisherCodecPreferences', () => {
constrainedHigh,
constrainedBaseline,
]);
expect(preferences).toEqual([
highProfileLevel31,
highProfileLevel51,
constrainedHigh,
mainProfile,
constrainedBaseline,
]);
});
it('ranks High and Constrained High above Constrained Baseline, whatever level each one reports', () => {
const {constrainedBaseline, mainProfile, highProfileLevel31, highProfileLevel51, constrainedHigh} = levelSpread();
const preferences = selectPublisherCodecPreferences(
'h264',
[mainProfile, highProfileLevel31, highProfileLevel51, constrainedHigh, constrainedBaseline],
true,
);
expect(preferences).toEqual([
highProfileLevel31,
highProfileLevel51,
@@ -150,7 +100,7 @@ describe('selectPublisherCodecPreferences', () => {
]);
});
it('ranks packetization-mode=1 above packetization-mode=0 in both arms, which no hardware encoder takes', () => {
it('ranks packetization-mode=1 above packetization-mode=0, which no hardware encoder takes', () => {
const constrainedBaselineMode0 = codec(
'video/H264',
'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f',
@@ -167,7 +117,6 @@ describe('selectPublisherCodecPreferences', () => {
const capabilities = [highProfileMode0, constrainedBaselineMode0, constrainedBaselineMode1, highProfileMode1];
const expected = [highProfileMode1, constrainedBaselineMode1, highProfileMode0, constrainedBaselineMode0];
expect(selectPublisherCodecPreferences('h264', capabilities)).toEqual(expected);
expect(selectPublisherCodecPreferences('h264', capabilities, true)).toEqual(expected);
});
it('puts the chosen codec first and keeps every other codec in browser capability order', () => {
@@ -175,7 +124,6 @@ describe('selectPublisherCodecPreferences', () => {
const vp8 = codec('video/VP8');
const rtx = codec('video/rtx');
expect(selectPublisherCodecPreferences('vp9', [vp8, rtx, vp9])).toEqual([vp9, vp8, rtx]);
expect(selectPublisherCodecPreferences('vp9', [vp8, rtx, vp9], true)).toEqual([vp9, vp8, rtx]);
});
it('keeps the other codecs so a later publication on the same connection can negotiate them', () => {
@@ -192,7 +140,7 @@ describe('selectPublisherCodecPreferences', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const capabilities = [vp8, highProfile, constrainedBaseline];
expect(selectPublisherCodecPreferences('vp8', capabilities)).toEqual([vp8, highProfile, constrainedBaseline]);
expect(selectPublisherCodecPreferences('vp8', capabilities, true, new Set(['42e0']))).toEqual([
expect(selectPublisherCodecPreferences('vp8', capabilities, new Set(['42e0']))).toEqual([
vp8,
constrainedBaseline,
highProfile,
@@ -203,25 +151,15 @@ describe('selectPublisherCodecPreferences', () => {
expect(selectPublisherCodecPreferences('av1', [codec('video/VP8'), codec('video/rtx')])).toEqual([]);
});
it('ignores the profiles this host measured while screen share delivery is off', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const highProfile = codec('video/H264', highLine);
const capabilities = [highProfile, constrainedBaseline];
expect(selectPublisherCodecPreferences('h264', capabilities, false, new Set(['42e0']))).toEqual([
highProfile,
constrainedBaseline,
]);
});
it('only lets a profile this host encodes in hardware outrank Constrained Baseline', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const highProfile = codec('video/H264', highLine);
const capabilities = [highProfile, constrainedBaseline];
expect(selectPublisherCodecPreferences('h264', capabilities, true, new Set(['42e0']))).toEqual([
expect(selectPublisherCodecPreferences('h264', capabilities, new Set(['42e0']))).toEqual([
constrainedBaseline,
highProfile,
]);
expect(selectPublisherCodecPreferences('h264', capabilities, true, new Set(['6400', '42e0']))).toEqual([
expect(selectPublisherCodecPreferences('h264', capabilities, new Set(['6400', '42e0']))).toEqual([
highProfile,
constrainedBaseline,
]);
@@ -231,8 +169,8 @@ describe('selectPublisherCodecPreferences', () => {
const constrainedBaseline = codec('video/H264', constrainedBaselineLine);
const highProfile = codec('video/H264', highLine);
const capabilities = [constrainedBaseline, highProfile];
expect(selectPublisherCodecPreferences('h264', capabilities, true)).toEqual([highProfile, constrainedBaseline]);
expect(selectPublisherCodecPreferences('h264', capabilities, true, new Set())).toEqual([
expect(selectPublisherCodecPreferences('h264', capabilities)).toEqual([highProfile, constrainedBaseline]);
expect(selectPublisherCodecPreferences('h264', capabilities, new Set())).toEqual([
highProfile,
constrainedBaseline,
]);
@@ -245,7 +183,7 @@ describe('selectPublisherCodecPreferences', () => {
'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=64001f',
);
expect(
selectPublisherCodecPreferences('h264', [highProfileMode0, constrainedBaselineMode1], true, new Set(['6400'])),
selectPublisherCodecPreferences('h264', [highProfileMode0, constrainedBaselineMode1], new Set(['6400'])),
).toEqual([constrainedBaselineMode1, highProfileMode0]);
});
});
@@ -123,13 +123,6 @@ const videoCodecMimeTypes: Record<VideoCodec, Array<string>> = {
vp8: ['video/vp8'],
};
const h264ProfileRanks = new Map([
['6400', 0],
['640c', 1],
['4d00', 2],
['4200', 3],
['42e0', 4],
]);
const h264DeliveryProfileRanks = new Map([
['6400', 0],
['640c', 1],
['42e0', 2],
@@ -139,8 +132,7 @@ const h264DeliveryProfileRanks = new Map([
const h264UnrankedProfileScore = 5;
const h264MissingProfileScore = 6;
const h264NonHardwareProfilePenalty = 8;
const h264PacketizationMode0Score = 10;
const h264DeliveryPacketizationMode0Score = 20;
const h264PacketizationMode0Score = 20;
type RtpCodecCapability = RTCRtpCapabilities['codecs'][number] & {sdpFmtpLine?: string};
enum PCState {
@@ -523,7 +515,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
this.loggerOptions,
rtcConfig,
this.options.subscriberVideoCodecExclusions,
this.options.screenShareDelivery ?? false,
);
} else {
this.participantSid = joinResponse.participant?.sid;
@@ -537,7 +528,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
this.loggerOptions,
rtcConfig,
this.options.subscriberVideoCodecExclusions,
this.options.screenShareDelivery ?? false,
);
}
@@ -1062,12 +1052,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
if (typeof RTCRtpSender === 'undefined' || typeof RTCRtpSender.getCapabilities !== 'function') return;
const capabilities = RTCRtpSender.getCapabilities('video');
if (!capabilities) return;
const preferences = selectPublisherCodecPreferences(
codec,
capabilities.codecs,
this.options.screenShareDelivery ?? false,
this.options.h264HardwareProfiles,
);
const preferences = selectPublisherCodecPreferences(codec, capabilities.codecs, this.options.h264HardwareProfiles);
if (preferences.length === 0) {
this.log.warn('sender cannot encode the requested codec, leaving the browser order in place', {
...this.logContext,
@@ -1843,33 +1828,27 @@ function getFmtpParameter(sdpFmtpLine: string | undefined, key: string): string
function getH264PublisherCodecScore(
codec: RtpCodecCapability,
screenShareDelivery: boolean,
hardwareProfiles: ReadonlySet<string> | undefined,
): number {
const profileLevelId = getFmtpParameter(codec.sdpFmtpLine, 'profile-level-id');
const packetizationMode = getFmtpParameter(codec.sdpFmtpLine, 'packetization-mode');
const mode0Score = screenShareDelivery ? h264DeliveryPacketizationMode0Score : h264PacketizationMode0Score;
const packetizationScore = packetizationMode === '1' ? 0 : mode0Score;
const packetizationScore = packetizationMode === '1' ? 0 : h264PacketizationMode0Score;
if (!profileLevelId) return packetizationScore + h264MissingProfileScore;
const profile = profileLevelId.slice(0, 4);
if (!screenShareDelivery) {
return packetizationScore + (h264ProfileRanks.get(profile) ?? h264UnrankedProfileScore);
}
const isSoftwareOnly = hardwareProfiles !== undefined && hardwareProfiles.size > 0 && !hardwareProfiles.has(profile);
const hardwareScore = isSoftwareOnly ? h264NonHardwareProfilePenalty : 0;
return packetizationScore + hardwareScore + (h264DeliveryProfileRanks.get(profile) ?? h264UnrankedProfileScore);
return packetizationScore + hardwareScore + (h264ProfileRanks.get(profile) ?? h264UnrankedProfileScore);
}
function preferHardwareH264Codecs(
codecs: ReadonlyArray<RtpCodecCapability>,
screenShareDelivery: boolean,
hardwareProfiles: ReadonlySet<string> | undefined,
): Array<RtpCodecCapability> {
return codecs
.map((codec, index) => ({
codec,
index,
score: getH264PublisherCodecScore(codec, screenShareDelivery, hardwareProfiles),
score: getH264PublisherCodecScore(codec, hardwareProfiles),
}))
.sort((a, b) => a.score - b.score || a.index - b.index)
.map((entry) => entry.codec);
@@ -1878,17 +1857,15 @@ function preferHardwareH264Codecs(
export function selectPublisherCodecPreferences(
codec: VideoCodec,
codecs: ReadonlyArray<RtpCodecCapability>,
screenShareDelivery: boolean = false,
h264HardwareProfiles?: ReadonlySet<string>,
): Array<RtpCodecCapability> {
const mimeTypes = new Set(videoCodecMimeTypes[codec]);
const selected = codecs.filter((entry) => mimeTypes.has(entry.mimeType.toLowerCase()));
if (selected.length === 0) return [];
const preferred =
codec === 'h264' ? preferHardwareH264Codecs(selected, screenShareDelivery, h264HardwareProfiles) : selected;
const preferred = codec === 'h264' ? preferHardwareH264Codecs(selected, h264HardwareProfiles) : selected;
const isH264 = (entry: RtpCodecCapability): boolean => entry.mimeType.toLowerCase() === 'video/h264';
const remaining = codecs.filter((entry) => !mimeTypes.has(entry.mimeType.toLowerCase()));
const rankedH264 = preferHardwareH264Codecs(remaining.filter(isH264), screenShareDelivery, h264HardwareProfiles);
const rankedH264 = preferHardwareH264Codecs(remaining.filter(isH264), h264HardwareProfiles);
let nextH264 = 0;
const rest = remaining.map((entry) => (isH264(entry) ? rankedH264[nextH264++] : entry));
return [...preferred, ...rest];
@@ -63,7 +63,6 @@ import {
publishDefaults,
roomConnectOptionDefaults,
roomOptionDefaults,
screenShareDeliveryPublishDefaults,
videoDefaults,
} from './defaults.ts';
import {ConnectionError, ConnectionErrorReason, UnexpectedConnectionState, UnsupportedServer} from './errors.ts';
@@ -228,7 +227,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
...options?.videoCaptureDefaults,
};
this.options.publishDefaults = {
...(this.options.screenShareDelivery ? screenShareDeliveryPublishDefaults : publishDefaults),
...publishDefaults,
...options?.publishDefaults,
};
@@ -8,7 +8,7 @@ import {AudioPresets, BackupCodecPolicy, ScreenSharePresets, VideoPresets} from
export const defaultVideoCodec = 'h264';
export const screenShareDeliveryPublishDefaults: TrackPublishDefaults = {
export const publishDefaults: TrackPublishDefaults = {
audioPreset: AudioPresets.music,
dtx: false,
red: true,
@@ -21,11 +21,6 @@ export const screenShareDeliveryPublishDefaults: TrackPublishDefaults = {
preConnectBuffer: false,
} as const;
export const publishDefaults: TrackPublishDefaults = {
...screenShareDeliveryPublishDefaults,
degradationPreference: 'maintain-resolution',
};
export const audioDefaults: AudioCaptureOptions = {
deviceId: {ideal: 'default'},
autoGainControl: true,
@@ -814,7 +814,6 @@ export default class LocalParticipant extends Participant {
...this.roomOptions.publishDefaults,
...options,
};
track.screenShareDelivery = this.roomOptions.screenShareDelivery ?? false;
const isStereoInput =
('channelCount' in track.mediaStreamTrack.getSettings() &&
track.mediaStreamTrack.getSettings().channelCount === 2) ||
@@ -1788,7 +1787,7 @@ export default class LocalParticipant extends Participant {
return;
}
let subscribedCodecs = update.subscribedCodecs;
if (this.roomOptions.screenShareDelivery && hasSingleRidlessEncoding(pub.videoTrack)) {
if (hasSingleRidlessEncoding(pub.videoTrack)) {
subscribedCodecs = subscribedCodecs.filter((codec) => codec.qualities.some((quality) => quality.enabled));
if (subscribedCodecs.length === 0) {
return;
@@ -41,8 +41,6 @@ export default abstract class LocalTrack<TrackKind extends Track.Kind = Track.Ki
codec?: VideoCodec;
screenShareDelivery: boolean = false;
get constraints() {
return this._constraints;
}
@@ -563,7 +561,7 @@ export default abstract class LocalTrack<TrackKind extends Track.Kind = Track.Ki
);
private debouncedTrackMuteHandler = debounce(async () => {
if (this.screenShareDelivery && this.source === Track.Source.ScreenShare) {
if (this.source === Track.Source.ScreenShare) {
this.log.debug('screen share capture went idle, keeping upstream published', this.logContext);
return;
}