Skip to content

Commit

Permalink
Prevent concurrent setParameter call to avoid exception (#585)
Browse files Browse the repository at this point in the history
if multiple get/setParameter are called concurrently, certain timing of
events could lead to the browser throwing an exception in `setParameter`,
due to a missing `getParameter` call.
  • Loading branch information
davidzhao committed Mar 2, 2023
1 parent b0fbe45 commit 0a33b1a
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 47 deletions.
5 changes: 5 additions & 0 deletions .changeset/sour-clocks-juggle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'livekit-client': patch
---

Prevent concurrent RTCRtpSender.setParameter call to avoid exception
2 changes: 1 addition & 1 deletion src/room/participant/LocalParticipant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -957,7 +957,7 @@ export default class LocalParticipant extends Participant {
}
}
} else if (update.subscribedQualities.length > 0) {
pub.videoTrack?.setPublishingLayers(update.subscribedQualities);
await pub.videoTrack?.setPublishingLayers(update.subscribedQualities);
}
};

Expand Down
108 changes: 62 additions & 46 deletions src/room/track/LocalVideoTrack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import log from '../../logger';
import { VideoLayer, VideoQuality } from '../../proto/livekit_models';
import type { SubscribedCodec, SubscribedQuality } from '../../proto/livekit_rtc';
import { computeBitrate, monitorFrequency, VideoSenderStats } from '../stats';
import { isFireFox, isMobile, isWeb } from '../utils';
import { isFireFox, isMobile, isWeb, Mutex } from '../utils';
import LocalTrack from './LocalTrack';
import type { VideoCaptureOptions, VideoCodec } from './options';
import { Track } from './Track';
Expand Down Expand Up @@ -39,6 +39,12 @@ export default class LocalVideoTrack extends LocalTrack {

private subscribedCodecs?: SubscribedCodec[];

// prevents concurrent manipulations to track sender
// if multiple get/setParameter are called concurrently, certain timing of events
// could lead to the browser throwing an exception in `setParameter`, due to
// a missing `getParameter` call.
private senderLock: Mutex;

/**
*
* @param mediaTrack
Expand All @@ -51,6 +57,7 @@ export default class LocalVideoTrack extends LocalTrack {
userProvidedTrack = true,
) {
super(mediaTrack, Track.Kind.Video, constraints, userProvidedTrack);
this.senderLock = new Mutex();
}

get isSimulcast(): boolean {
Expand Down Expand Up @@ -257,6 +264,7 @@ export default class LocalVideoTrack extends LocalTrack {
simulcastCodecInfo.sender,
simulcastCodecInfo.encodings!,
codec.qualities,
this.senderLock,
);
}
}
Expand All @@ -274,7 +282,7 @@ export default class LocalVideoTrack extends LocalTrack {
return;
}

await setPublishingLayersForSender(this.sender, this.encodings, qualities);
await setPublishingLayersForSender(this.sender, this.encodings, qualities, this.senderLock);
}

protected monitorSender = async () => {
Expand Down Expand Up @@ -317,58 +325,66 @@ async function setPublishingLayersForSender(
sender: RTCRtpSender,
senderEncodings: RTCRtpEncodingParameters[],
qualities: SubscribedQuality[],
senderLock: Mutex,
) {
const unlock = await senderLock.lock();
log.debug('setPublishingLayersForSender', { sender, qualities, senderEncodings });
const params = sender.getParameters();
const { encodings } = params;
if (!encodings) {
return;
}

if (encodings.length !== senderEncodings.length) {
log.warn('cannot set publishing layers, encodings mismatch');
return;
}

let hasChanged = false;
encodings.forEach((encoding, idx) => {
let rid = encoding.rid ?? '';
if (rid === '') {
rid = 'q';
try {
const params = sender.getParameters();
const { encodings } = params;
if (!encodings) {
return;
}
const quality = videoQualityForRid(rid);
const subscribedQuality = qualities.find((q) => q.quality === quality);
if (!subscribedQuality) {

if (encodings.length !== senderEncodings.length) {
log.warn('cannot set publishing layers, encodings mismatch');
return;
}
if (encoding.active !== subscribedQuality.enabled) {
hasChanged = true;
encoding.active = subscribedQuality.enabled;
log.debug(
`setting layer ${subscribedQuality.quality} to ${encoding.active ? 'enabled' : 'disabled'}`,
);

// FireFox does not support setting encoding.active to false, so we
// have a workaround of lowering its bitrate and resolution to the min.
if (isFireFox()) {
if (subscribedQuality.enabled) {
encoding.scaleResolutionDownBy = senderEncodings[idx].scaleResolutionDownBy;
encoding.maxBitrate = senderEncodings[idx].maxBitrate;
/* @ts-ignore */
encoding.maxFrameRate = senderEncodings[idx].maxFrameRate;
} else {
encoding.scaleResolutionDownBy = 4;
encoding.maxBitrate = 10;
/* @ts-ignore */
encoding.maxFrameRate = 2;

let hasChanged = false;
encodings.forEach((encoding, idx) => {
let rid = encoding.rid ?? '';
if (rid === '') {
rid = 'q';
}
const quality = videoQualityForRid(rid);
const subscribedQuality = qualities.find((q) => q.quality === quality);
if (!subscribedQuality) {
return;
}
if (encoding.active !== subscribedQuality.enabled) {
hasChanged = true;
encoding.active = subscribedQuality.enabled;
log.debug(
`setting layer ${subscribedQuality.quality} to ${
encoding.active ? 'enabled' : 'disabled'
}`,
);

// FireFox does not support setting encoding.active to false, so we
// have a workaround of lowering its bitrate and resolution to the min.
if (isFireFox()) {
if (subscribedQuality.enabled) {
encoding.scaleResolutionDownBy = senderEncodings[idx].scaleResolutionDownBy;
encoding.maxBitrate = senderEncodings[idx].maxBitrate;
/* @ts-ignore */
encoding.maxFrameRate = senderEncodings[idx].maxFrameRate;
} else {
encoding.scaleResolutionDownBy = 4;
encoding.maxBitrate = 10;
/* @ts-ignore */
encoding.maxFrameRate = 2;
}
}
}
}
});
});

if (hasChanged) {
params.encodings = encodings;
await sender.setParameters(params);
if (hasChanged) {
params.encodings = encodings;
await sender.setParameters(params);
}
} finally {
unlock();
}
}

Expand Down

0 comments on commit 0a33b1a

Please sign in to comment.