From 760af8398f6c860cc530801e04c86378ba43d0f0 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Wed, 15 May 2024 14:50:09 +0200 Subject: [PATCH] Add degradationPreference option for LocalVideoTrack (#1138) * Add degradationPreference option for LocalVideoTrack * Create dull-buckets-chew.md * remove redundant check --- .changeset/dull-buckets-chew.md | 5 +++++ src/room/participant/LocalParticipant.ts | 20 ++++++-------------- src/room/participant/publishUtils.ts | 15 +++++++++++++++ src/room/track/LocalTrack.ts | 11 ++++++++++- src/room/track/LocalVideoTrack.ts | 23 +++++++++++++++++++++++ src/room/track/options.ts | 5 +++++ src/room/utils.ts | 6 ++++-- 7 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 .changeset/dull-buckets-chew.md diff --git a/.changeset/dull-buckets-chew.md b/.changeset/dull-buckets-chew.md new file mode 100644 index 0000000000..bc1659f59a --- /dev/null +++ b/.changeset/dull-buckets-chew.md @@ -0,0 +1,5 @@ +--- +"livekit-client": patch +--- + +Add degradationPreference option for LocalVideoTrack diff --git a/src/room/participant/LocalParticipant.ts b/src/room/participant/LocalParticipant.ts index 2d4e730603..48aaf55876 100644 --- a/src/room/participant/LocalParticipant.ts +++ b/src/room/participant/LocalParticipant.ts @@ -54,6 +54,7 @@ import { trackPermissionToProto } from './ParticipantTrackPermission'; import { computeTrackBackupEncodings, computeVideoEncodings, + getDefaultDegradationPreference, mediaTrackToLocalTrack, } from './publishUtils'; @@ -857,6 +858,11 @@ export default class LocalParticipant extends Participant { track.sender = await this.engine.createSender(track, opts, encodings); + if (track instanceof LocalVideoTrack) { + opts.degradationPreference ??= getDefaultDegradationPreference(track); + track.setDegradationPreference(opts.degradationPreference); + } + if (encodings) { if (isFireFox() && track.kind === Track.Kind.Audio) { /* Refer to RFC https://datatracker.ietf.org/doc/html/rfc7587#section-6.1, @@ -889,20 +895,6 @@ export default class LocalParticipant extends Participant { } } - if (track.kind === Track.Kind.Video && track.source === Track.Source.ScreenShare) { - // a few of reasons we are forcing this setting without allowing overrides: - // 1. without this, Chrome seems to aggressively resize the SVC video stating `quality-limitation: bandwidth` even when BW isn't an issue - // 2. since we are overriding contentHint to motion (to workaround L1T3 publishing), it overrides the default degradationPreference to `balanced` - try { - this.log.debug(`setting degradationPreference to maintain-resolution`); - const params = track.sender.getParameters(); - params.degradationPreference = 'maintain-resolution'; - await track.sender.setParameters(params); - } catch (e) { - this.log.warn(`failed to set degradationPreference: ${e}`); - } - } - await this.engine.negotiate(); if (track instanceof LocalVideoTrack) { diff --git a/src/room/participant/publishUtils.ts b/src/room/participant/publishUtils.ts index d8fa4417a1..2e6eb4c5a3 100644 --- a/src/room/participant/publishUtils.ts +++ b/src/room/participant/publishUtils.ts @@ -19,6 +19,7 @@ import { isReactNative, isSVCCodec, isSafari, + unwrapConstraint, } from '../utils'; /** @internal */ @@ -440,3 +441,17 @@ export class ScalabilityMode { return `L${this.spatial}T${this.temporal}${this.suffix ?? ''}`; } } + +export function getDefaultDegradationPreference(track: LocalVideoTrack): RTCDegradationPreference { + // a few of reasons we have different default paths: + // 1. without this, Chrome seems to aggressively resize the SVC video stating `quality-limitation: bandwidth` even when BW isn't an issue + // 2. since we are overriding contentHint to motion (to workaround L1T3 publishing), it overrides the default degradationPreference to `balanced` + if ( + track.source === Track.Source.ScreenShare || + (track.constraints.height && unwrapConstraint(track.constraints.height) >= 1080) + ) { + return 'maintain-resolution'; + } else { + return 'balanced'; + } +} diff --git a/src/room/track/LocalTrack.ts b/src/room/track/LocalTrack.ts index 7024b14c7a..b4aa138730 100644 --- a/src/room/track/LocalTrack.ts +++ b/src/room/track/LocalTrack.ts @@ -15,8 +15,17 @@ const defaultDimensionsTimeout = 1000; export default abstract class LocalTrack< TrackKind extends Track.Kind = Track.Kind, > extends Track { + protected _sender?: RTCRtpSender; + /** @internal */ - sender?: RTCRtpSender; + get sender(): RTCRtpSender | undefined { + return this._sender; + } + + /** @internal */ + set sender(sender: RTCRtpSender | undefined) { + this._sender = sender; + } /** @internal */ codec?: VideoCodec; diff --git a/src/room/track/LocalVideoTrack.ts b/src/room/track/LocalVideoTrack.ts index bb4ea17040..9108304f7c 100644 --- a/src/room/track/LocalVideoTrack.ts +++ b/src/room/track/LocalVideoTrack.ts @@ -53,6 +53,15 @@ export default class LocalVideoTrack extends LocalTrack { // a missing `getParameter` call. private senderLock: Mutex; + private degradationPreference: RTCDegradationPreference = 'balanced'; + + override set sender(sender: RTCRtpSender | undefined) { + this._sender = sender; + if (this.degradationPreference) { + this.setDegradationPreference(this.degradationPreference); + } + } + /** * * @param mediaTrack @@ -273,6 +282,20 @@ export default class LocalVideoTrack extends LocalTrack { } } + async setDegradationPreference(preference: RTCDegradationPreference) { + this.degradationPreference = preference; + if (this.sender) { + try { + this.log.debug(`setting degradationPreference to ${preference}`, this.logContext); + const params = this.sender.getParameters(); + params.degradationPreference = preference; + this.sender.setParameters(params); + } catch (e: any) { + this.log.warn(`failed to set degradationPreference`, { error: e, ...this.logContext }); + } + } + } + addSimulcastTrack( codec: VideoCodec, encodings?: RTCRtpEncodingParameters[], diff --git a/src/room/track/options.ts b/src/room/track/options.ts index d1e647e855..c6a63acab6 100644 --- a/src/room/track/options.ts +++ b/src/room/track/options.ts @@ -64,6 +64,11 @@ export interface TrackPublishDefaults { */ scalabilityMode?: ScalabilityMode; + /** + * degradation preference + */ + degradationPreference?: RTCDegradationPreference; + /** * Up to two additional simulcast layers to publish in addition to the original * Track. diff --git a/src/room/utils.ts b/src/room/utils.ts index e2494d9245..e16a6b769b 100644 --- a/src/room/utils.ts +++ b/src/room/utils.ts @@ -491,8 +491,10 @@ export function isVideoCodec(maybeCodec: string): maybeCodec is VideoCodec { return videoCodecs.includes(maybeCodec as VideoCodec); } -export function unwrapConstraint(constraint: ConstrainDOMString): string { - if (typeof constraint === 'string') { +export function unwrapConstraint(constraint: ConstrainDOMString): string; +export function unwrapConstraint(constraint: ConstrainULong): number; +export function unwrapConstraint(constraint: ConstrainDOMString | ConstrainULong): string | number { + if (typeof constraint === 'string' || typeof constraint === 'number') { return constraint; }