livekit-client 1.0.4 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livekit-client.esm.mjs +218 -106
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/proto/livekit_rtc.d.ts +7 -0
- package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +8 -4
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +2 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/proto/livekit_rtc.ts +12 -0
- package/src/room/Room.ts +0 -8
- package/src/room/participant/LocalParticipant.ts +202 -88
- package/src/room/track/LocalVideoTrack.ts +34 -7
- package/src/room/track/RemoteVideoTrack.ts +2 -2
- package/src/room/utils.ts +3 -2
@@ -15,15 +15,14 @@ import RTCEngine from '../RTCEngine';
|
|
15
15
|
import LocalAudioTrack from '../track/LocalAudioTrack';
|
16
16
|
import LocalTrack from '../track/LocalTrack';
|
17
17
|
import LocalTrackPublication from '../track/LocalTrackPublication';
|
18
|
-
import LocalVideoTrack, {
|
19
|
-
SimulcastTrackInfo,
|
20
|
-
videoLayersFromEncodings,
|
21
|
-
} from '../track/LocalVideoTrack';
|
18
|
+
import LocalVideoTrack, { videoLayersFromEncodings } from '../track/LocalVideoTrack';
|
22
19
|
import {
|
20
|
+
AudioCaptureOptions,
|
23
21
|
CreateLocalTracksOptions,
|
24
22
|
ScreenShareCaptureOptions,
|
25
23
|
ScreenSharePresets,
|
26
24
|
TrackPublishOptions,
|
25
|
+
VideoCaptureOptions,
|
27
26
|
VideoCodec,
|
28
27
|
} from '../track/options';
|
29
28
|
import { Track } from '../track/Track';
|
@@ -34,7 +33,7 @@ import { ParticipantTrackPermission, trackPermissionToProto } from './Participan
|
|
34
33
|
import { computeVideoEncodings, mediaTrackToLocalTrack } from './publishUtils';
|
35
34
|
import RemoteParticipant from './RemoteParticipant';
|
36
35
|
|
37
|
-
const
|
36
|
+
const compatibleCodec = 'vp8';
|
38
37
|
export default class LocalParticipant extends Participant {
|
39
38
|
audioTracks: Map<string, LocalTrackPublication>;
|
40
39
|
|
@@ -117,8 +116,11 @@ export default class LocalParticipant extends Participant {
|
|
117
116
|
* If a track has already published, it'll mute or unmute the track.
|
118
117
|
* Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
|
119
118
|
*/
|
120
|
-
setCameraEnabled(
|
121
|
-
|
119
|
+
setCameraEnabled(
|
120
|
+
enabled: boolean,
|
121
|
+
options?: VideoCaptureOptions,
|
122
|
+
): Promise<LocalTrackPublication | undefined> {
|
123
|
+
return this.setTrackEnabled(Track.Source.Camera, enabled, options);
|
122
124
|
}
|
123
125
|
|
124
126
|
/**
|
@@ -127,16 +129,22 @@ export default class LocalParticipant extends Participant {
|
|
127
129
|
* If a track has already published, it'll mute or unmute the track.
|
128
130
|
* Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
|
129
131
|
*/
|
130
|
-
setMicrophoneEnabled(
|
131
|
-
|
132
|
+
setMicrophoneEnabled(
|
133
|
+
enabled: boolean,
|
134
|
+
options?: AudioCaptureOptions,
|
135
|
+
): Promise<LocalTrackPublication | undefined> {
|
136
|
+
return this.setTrackEnabled(Track.Source.Microphone, enabled, options);
|
132
137
|
}
|
133
138
|
|
134
139
|
/**
|
135
140
|
* Start or stop sharing a participant's screen
|
136
141
|
* Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
|
137
142
|
*/
|
138
|
-
setScreenShareEnabled(
|
139
|
-
|
143
|
+
setScreenShareEnabled(
|
144
|
+
enabled: boolean,
|
145
|
+
options?: ScreenShareCaptureOptions,
|
146
|
+
): Promise<LocalTrackPublication | undefined> {
|
147
|
+
return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options);
|
140
148
|
}
|
141
149
|
|
142
150
|
/** @internal */
|
@@ -155,16 +163,32 @@ export default class LocalParticipant extends Participant {
|
|
155
163
|
* Resolves with LocalTrackPublication if successful and void otherwise
|
156
164
|
*/
|
157
165
|
private async setTrackEnabled(
|
158
|
-
source: Track.Source,
|
166
|
+
source: Extract<Track.Source, Track.Source.Camera>,
|
159
167
|
enabled: boolean,
|
160
|
-
|
168
|
+
options?: VideoCaptureOptions,
|
169
|
+
): Promise<LocalTrackPublication | undefined>;
|
170
|
+
private async setTrackEnabled(
|
171
|
+
source: Extract<Track.Source, Track.Source.Microphone>,
|
172
|
+
enabled: boolean,
|
173
|
+
options?: AudioCaptureOptions,
|
174
|
+
): Promise<LocalTrackPublication | undefined>;
|
175
|
+
private async setTrackEnabled(
|
176
|
+
source: Extract<Track.Source, Track.Source.ScreenShare>,
|
177
|
+
enabled: boolean,
|
178
|
+
options?: ScreenShareCaptureOptions,
|
179
|
+
): Promise<LocalTrackPublication | undefined>;
|
180
|
+
private async setTrackEnabled(
|
181
|
+
source: Track.Source,
|
182
|
+
enabled: true,
|
183
|
+
options?: VideoCaptureOptions | AudioCaptureOptions | ScreenShareCaptureOptions,
|
184
|
+
) {
|
161
185
|
log.debug('setTrackEnabled', { source, enabled });
|
162
186
|
let track = this.getTrack(source);
|
163
187
|
if (enabled) {
|
164
188
|
if (track) {
|
165
189
|
await track.unmute();
|
166
190
|
} else {
|
167
|
-
let
|
191
|
+
let localTracks: Array<LocalTrack> | undefined;
|
168
192
|
if (this.pendingPublishing.has(source)) {
|
169
193
|
log.info('skipping duplicate published source', { source });
|
170
194
|
// no-op it's already been requested
|
@@ -174,23 +198,32 @@ export default class LocalParticipant extends Participant {
|
|
174
198
|
try {
|
175
199
|
switch (source) {
|
176
200
|
case Track.Source.Camera:
|
177
|
-
|
178
|
-
video: true,
|
201
|
+
localTracks = await this.createTracks({
|
202
|
+
video: (options as VideoCaptureOptions | undefined) ?? true,
|
179
203
|
});
|
204
|
+
|
180
205
|
break;
|
181
206
|
case Track.Source.Microphone:
|
182
|
-
|
183
|
-
audio: true,
|
207
|
+
localTracks = await this.createTracks({
|
208
|
+
audio: (options as AudioCaptureOptions | undefined) ?? true,
|
184
209
|
});
|
185
210
|
break;
|
186
211
|
case Track.Source.ScreenShare:
|
187
|
-
|
212
|
+
localTracks = await this.createScreenTracks({
|
213
|
+
...(options as ScreenShareCaptureOptions | undefined),
|
214
|
+
});
|
188
215
|
break;
|
189
216
|
default:
|
190
217
|
throw new TrackInvalidError(source);
|
191
218
|
}
|
192
|
-
|
193
|
-
|
219
|
+
const publishPromises: Array<Promise<LocalTrackPublication>> = [];
|
220
|
+
for (const localTrack of localTracks) {
|
221
|
+
publishPromises.push(this.publishTrack(localTrack));
|
222
|
+
}
|
223
|
+
const publishedTracks = await Promise.all(publishPromises);
|
224
|
+
// for screen share publications including audio, this will only return the screen share publication, not the screen share audio one
|
225
|
+
// revisit if we want to return an array of tracks instead for v2
|
226
|
+
[track] = publishedTracks;
|
194
227
|
} catch (e) {
|
195
228
|
if (e instanceof Error && !(e instanceof TrackInvalidError)) {
|
196
229
|
this.emit(ParticipantEvent.MediaDevicesError, e);
|
@@ -204,6 +237,10 @@ export default class LocalParticipant extends Participant {
|
|
204
237
|
// screenshare cannot be muted, unpublish instead
|
205
238
|
if (source === Track.Source.ScreenShare) {
|
206
239
|
track = this.unpublishTrack(track.track);
|
240
|
+
const screenAudioTrack = this.getTrack(Track.Source.ScreenShareAudio);
|
241
|
+
if (screenAudioTrack && screenAudioTrack.track) {
|
242
|
+
this.unpublishTrack(screenAudioTrack.track);
|
243
|
+
}
|
207
244
|
} else {
|
208
245
|
await track.mute();
|
209
246
|
}
|
@@ -415,7 +452,6 @@ export default class LocalParticipant extends Participant {
|
|
415
452
|
// compute encodings and layers for video
|
416
453
|
let encodings: RTCRtpEncodingParameters[] | undefined;
|
417
454
|
let simEncodings: RTCRtpEncodingParameters[] | undefined;
|
418
|
-
let simulcastTracks: SimulcastTrackInfo[] | undefined;
|
419
455
|
if (track.kind === Track.Kind.Video) {
|
420
456
|
// TODO: support react native, which doesn't expose getSettings
|
421
457
|
const settings = track.mediaStreamTrack.getSettings();
|
@@ -425,37 +461,38 @@ export default class LocalParticipant extends Participant {
|
|
425
461
|
req.width = width ?? 0;
|
426
462
|
req.height = height ?? 0;
|
427
463
|
// for svc codecs, disable simulcast and use vp8 for backup codec
|
428
|
-
if (
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
464
|
+
if (track instanceof LocalVideoTrack) {
|
465
|
+
if (opts?.videoCodec === 'vp9' || opts?.videoCodec === 'av1') {
|
466
|
+
// set scalabilityMode to 'L3T3' by default
|
467
|
+
opts.scalabilityMode = opts.scalabilityMode ?? 'L3T3';
|
468
|
+
|
469
|
+
// add backup codec track
|
470
|
+
const simOpts = { ...opts };
|
471
|
+
simOpts.simulcast = true;
|
472
|
+
simOpts.scalabilityMode = undefined;
|
473
|
+
simEncodings = computeVideoEncodings(
|
474
|
+
track.source === Track.Source.ScreenShare,
|
475
|
+
width,
|
476
|
+
height,
|
477
|
+
simOpts,
|
478
|
+
);
|
479
|
+
}
|
480
|
+
|
481
|
+
// set vp8 codec as backup for any other codecs
|
482
|
+
if (opts.videoCodec && opts.videoCodec !== 'vp8') {
|
483
|
+
req.simulcastCodecs = [
|
484
|
+
{
|
485
|
+
codec: opts.videoCodec,
|
486
|
+
cid: track.mediaStreamTrack.id,
|
487
|
+
enableSimulcastLayers: true,
|
488
|
+
},
|
489
|
+
{
|
490
|
+
codec: compatibleCodec,
|
491
|
+
cid: '',
|
492
|
+
enableSimulcastLayers: true,
|
493
|
+
},
|
494
|
+
];
|
495
|
+
}
|
459
496
|
}
|
460
497
|
|
461
498
|
encodings = computeVideoEncodings(
|
@@ -501,22 +538,6 @@ export default class LocalParticipant extends Participant {
|
|
501
538
|
track.codec = opts.videoCodec;
|
502
539
|
}
|
503
540
|
|
504
|
-
const localTrack = track as LocalVideoTrack;
|
505
|
-
if (simulcastTracks) {
|
506
|
-
for await (const simulcastTrack of simulcastTracks) {
|
507
|
-
const simTransceiverInit: RTCRtpTransceiverInit = { direction: 'sendonly' };
|
508
|
-
if (simulcastTrack.encodings) {
|
509
|
-
simTransceiverInit.sendEncodings = simulcastTrack.encodings;
|
510
|
-
}
|
511
|
-
const simTransceiver = await this.engine.publisher!.pc.addTransceiver(
|
512
|
-
simulcastTrack.mediaStreamTrack,
|
513
|
-
simTransceiverInit,
|
514
|
-
);
|
515
|
-
this.setPreferredCodec(simTransceiver, localTrack.kind, simulcastTrack.codec);
|
516
|
-
localTrack.setSimulcastTrackSender(simulcastTrack.codec, simTransceiver.sender);
|
517
|
-
}
|
518
|
-
}
|
519
|
-
|
520
541
|
this.engine.negotiate();
|
521
542
|
|
522
543
|
// store RTPSender
|
@@ -534,6 +555,91 @@ export default class LocalParticipant extends Participant {
|
|
534
555
|
return publication;
|
535
556
|
}
|
536
557
|
|
558
|
+
/** @internal
|
559
|
+
* publish additional codec to existing track
|
560
|
+
*/
|
561
|
+
async publishAdditionalCodecForTrack(
|
562
|
+
track: LocalTrack | MediaStreamTrack,
|
563
|
+
videoCodec: VideoCodec,
|
564
|
+
options?: TrackPublishOptions,
|
565
|
+
) {
|
566
|
+
const opts: TrackPublishOptions = {
|
567
|
+
...this.roomOptions?.publishDefaults,
|
568
|
+
...options,
|
569
|
+
};
|
570
|
+
// clear scalabilityMode setting for backup codec
|
571
|
+
opts.scalabilityMode = undefined;
|
572
|
+
opts.videoCodec = videoCodec;
|
573
|
+
// is it not published? if so skip
|
574
|
+
let existingPublication: LocalTrackPublication | undefined;
|
575
|
+
this.tracks.forEach((publication) => {
|
576
|
+
if (!publication.track) {
|
577
|
+
return;
|
578
|
+
}
|
579
|
+
if (publication.track === track) {
|
580
|
+
existingPublication = <LocalTrackPublication>publication;
|
581
|
+
}
|
582
|
+
});
|
583
|
+
if (!existingPublication) {
|
584
|
+
throw new TrackInvalidError('track is not published');
|
585
|
+
}
|
586
|
+
|
587
|
+
if (!(track instanceof LocalVideoTrack)) {
|
588
|
+
throw new TrackInvalidError('track is not a video track');
|
589
|
+
}
|
590
|
+
|
591
|
+
const settings = track.mediaStreamTrack.getSettings();
|
592
|
+
const width = settings.width ?? track.dimensions?.width;
|
593
|
+
const height = settings.height ?? track.dimensions?.height;
|
594
|
+
|
595
|
+
const encodings = computeVideoEncodings(
|
596
|
+
track.source === Track.Source.ScreenShare,
|
597
|
+
width,
|
598
|
+
height,
|
599
|
+
opts,
|
600
|
+
);
|
601
|
+
const simulcastTrack = track.addSimulcastTrack(opts.videoCodec, encodings);
|
602
|
+
const req = AddTrackRequest.fromPartial({
|
603
|
+
cid: simulcastTrack.mediaStreamTrack.id,
|
604
|
+
type: Track.kindToProto(track.kind),
|
605
|
+
muted: track.isMuted,
|
606
|
+
source: Track.sourceToProto(track.source),
|
607
|
+
sid: track.sid,
|
608
|
+
simulcastCodecs: [
|
609
|
+
{
|
610
|
+
codec: opts.videoCodec,
|
611
|
+
cid: simulcastTrack.mediaStreamTrack.id,
|
612
|
+
enableSimulcastLayers: opts.simulcast,
|
613
|
+
},
|
614
|
+
],
|
615
|
+
});
|
616
|
+
req.layers = videoLayersFromEncodings(req.width, req.height, encodings);
|
617
|
+
|
618
|
+
if (!this.engine || this.engine.isClosed) {
|
619
|
+
throw new UnexpectedConnectionState('cannot publish track when not connected');
|
620
|
+
}
|
621
|
+
|
622
|
+
const ti = await this.engine.addTrack(req);
|
623
|
+
|
624
|
+
if (!this.engine.publisher) {
|
625
|
+
throw new UnexpectedConnectionState('publisher is closed');
|
626
|
+
}
|
627
|
+
const transceiverInit: RTCRtpTransceiverInit = { direction: 'sendonly' };
|
628
|
+
if (encodings) {
|
629
|
+
transceiverInit.sendEncodings = encodings;
|
630
|
+
}
|
631
|
+
// addTransceiver for react-native is async. web is synchronous, but await won't effect it.
|
632
|
+
const transceiver = await this.engine.publisher.pc.addTransceiver(
|
633
|
+
simulcastTrack.mediaStreamTrack,
|
634
|
+
transceiverInit,
|
635
|
+
);
|
636
|
+
this.setPreferredCodec(transceiver, track.kind, opts.videoCodec);
|
637
|
+
track.setSimulcastTrackSender(opts.videoCodec, transceiver.sender);
|
638
|
+
|
639
|
+
this.engine.negotiate();
|
640
|
+
log.debug(`published ${opts.videoCodec} for track ${track.sid}`, { encodings, trackInfo: ti });
|
641
|
+
}
|
642
|
+
|
537
643
|
unpublishTrack(
|
538
644
|
track: LocalTrack | MediaStreamTrack,
|
539
645
|
stopOnUnpublish?: boolean,
|
@@ -721,7 +827,7 @@ export default class LocalParticipant extends Participant {
|
|
721
827
|
this.onTrackMuted(track, track.isMuted);
|
722
828
|
};
|
723
829
|
|
724
|
-
private handleSubscribedQualityUpdate = (update: SubscribedQualityUpdate) => {
|
830
|
+
private handleSubscribedQualityUpdate = async (update: SubscribedQualityUpdate) => {
|
725
831
|
if (!this.roomOptions?.dynacast) {
|
726
832
|
return;
|
727
833
|
}
|
@@ -734,7 +840,14 @@ export default class LocalParticipant extends Participant {
|
|
734
840
|
return;
|
735
841
|
}
|
736
842
|
if (update.subscribedCodecs.length > 0) {
|
737
|
-
pub.videoTrack
|
843
|
+
if (!pub.videoTrack) {
|
844
|
+
return;
|
845
|
+
}
|
846
|
+
const newCodecs = await pub.videoTrack.setPublishingCodecs(update.subscribedCodecs);
|
847
|
+
for await (const codec of newCodecs) {
|
848
|
+
log.debug(`publish ${codec} for ${pub.videoTrack.sid}`);
|
849
|
+
await this.publishAdditionalCodecForTrack(pub.videoTrack, codec, pub.options);
|
850
|
+
}
|
738
851
|
} else if (update.subscribedQualities.length > 0) {
|
739
852
|
pub.videoTrack?.setPublishingLayers(update.subscribedQualities);
|
740
853
|
}
|
@@ -794,35 +907,36 @@ export default class LocalParticipant extends Participant {
|
|
794
907
|
const cap = RTCRtpSender.getCapabilities(kind);
|
795
908
|
if (!cap) return;
|
796
909
|
log.debug('get capabilities', cap);
|
797
|
-
|
798
|
-
const
|
910
|
+
const matched: RTCRtpCodecCapability[] = [];
|
911
|
+
const partialMatched: RTCRtpCodecCapability[] = [];
|
912
|
+
const unmatched: RTCRtpCodecCapability[] = [];
|
799
913
|
cap.codecs.forEach((c) => {
|
800
914
|
const codec = c.mimeType.toLowerCase();
|
915
|
+
if (codec === 'audio/opus') {
|
916
|
+
matched.push(c);
|
917
|
+
return;
|
918
|
+
}
|
801
919
|
const matchesVideoCodec = codec === `video/${videoCodec}`;
|
802
|
-
|
803
|
-
|
804
|
-
codecs.push(c);
|
920
|
+
if (!matchesVideoCodec) {
|
921
|
+
unmatched.push(c);
|
805
922
|
return;
|
806
923
|
}
|
807
924
|
// for h264 codecs that have sdpFmtpLine available, use only if the
|
808
925
|
// profile-level-id is 42e01f for cross-browser compatibility
|
809
|
-
if (videoCodec === 'h264'
|
810
|
-
if (
|
811
|
-
|
812
|
-
|
926
|
+
if (videoCodec === 'h264') {
|
927
|
+
if (c.sdpFmtpLine && c.sdpFmtpLine.includes('profile-level-id=42e01f')) {
|
928
|
+
matched.push(c);
|
929
|
+
} else {
|
930
|
+
partialMatched.push(c);
|
813
931
|
}
|
814
|
-
}
|
815
|
-
if (matchesVideoCodec || codec === 'audio/opus') {
|
816
|
-
selected = c;
|
817
932
|
return;
|
818
933
|
}
|
819
|
-
|
934
|
+
|
935
|
+
matched.push(c);
|
820
936
|
});
|
821
937
|
|
822
|
-
if (
|
823
|
-
|
824
|
-
codecs.unshift(selected);
|
825
|
-
transceiver.setCodecPreferences(codecs);
|
938
|
+
if ('setCodecPreferences' in transceiver) {
|
939
|
+
transceiver.setCodecPreferences(matched.concat(partialMatched, unmatched));
|
826
940
|
}
|
827
941
|
}
|
828
942
|
|
@@ -24,6 +24,8 @@ export class SimulcastTrackInfo {
|
|
24
24
|
}
|
25
25
|
}
|
26
26
|
|
27
|
+
const refreshSubscribedCodecAfterNewCodec = 5000;
|
28
|
+
|
27
29
|
export default class LocalVideoTrack extends LocalTrack {
|
28
30
|
/* internal */
|
29
31
|
signalClient?: SignalClient;
|
@@ -37,6 +39,8 @@ export default class LocalVideoTrack extends LocalTrack {
|
|
37
39
|
SimulcastTrackInfo
|
38
40
|
>();
|
39
41
|
|
42
|
+
private subscribedCodecs?: SubscribedCodec[];
|
43
|
+
|
40
44
|
constructor(mediaTrack: MediaStreamTrack, constraints?: MediaTrackConstraints) {
|
41
45
|
super(mediaTrack, Track.Kind.Video, constraints);
|
42
46
|
}
|
@@ -194,26 +198,48 @@ export default class LocalVideoTrack extends LocalTrack {
|
|
194
198
|
return;
|
195
199
|
}
|
196
200
|
simulcastCodecInfo.sender = sender;
|
201
|
+
|
202
|
+
// browser will reenable disabled codec/layers after new codec has been published,
|
203
|
+
// so refresh subscribedCodecs after publish a new codec
|
204
|
+
setTimeout(() => {
|
205
|
+
if (this.subscribedCodecs) {
|
206
|
+
this.setPublishingCodecs(this.subscribedCodecs);
|
207
|
+
}
|
208
|
+
}, refreshSubscribedCodecAfterNewCodec);
|
197
209
|
}
|
198
210
|
|
199
211
|
/**
|
200
212
|
* @internal
|
201
213
|
* Sets codecs that should be publishing
|
202
214
|
*/
|
203
|
-
async setPublishingCodecs(codecs: SubscribedCodec[]) {
|
204
|
-
log.debug('setting publishing codecs',
|
215
|
+
async setPublishingCodecs(codecs: SubscribedCodec[]): Promise<VideoCodec[]> {
|
216
|
+
log.debug('setting publishing codecs', {
|
217
|
+
codecs,
|
218
|
+
currentCodec: this.codec,
|
219
|
+
});
|
220
|
+
// only enable simulcast codec for preference codec setted
|
221
|
+
if (!this.codec && codecs.length > 0) {
|
222
|
+
await this.setPublishingLayers(codecs[0].qualities);
|
223
|
+
return [];
|
224
|
+
}
|
225
|
+
|
226
|
+
this.subscribedCodecs = codecs;
|
205
227
|
|
228
|
+
const newCodecs: VideoCodec[] = [];
|
206
229
|
for await (const codec of codecs) {
|
207
|
-
if (this.codec === codec.codec) {
|
230
|
+
if (!this.codec || this.codec === codec.codec) {
|
208
231
|
await this.setPublishingLayers(codec.qualities);
|
209
232
|
} else {
|
210
233
|
const simulcastCodecInfo = this.simulcastCodecs.get(codec.codec as VideoCodec);
|
211
234
|
log.debug(`try setPublishingCodec for ${codec.codec}`, simulcastCodecInfo);
|
212
235
|
if (!simulcastCodecInfo || !simulcastCodecInfo.sender) {
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
236
|
+
for (const q of codec.qualities) {
|
237
|
+
if (q.enabled) {
|
238
|
+
newCodecs.push(codec.codec as VideoCodec);
|
239
|
+
break;
|
240
|
+
}
|
241
|
+
}
|
242
|
+
} else if (simulcastCodecInfo.encodings) {
|
217
243
|
log.debug(`try setPublishingLayersForSender ${codec.codec}`);
|
218
244
|
await setPublishingLayersForSender(
|
219
245
|
simulcastCodecInfo.sender,
|
@@ -223,6 +249,7 @@ export default class LocalVideoTrack extends LocalTrack {
|
|
223
249
|
}
|
224
250
|
}
|
225
251
|
}
|
252
|
+
return newCodecs;
|
226
253
|
}
|
227
254
|
|
228
255
|
/**
|
@@ -1,11 +1,11 @@
|
|
1
1
|
import { debounce } from 'ts-debounce';
|
2
|
+
import log from '../../logger';
|
2
3
|
import { TrackEvent } from '../events';
|
3
4
|
import { computeBitrate, monitorFrequency, VideoReceiverStats } from '../stats';
|
4
5
|
import { getIntersectionObserver, getResizeObserver, ObservableMediaElement } from '../utils';
|
5
6
|
import RemoteTrack from './RemoteTrack';
|
6
7
|
import { attachToElement, detachTrack, Track } from './Track';
|
7
8
|
import { AdaptiveStreamSettings } from './types';
|
8
|
-
import log from '../../logger';
|
9
9
|
|
10
10
|
const REACTION_DELAY = 100;
|
11
11
|
|
@@ -305,7 +305,7 @@ class HTMLElementInfo implements ElementInfo {
|
|
305
305
|
}
|
306
306
|
|
307
307
|
height(): number {
|
308
|
-
return this.element.
|
308
|
+
return this.element.clientHeight;
|
309
309
|
}
|
310
310
|
|
311
311
|
observe() {
|
package/src/room/utils.ts
CHANGED
@@ -81,8 +81,9 @@ let emptyVideoStreamTrack: MediaStreamTrack | undefined;
|
|
81
81
|
export function getEmptyVideoStreamTrack() {
|
82
82
|
if (!emptyVideoStreamTrack) {
|
83
83
|
const canvas = document.createElement('canvas');
|
84
|
-
canvas
|
85
|
-
canvas.
|
84
|
+
// the canvas size is set to 16, because electron apps seem to fail with smaller values
|
85
|
+
canvas.width = 16;
|
86
|
+
canvas.height = 16;
|
86
87
|
canvas.getContext('2d')?.fillRect(0, 0, canvas.width, canvas.height);
|
87
88
|
// @ts-ignore
|
88
89
|
const emptyStream = canvas.captureStream();
|