Skip to content

Commit 38cf002

Browse files
davidzhaolukasIO
andauthored
allow manual controls even when adaptiveStream is enabled (livekit#1569)
Co-authored-by: lukasIO <mail@lukasseiler.de>
1 parent 47b0d87 commit 38cf002

File tree

5 files changed

+108
-34
lines changed

5 files changed

+108
-34
lines changed

.changeset/rotten-lemons-itch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"livekit-client": patch
3+
---
4+
5+
allow manual controls even when adaptiveStream is enabled

examples/demo/demo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ const appActions = {
114114
},
115115
publishDefaults: {
116116
simulcast,
117-
videoSimulcastLayers: [VideoPresets.h90, VideoPresets.h216],
117+
videoSimulcastLayers: [VideoPresets.h180, VideoPresets.h360],
118118
videoCodec: preferredCodec || 'vp8',
119119
dtx: true,
120120
red: true,

src/room/participant/RemoteParticipant.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export default class RemoteParticipant extends Participant {
8686
this.log.debug('send update settings', {
8787
...this.logContext,
8888
...getLogContextFromTrack(publication),
89+
settings,
8990
});
9091
this.signalClient.sendUpdateTrackSettings(settings);
9192
});

src/room/track/RemoteTrackPublication.ts

Lines changed: 89 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { isRemoteVideoTrack } from '../utils';
1111
import type RemoteTrack from './RemoteTrack';
1212
import { Track, VideoQuality } from './Track';
1313
import { TrackPublication } from './TrackPublication';
14+
import { areDimensionsSmaller, layerDimensionsFor } from './utils';
1415

1516
export default class RemoteTrackPublication extends TrackPublication {
1617
track?: RemoteTrack = undefined;
@@ -21,11 +22,15 @@ export default class RemoteTrackPublication extends TrackPublication {
2122
// keeps track of client's desire to subscribe to a track, also true if autoSubscribe is active
2223
protected subscribed?: boolean;
2324

24-
protected disabled: boolean = false;
25+
protected requestedDisabled: boolean | undefined = undefined;
2526

26-
protected currentVideoQuality?: VideoQuality = VideoQuality.HIGH;
27+
protected visible: boolean = true;
2728

28-
protected videoDimensions?: Track.Dimensions;
29+
protected videoDimensionsAdaptiveStream?: Track.Dimensions;
30+
31+
protected requestedVideoDimensions?: Track.Dimensions;
32+
33+
protected requestedMaxQuality?: VideoQuality;
2934

3035
protected fps?: number;
3136

@@ -105,7 +110,9 @@ export default class RemoteTrackPublication extends TrackPublication {
105110
}
106111

107112
get isEnabled(): boolean {
108-
return !this.disabled;
113+
return this.requestedDisabled !== undefined
114+
? !this.requestedDisabled
115+
: this.isAdaptiveStream && this.visible;
109116
}
110117

111118
get isLocal() {
@@ -119,10 +126,10 @@ export default class RemoteTrackPublication extends TrackPublication {
119126
* @param enabled
120127
*/
121128
setEnabled(enabled: boolean) {
122-
if (!this.isManualOperationAllowed() || this.disabled === !enabled) {
129+
if (!this.isManualOperationAllowed() || this.requestedDisabled === !enabled) {
123130
return;
124131
}
125-
this.disabled = !enabled;
132+
this.requestedDisabled = !enabled;
126133

127134
this.emitTrackUpdate();
128135
}
@@ -135,29 +142,36 @@ export default class RemoteTrackPublication extends TrackPublication {
135142
* optimize for uninterrupted video
136143
*/
137144
setVideoQuality(quality: VideoQuality) {
138-
if (!this.isManualOperationAllowed() || this.currentVideoQuality === quality) {
145+
if (!this.isManualOperationAllowed() || this.requestedMaxQuality === quality) {
139146
return;
140147
}
141-
this.currentVideoQuality = quality;
142-
this.videoDimensions = undefined;
148+
this.requestedMaxQuality = quality;
149+
this.requestedVideoDimensions = undefined;
143150

144151
this.emitTrackUpdate();
145152
}
146153

154+
/**
155+
* Explicitly set the video dimensions for this track.
156+
*
157+
* This will take precedence over adaptive stream dimensions.
158+
*
159+
* @param dimensions The video dimensions to set.
160+
*/
147161
setVideoDimensions(dimensions: Track.Dimensions) {
148162
if (!this.isManualOperationAllowed()) {
149163
return;
150164
}
151165
if (
152-
this.videoDimensions?.width === dimensions.width &&
153-
this.videoDimensions?.height === dimensions.height
166+
this.requestedVideoDimensions?.width === dimensions.width &&
167+
this.requestedVideoDimensions?.height === dimensions.height
154168
) {
155169
return;
156170
}
157171
if (isRemoteVideoTrack(this.track)) {
158-
this.videoDimensions = dimensions;
172+
this.requestedVideoDimensions = dimensions;
159173
}
160-
this.currentVideoQuality = undefined;
174+
this.requestedMaxQuality = undefined;
161175

162176
this.emitTrackUpdate();
163177
}
@@ -180,7 +194,7 @@ export default class RemoteTrackPublication extends TrackPublication {
180194
}
181195

182196
get videoQuality(): VideoQuality | undefined {
183-
return this.currentVideoQuality;
197+
return this.requestedMaxQuality ?? VideoQuality.HIGH;
184198
}
185199

186200
/** @internal */
@@ -260,13 +274,6 @@ export default class RemoteTrackPublication extends TrackPublication {
260274
}
261275

262276
private isManualOperationAllowed(): boolean {
263-
if (this.kind === Track.Kind.Video && this.isAdaptiveStream) {
264-
this.log.warn(
265-
'adaptive stream is enabled, cannot change video track settings',
266-
this.logContext,
267-
);
268-
return false;
269-
}
270277
if (!this.isDesired) {
271278
this.log.warn('cannot update track settings when not subscribed', this.logContext);
272279
return false;
@@ -288,7 +295,7 @@ export default class RemoteTrackPublication extends TrackPublication {
288295
`adaptivestream video visibility ${this.trackSid}, visible=${visible}`,
289296
this.logContext,
290297
);
291-
this.disabled = !visible;
298+
this.visible = visible;
292299
this.emitTrackUpdate();
293300
};
294301

@@ -297,25 +304,75 @@ export default class RemoteTrackPublication extends TrackPublication {
297304
`adaptivestream video dimensions ${dimensions.width}x${dimensions.height}`,
298305
this.logContext,
299306
);
300-
this.videoDimensions = dimensions;
307+
this.videoDimensionsAdaptiveStream = dimensions;
301308
this.emitTrackUpdate();
302309
};
303310

304311
/* @internal */
305312
emitTrackUpdate() {
306313
const settings: UpdateTrackSettings = new UpdateTrackSettings({
307314
trackSids: [this.trackSid],
308-
disabled: this.disabled,
315+
disabled: !this.isEnabled,
309316
fps: this.fps,
310317
});
311-
if (this.videoDimensions) {
312-
settings.width = Math.ceil(this.videoDimensions.width);
313-
settings.height = Math.ceil(this.videoDimensions.height);
314-
} else if (this.currentVideoQuality !== undefined) {
315-
settings.quality = this.currentVideoQuality;
316-
} else {
317-
// defaults to high quality
318-
settings.quality = VideoQuality.HIGH;
318+
319+
if (this.kind === Track.Kind.Video) {
320+
let minDimensions = this.requestedVideoDimensions;
321+
322+
if (this.videoDimensionsAdaptiveStream !== undefined) {
323+
if (minDimensions) {
324+
// check whether the adaptive stream dimensions are smaller than the requested dimensions and use smaller one
325+
const smallerAdaptive = areDimensionsSmaller(
326+
this.videoDimensionsAdaptiveStream,
327+
minDimensions,
328+
);
329+
if (smallerAdaptive) {
330+
this.log.debug('using adaptive stream dimensions instead of requested', {
331+
...this.logContext,
332+
...this.videoDimensionsAdaptiveStream,
333+
});
334+
minDimensions = this.videoDimensionsAdaptiveStream;
335+
}
336+
} else if (this.requestedMaxQuality !== undefined && this.trackInfo) {
337+
// check whether adaptive stream dimensions are smaller than the max quality layer and use smaller one
338+
const maxQualityLayer = layerDimensionsFor(this.trackInfo, this.requestedMaxQuality);
339+
340+
if (
341+
maxQualityLayer &&
342+
areDimensionsSmaller(this.videoDimensionsAdaptiveStream, maxQualityLayer)
343+
) {
344+
this.log.debug('using adaptive stream dimensions instead of max quality layer', {
345+
...this.logContext,
346+
...this.videoDimensionsAdaptiveStream,
347+
});
348+
minDimensions = this.videoDimensionsAdaptiveStream;
349+
}
350+
} else {
351+
this.log.debug('using adaptive stream dimensions', {
352+
...this.logContext,
353+
...this.videoDimensionsAdaptiveStream,
354+
});
355+
minDimensions = this.videoDimensionsAdaptiveStream;
356+
}
357+
}
358+
359+
if (minDimensions) {
360+
settings.width = Math.ceil(minDimensions.width);
361+
settings.height = Math.ceil(minDimensions.height);
362+
} else if (this.requestedMaxQuality !== undefined) {
363+
this.log.debug('using requested max quality', {
364+
...this.logContext,
365+
quality: this.requestedMaxQuality,
366+
});
367+
settings.quality = this.requestedMaxQuality;
368+
} else {
369+
this.log.debug('using default quality', {
370+
...this.logContext,
371+
quality: VideoQuality.HIGH,
372+
});
373+
// defaults to high quality
374+
settings.quality = VideoQuality.HIGH;
375+
}
319376
}
320377

321378
this.emit(TrackEvent.UpdateSettings, settings);

src/room/track/utils.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { TrackPublishedResponse, TrackSource } from '@livekit/protocol';
1+
import { TrackInfo, TrackPublishedResponse, TrackSource, VideoQuality } from '@livekit/protocol';
22
import type { AudioProcessorOptions, TrackProcessor, VideoProcessorOptions } from '../..';
33
import { cloneDeep } from '../../utils/cloneDeep';
44
import { isSafari, sleep } from '../utils';
@@ -329,3 +329,14 @@ export function getTrackSourceFromProto(source: TrackSource): Track.Source {
329329
return Track.Source.Unknown;
330330
}
331331
}
332+
333+
export function areDimensionsSmaller(a: Track.Dimensions, b: Track.Dimensions): boolean {
334+
return a.width * a.height < b.width * b.height;
335+
}
336+
337+
export function layerDimensionsFor(
338+
trackInfo: TrackInfo,
339+
quality: VideoQuality,
340+
): Track.Dimensions | undefined {
341+
return trackInfo.layers?.find((l) => l.quality === quality);
342+
}

0 commit comments

Comments
 (0)