livekit-client 1.1.1 → 1.1.4
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 +299 -122
- 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 +2 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +7 -2
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +4 -2
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +3 -2
- 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 +1 -1
- package/src/proto/livekit_rtc.ts +12 -0
- package/src/room/Room.ts +79 -30
- package/src/room/participant/LocalParticipant.ts +135 -61
- package/src/room/participant/RemoteParticipant.ts +5 -11
- package/src/room/participant/publishUtils.ts +2 -2
- package/src/room/track/LocalAudioTrack.ts +7 -3
- package/src/room/track/LocalTrack.ts +11 -2
- package/src/room/track/LocalVideoTrack.ts +41 -10
- package/src/room/track/RemoteVideoTrack.ts +30 -2
- package/src/room/track/create.ts +2 -2
- package/src/room/utils.ts +3 -2
package/src/room/Room.ts
CHANGED
@@ -108,14 +108,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
108
108
|
this.identityToSid = new Map();
|
109
109
|
this.options = options || {};
|
110
110
|
|
111
|
-
switch (this.options?.publishDefaults?.videoCodec) {
|
112
|
-
case 'av1':
|
113
|
-
case 'vp9':
|
114
|
-
this.options.publishDefaults.simulcast = undefined;
|
115
|
-
break;
|
116
|
-
default:
|
117
|
-
}
|
118
|
-
|
119
111
|
this.options.audioCaptureDefaults = {
|
120
112
|
...audioDefaults,
|
121
113
|
...options?.audioCaptureDefaults,
|
@@ -178,6 +170,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
178
170
|
})
|
179
171
|
.on(EngineEvent.Restarting, this.handleRestarting)
|
180
172
|
.on(EngineEvent.Restarted, this.handleRestarted);
|
173
|
+
|
174
|
+
if (this.localParticipant) {
|
175
|
+
this.localParticipant.engine = this.engine;
|
176
|
+
}
|
181
177
|
}
|
182
178
|
|
183
179
|
/**
|
@@ -295,7 +291,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
295
291
|
this.metadata = joinResponse.room!.metadata;
|
296
292
|
this.emit(RoomEvent.SignalConnected);
|
297
293
|
} catch (err) {
|
298
|
-
this.
|
294
|
+
this.recreateEngine();
|
299
295
|
this.setAndEmitConnectionState(ConnectionState.Disconnected);
|
300
296
|
throw err;
|
301
297
|
}
|
@@ -304,14 +300,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
304
300
|
return new Promise<Room>((resolve, reject) => {
|
305
301
|
const connectTimeout = setTimeout(() => {
|
306
302
|
// timeout
|
307
|
-
this.
|
303
|
+
this.recreateEngine();
|
308
304
|
this.setAndEmitConnectionState(ConnectionState.Disconnected);
|
309
305
|
reject(new ConnectionError('could not connect after timeout'));
|
310
306
|
}, maxICEConnectTimeout);
|
311
307
|
const abortHandler = () => {
|
312
308
|
log.warn('closing engine');
|
313
309
|
clearTimeout(connectTimeout);
|
314
|
-
this.
|
310
|
+
this.recreateEngine();
|
315
311
|
this.setAndEmitConnectionState(ConnectionState.Disconnected);
|
316
312
|
reject(new ConnectionError('room connection has been cancelled'));
|
317
313
|
};
|
@@ -503,11 +499,37 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
503
499
|
}
|
504
500
|
}
|
505
501
|
|
502
|
+
private recreateEngine() {
|
503
|
+
this.engine.close();
|
504
|
+
/* @ts-ignore */
|
505
|
+
this.engine = undefined;
|
506
|
+
|
507
|
+
// clear out existing remote participants, since they may have attached
|
508
|
+
// the old engine
|
509
|
+
this.participants.clear();
|
510
|
+
|
511
|
+
this.createEngine();
|
512
|
+
}
|
513
|
+
|
506
514
|
private onTrackAdded(
|
507
515
|
mediaTrack: MediaStreamTrack,
|
508
516
|
stream: MediaStream,
|
509
517
|
receiver?: RTCRtpReceiver,
|
510
518
|
) {
|
519
|
+
// don't fire onSubscribed when connecting
|
520
|
+
// WebRTC fires onTrack as soon as setRemoteDescription is called on the offer
|
521
|
+
// at that time, ICE connectivity has not been established so the track is not
|
522
|
+
// technically subscribed.
|
523
|
+
// We'll defer these events until when the room is connected or eventually disconnected.
|
524
|
+
if (this.state === ConnectionState.Connecting || this.state === ConnectionState.Reconnecting) {
|
525
|
+
setTimeout(() => {
|
526
|
+
this.onTrackAdded(mediaTrack, stream, receiver);
|
527
|
+
}, 50);
|
528
|
+
return;
|
529
|
+
}
|
530
|
+
if (this.state === ConnectionState.Disconnected) {
|
531
|
+
log.warn('skipping incoming track after Room disconnected');
|
532
|
+
}
|
511
533
|
const parts = unpackStreamId(stream.id);
|
512
534
|
const participantId = parts[0];
|
513
535
|
let trackId = parts[1];
|
@@ -532,14 +554,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
532
554
|
}
|
533
555
|
|
534
556
|
private handleRestarting = () => {
|
535
|
-
if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
|
536
|
-
this.emit(RoomEvent.Reconnecting);
|
537
|
-
}
|
538
|
-
|
539
557
|
// also unwind existing participants & existing subscriptions
|
540
558
|
for (const p of this.participants.values()) {
|
541
559
|
this.handleParticipantDisconnected(p.sid, p);
|
542
560
|
}
|
561
|
+
|
562
|
+
if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
|
563
|
+
this.emit(RoomEvent.Reconnecting);
|
564
|
+
}
|
543
565
|
};
|
544
566
|
|
545
567
|
private handleRestarted = async (joinResponse: JoinResponse) => {
|
@@ -570,7 +592,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
570
592
|
const track = pub.track!;
|
571
593
|
this.localParticipant.unpublishTrack(track, false);
|
572
594
|
if (!track.isMuted) {
|
573
|
-
if (
|
595
|
+
if (
|
596
|
+
(track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) &&
|
597
|
+
!track.isUserProvided
|
598
|
+
) {
|
574
599
|
// we need to restart the track before publishing, often a full reconnect
|
575
600
|
// is necessary because computer had gone to sleep.
|
576
601
|
log.debug('restarting existing track', {
|
@@ -644,7 +669,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
644
669
|
this.handleParticipantDisconnected(info.sid, remoteParticipant);
|
645
670
|
} else if (isNewParticipant) {
|
646
671
|
// fire connected event
|
647
|
-
this.
|
672
|
+
this.emitWhenConnected(RoomEvent.ParticipantConnected, remoteParticipant);
|
648
673
|
} else {
|
649
674
|
// just update, no events
|
650
675
|
remoteParticipant.updateInfo(info);
|
@@ -663,7 +688,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
663
688
|
participant.tracks.forEach((publication) => {
|
664
689
|
participant.unpublishTrack(publication.trackSid, true);
|
665
690
|
});
|
666
|
-
this.
|
691
|
+
this.emitWhenConnected(RoomEvent.ParticipantDisconnected, participant);
|
667
692
|
}
|
668
693
|
|
669
694
|
// updates are sent only when there's a change to speaker ordering
|
@@ -698,7 +723,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
698
723
|
});
|
699
724
|
|
700
725
|
this.activeSpeakers = activeSpeakers;
|
701
|
-
this.
|
726
|
+
this.emitWhenConnected(RoomEvent.ActiveSpeakersChanged, activeSpeakers);
|
702
727
|
};
|
703
728
|
|
704
729
|
// process list of changed speakers
|
@@ -727,7 +752,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
727
752
|
const activeSpeakers = Array.from(lastSpeakers.values());
|
728
753
|
activeSpeakers.sort((a, b) => b.audioLevel - a.audioLevel);
|
729
754
|
this.activeSpeakers = activeSpeakers;
|
730
|
-
this.
|
755
|
+
this.emitWhenConnected(RoomEvent.ActiveSpeakersChanged, activeSpeakers);
|
731
756
|
};
|
732
757
|
|
733
758
|
private handleStreamStateUpdate = (streamStateUpdate: StreamStateUpdate) => {
|
@@ -742,7 +767,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
742
767
|
}
|
743
768
|
pub.track.streamState = Track.streamStateFromProto(streamState.state);
|
744
769
|
participant.emit(ParticipantEvent.TrackStreamStateChanged, pub, pub.track.streamState);
|
745
|
-
this.
|
770
|
+
this.emitWhenConnected(
|
771
|
+
ParticipantEvent.TrackStreamStateChanged,
|
772
|
+
pub,
|
773
|
+
pub.track.streamState,
|
774
|
+
participant,
|
775
|
+
);
|
746
776
|
});
|
747
777
|
};
|
748
778
|
|
@@ -762,7 +792,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
762
792
|
pub,
|
763
793
|
pub.subscriptionStatus,
|
764
794
|
);
|
765
|
-
this.
|
795
|
+
this.emitWhenConnected(
|
766
796
|
ParticipantEvent.TrackSubscriptionPermissionChanged,
|
767
797
|
pub,
|
768
798
|
pub.subscriptionStatus,
|
@@ -803,7 +833,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
803
833
|
|
804
834
|
private handleRoomUpdate = (r: RoomModel) => {
|
805
835
|
this.metadata = r.metadata;
|
806
|
-
this.
|
836
|
+
this.emitWhenConnected(RoomEvent.RoomMetadataChanged, r.metadata);
|
807
837
|
};
|
808
838
|
|
809
839
|
private handleConnectionQualityUpdate = (update: ConnectionQualityUpdate) => {
|
@@ -858,7 +888,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
858
888
|
// and remote participant joined the room
|
859
889
|
participant
|
860
890
|
.on(ParticipantEvent.TrackPublished, (trackPublication: RemoteTrackPublication) => {
|
861
|
-
this.
|
891
|
+
this.emitWhenConnected(RoomEvent.TrackPublished, trackPublication, participant);
|
862
892
|
})
|
863
893
|
.on(
|
864
894
|
ParticipantEvent.TrackSubscribed,
|
@@ -872,7 +902,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
872
902
|
},
|
873
903
|
)
|
874
904
|
.on(ParticipantEvent.TrackUnpublished, (publication: RemoteTrackPublication) => {
|
875
|
-
this.
|
905
|
+
this.emitWhenConnected(RoomEvent.TrackUnpublished, publication, participant);
|
876
906
|
})
|
877
907
|
.on(
|
878
908
|
ParticipantEvent.TrackUnsubscribed,
|
@@ -884,23 +914,32 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
884
914
|
this.emit(RoomEvent.TrackSubscriptionFailed, sid, participant);
|
885
915
|
})
|
886
916
|
.on(ParticipantEvent.TrackMuted, (pub: TrackPublication) => {
|
887
|
-
this.
|
917
|
+
this.emitWhenConnected(RoomEvent.TrackMuted, pub, participant);
|
888
918
|
})
|
889
919
|
.on(ParticipantEvent.TrackUnmuted, (pub: TrackPublication) => {
|
890
|
-
this.
|
920
|
+
this.emitWhenConnected(RoomEvent.TrackUnmuted, pub, participant);
|
891
921
|
})
|
892
922
|
.on(ParticipantEvent.ParticipantMetadataChanged, (metadata: string | undefined) => {
|
893
|
-
this.
|
923
|
+
this.emitWhenConnected(RoomEvent.ParticipantMetadataChanged, metadata, participant);
|
894
924
|
})
|
895
925
|
.on(ParticipantEvent.ConnectionQualityChanged, (quality: ConnectionQuality) => {
|
896
|
-
this.
|
926
|
+
this.emitWhenConnected(RoomEvent.ConnectionQualityChanged, quality, participant);
|
897
927
|
})
|
898
928
|
.on(
|
899
929
|
ParticipantEvent.ParticipantPermissionsChanged,
|
900
930
|
(prevPermissions: ParticipantPermission) => {
|
901
|
-
this.
|
931
|
+
this.emitWhenConnected(
|
932
|
+
RoomEvent.ParticipantPermissionsChanged,
|
933
|
+
prevPermissions,
|
934
|
+
participant,
|
935
|
+
);
|
902
936
|
},
|
903
937
|
);
|
938
|
+
|
939
|
+
// update info at the end after callbacks have been set up
|
940
|
+
if (info) {
|
941
|
+
participant.updateInfo(info);
|
942
|
+
}
|
904
943
|
return participant;
|
905
944
|
}
|
906
945
|
|
@@ -967,6 +1006,16 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
967
1006
|
return true;
|
968
1007
|
}
|
969
1008
|
|
1009
|
+
private emitWhenConnected<E extends keyof RoomEventCallbacks>(
|
1010
|
+
event: E,
|
1011
|
+
...args: Parameters<RoomEventCallbacks[E]>
|
1012
|
+
): boolean {
|
1013
|
+
if (this.state === ConnectionState.Connected) {
|
1014
|
+
return this.emit(event, ...args);
|
1015
|
+
}
|
1016
|
+
return false;
|
1017
|
+
}
|
1018
|
+
|
970
1019
|
// /** @internal */
|
971
1020
|
emit<E extends keyof RoomEventCallbacks>(
|
972
1021
|
event: E,
|
@@ -15,10 +15,7 @@ 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 {
|
23
20
|
AudioCaptureOptions,
|
24
21
|
CreateLocalTracksOptions,
|
@@ -36,7 +33,7 @@ import { ParticipantTrackPermission, trackPermissionToProto } from './Participan
|
|
36
33
|
import { computeVideoEncodings, mediaTrackToLocalTrack } from './publishUtils';
|
37
34
|
import RemoteParticipant from './RemoteParticipant';
|
38
35
|
|
39
|
-
const
|
36
|
+
const compatibleCodec = 'vp8';
|
40
37
|
export default class LocalParticipant extends Participant {
|
41
38
|
audioTracks: Map<string, LocalTrackPublication>;
|
42
39
|
|
@@ -45,14 +42,15 @@ export default class LocalParticipant extends Participant {
|
|
45
42
|
/** map of track sid => all published tracks */
|
46
43
|
tracks: Map<string, LocalTrackPublication>;
|
47
44
|
|
45
|
+
/** @internal */
|
46
|
+
engine: RTCEngine;
|
47
|
+
|
48
48
|
private pendingPublishing = new Set<Track.Source>();
|
49
49
|
|
50
50
|
private cameraError: Error | undefined;
|
51
51
|
|
52
52
|
private microphoneError: Error | undefined;
|
53
53
|
|
54
|
-
private engine: RTCEngine;
|
55
|
-
|
56
54
|
private participantTrackPermissions: Array<ParticipantTrackPermission> = [];
|
57
55
|
|
58
56
|
private allParticipantsAllowedToSubscribe: boolean = true;
|
@@ -369,11 +367,11 @@ export default class LocalParticipant extends Participant {
|
|
369
367
|
if (tracks.length === 0) {
|
370
368
|
throw new TrackInvalidError('no video track found');
|
371
369
|
}
|
372
|
-
const screenVideo = new LocalVideoTrack(tracks[0]);
|
370
|
+
const screenVideo = new LocalVideoTrack(tracks[0], undefined, false);
|
373
371
|
screenVideo.source = Track.Source.ScreenShare;
|
374
372
|
const localTracks: Array<LocalTrack> = [screenVideo];
|
375
373
|
if (stream.getAudioTracks().length > 0) {
|
376
|
-
const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0]);
|
374
|
+
const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0], undefined, false);
|
377
375
|
screenAudio.source = Track.Source.ScreenShareAudio;
|
378
376
|
localTracks.push(screenAudio);
|
379
377
|
}
|
@@ -398,10 +396,10 @@ export default class LocalParticipant extends Participant {
|
|
398
396
|
if (track instanceof MediaStreamTrack) {
|
399
397
|
switch (track.kind) {
|
400
398
|
case 'audio':
|
401
|
-
track = new LocalAudioTrack(track);
|
399
|
+
track = new LocalAudioTrack(track, undefined, true);
|
402
400
|
break;
|
403
401
|
case 'video':
|
404
|
-
track = new LocalVideoTrack(track);
|
402
|
+
track = new LocalVideoTrack(track, undefined, true);
|
405
403
|
break;
|
406
404
|
default:
|
407
405
|
throw new TrackInvalidError(`unsupported MediaStreamTrack kind ${track.kind}`);
|
@@ -455,7 +453,6 @@ export default class LocalParticipant extends Participant {
|
|
455
453
|
// compute encodings and layers for video
|
456
454
|
let encodings: RTCRtpEncodingParameters[] | undefined;
|
457
455
|
let simEncodings: RTCRtpEncodingParameters[] | undefined;
|
458
|
-
let simulcastTracks: SimulcastTrackInfo[] | undefined;
|
459
456
|
if (track.kind === Track.Kind.Video) {
|
460
457
|
// TODO: support react native, which doesn't expose getSettings
|
461
458
|
const settings = track.mediaStreamTrack.getSettings();
|
@@ -465,37 +462,38 @@ export default class LocalParticipant extends Participant {
|
|
465
462
|
req.width = width ?? 0;
|
466
463
|
req.height = height ?? 0;
|
467
464
|
// for svc codecs, disable simulcast and use vp8 for backup codec
|
468
|
-
if (
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
465
|
+
if (track instanceof LocalVideoTrack) {
|
466
|
+
if (opts?.videoCodec === 'vp9' || opts?.videoCodec === 'av1') {
|
467
|
+
// set scalabilityMode to 'L3T3' by default
|
468
|
+
opts.scalabilityMode = opts.scalabilityMode ?? 'L3T3';
|
469
|
+
|
470
|
+
// add backup codec track
|
471
|
+
const simOpts = { ...opts };
|
472
|
+
simOpts.simulcast = true;
|
473
|
+
simOpts.scalabilityMode = undefined;
|
474
|
+
simEncodings = computeVideoEncodings(
|
475
|
+
track.source === Track.Source.ScreenShare,
|
476
|
+
width,
|
477
|
+
height,
|
478
|
+
simOpts,
|
479
|
+
);
|
480
|
+
}
|
481
|
+
|
482
|
+
// set vp8 codec as backup for any other codecs
|
483
|
+
if (opts.videoCodec && opts.videoCodec !== 'vp8') {
|
484
|
+
req.simulcastCodecs = [
|
485
|
+
{
|
486
|
+
codec: opts.videoCodec,
|
487
|
+
cid: track.mediaStreamTrack.id,
|
488
|
+
enableSimulcastLayers: true,
|
489
|
+
},
|
490
|
+
{
|
491
|
+
codec: compatibleCodec,
|
492
|
+
cid: '',
|
493
|
+
enableSimulcastLayers: true,
|
494
|
+
},
|
495
|
+
];
|
496
|
+
}
|
499
497
|
}
|
500
498
|
|
501
499
|
encodings = computeVideoEncodings(
|
@@ -541,22 +539,6 @@ export default class LocalParticipant extends Participant {
|
|
541
539
|
track.codec = opts.videoCodec;
|
542
540
|
}
|
543
541
|
|
544
|
-
const localTrack = track as LocalVideoTrack;
|
545
|
-
if (simulcastTracks) {
|
546
|
-
for await (const simulcastTrack of simulcastTracks) {
|
547
|
-
const simTransceiverInit: RTCRtpTransceiverInit = { direction: 'sendonly' };
|
548
|
-
if (simulcastTrack.encodings) {
|
549
|
-
simTransceiverInit.sendEncodings = simulcastTrack.encodings;
|
550
|
-
}
|
551
|
-
const simTransceiver = await this.engine.publisher!.pc.addTransceiver(
|
552
|
-
simulcastTrack.mediaStreamTrack,
|
553
|
-
simTransceiverInit,
|
554
|
-
);
|
555
|
-
this.setPreferredCodec(simTransceiver, localTrack.kind, simulcastTrack.codec);
|
556
|
-
localTrack.setSimulcastTrackSender(simulcastTrack.codec, simTransceiver.sender);
|
557
|
-
}
|
558
|
-
}
|
559
|
-
|
560
542
|
this.engine.negotiate();
|
561
543
|
|
562
544
|
// store RTPSender
|
@@ -574,6 +556,91 @@ export default class LocalParticipant extends Participant {
|
|
574
556
|
return publication;
|
575
557
|
}
|
576
558
|
|
559
|
+
/** @internal
|
560
|
+
* publish additional codec to existing track
|
561
|
+
*/
|
562
|
+
async publishAdditionalCodecForTrack(
|
563
|
+
track: LocalTrack | MediaStreamTrack,
|
564
|
+
videoCodec: VideoCodec,
|
565
|
+
options?: TrackPublishOptions,
|
566
|
+
) {
|
567
|
+
const opts: TrackPublishOptions = {
|
568
|
+
...this.roomOptions?.publishDefaults,
|
569
|
+
...options,
|
570
|
+
};
|
571
|
+
// clear scalabilityMode setting for backup codec
|
572
|
+
opts.scalabilityMode = undefined;
|
573
|
+
opts.videoCodec = videoCodec;
|
574
|
+
// is it not published? if so skip
|
575
|
+
let existingPublication: LocalTrackPublication | undefined;
|
576
|
+
this.tracks.forEach((publication) => {
|
577
|
+
if (!publication.track) {
|
578
|
+
return;
|
579
|
+
}
|
580
|
+
if (publication.track === track) {
|
581
|
+
existingPublication = <LocalTrackPublication>publication;
|
582
|
+
}
|
583
|
+
});
|
584
|
+
if (!existingPublication) {
|
585
|
+
throw new TrackInvalidError('track is not published');
|
586
|
+
}
|
587
|
+
|
588
|
+
if (!(track instanceof LocalVideoTrack)) {
|
589
|
+
throw new TrackInvalidError('track is not a video track');
|
590
|
+
}
|
591
|
+
|
592
|
+
const settings = track.mediaStreamTrack.getSettings();
|
593
|
+
const width = settings.width ?? track.dimensions?.width;
|
594
|
+
const height = settings.height ?? track.dimensions?.height;
|
595
|
+
|
596
|
+
const encodings = computeVideoEncodings(
|
597
|
+
track.source === Track.Source.ScreenShare,
|
598
|
+
width,
|
599
|
+
height,
|
600
|
+
opts,
|
601
|
+
);
|
602
|
+
const simulcastTrack = track.addSimulcastTrack(opts.videoCodec, encodings);
|
603
|
+
const req = AddTrackRequest.fromPartial({
|
604
|
+
cid: simulcastTrack.mediaStreamTrack.id,
|
605
|
+
type: Track.kindToProto(track.kind),
|
606
|
+
muted: track.isMuted,
|
607
|
+
source: Track.sourceToProto(track.source),
|
608
|
+
sid: track.sid,
|
609
|
+
simulcastCodecs: [
|
610
|
+
{
|
611
|
+
codec: opts.videoCodec,
|
612
|
+
cid: simulcastTrack.mediaStreamTrack.id,
|
613
|
+
enableSimulcastLayers: opts.simulcast,
|
614
|
+
},
|
615
|
+
],
|
616
|
+
});
|
617
|
+
req.layers = videoLayersFromEncodings(req.width, req.height, encodings);
|
618
|
+
|
619
|
+
if (!this.engine || this.engine.isClosed) {
|
620
|
+
throw new UnexpectedConnectionState('cannot publish track when not connected');
|
621
|
+
}
|
622
|
+
|
623
|
+
const ti = await this.engine.addTrack(req);
|
624
|
+
|
625
|
+
if (!this.engine.publisher) {
|
626
|
+
throw new UnexpectedConnectionState('publisher is closed');
|
627
|
+
}
|
628
|
+
const transceiverInit: RTCRtpTransceiverInit = { direction: 'sendonly' };
|
629
|
+
if (encodings) {
|
630
|
+
transceiverInit.sendEncodings = encodings;
|
631
|
+
}
|
632
|
+
// addTransceiver for react-native is async. web is synchronous, but await won't effect it.
|
633
|
+
const transceiver = await this.engine.publisher.pc.addTransceiver(
|
634
|
+
simulcastTrack.mediaStreamTrack,
|
635
|
+
transceiverInit,
|
636
|
+
);
|
637
|
+
this.setPreferredCodec(transceiver, track.kind, opts.videoCodec);
|
638
|
+
track.setSimulcastTrackSender(opts.videoCodec, transceiver.sender);
|
639
|
+
|
640
|
+
this.engine.negotiate();
|
641
|
+
log.debug(`published ${opts.videoCodec} for track ${track.sid}`, { encodings, trackInfo: ti });
|
642
|
+
}
|
643
|
+
|
577
644
|
unpublishTrack(
|
578
645
|
track: LocalTrack | MediaStreamTrack,
|
579
646
|
stopOnUnpublish?: boolean,
|
@@ -761,7 +828,7 @@ export default class LocalParticipant extends Participant {
|
|
761
828
|
this.onTrackMuted(track, track.isMuted);
|
762
829
|
};
|
763
830
|
|
764
|
-
private handleSubscribedQualityUpdate = (update: SubscribedQualityUpdate) => {
|
831
|
+
private handleSubscribedQualityUpdate = async (update: SubscribedQualityUpdate) => {
|
765
832
|
if (!this.roomOptions?.dynacast) {
|
766
833
|
return;
|
767
834
|
}
|
@@ -774,7 +841,14 @@ export default class LocalParticipant extends Participant {
|
|
774
841
|
return;
|
775
842
|
}
|
776
843
|
if (update.subscribedCodecs.length > 0) {
|
777
|
-
pub.videoTrack
|
844
|
+
if (!pub.videoTrack) {
|
845
|
+
return;
|
846
|
+
}
|
847
|
+
const newCodecs = await pub.videoTrack.setPublishingCodecs(update.subscribedCodecs);
|
848
|
+
for await (const codec of newCodecs) {
|
849
|
+
log.debug(`publish ${codec} for ${pub.videoTrack.sid}`);
|
850
|
+
await this.publishAdditionalCodecForTrack(pub.videoTrack, codec, pub.options);
|
851
|
+
}
|
778
852
|
} else if (update.subscribedQualities.length > 0) {
|
779
853
|
pub.videoTrack?.setPublishingLayers(update.subscribedQualities);
|
780
854
|
}
|
@@ -23,9 +23,7 @@ export default class RemoteParticipant extends Participant {
|
|
23
23
|
|
24
24
|
/** @internal */
|
25
25
|
static fromParticipantInfo(signalClient: SignalClient, pi: ParticipantInfo): RemoteParticipant {
|
26
|
-
|
27
|
-
rp.updateInfo(pi);
|
28
|
-
return rp;
|
26
|
+
return new RemoteParticipant(signalClient, pi.sid, pi.identity);
|
29
27
|
}
|
30
28
|
|
31
29
|
/** @internal */
|
@@ -182,8 +180,6 @@ export default class RemoteParticipant extends Participant {
|
|
182
180
|
|
183
181
|
/** @internal */
|
184
182
|
updateInfo(info: ParticipantInfo) {
|
185
|
-
const alreadyHasMetadata = this.hasMetadata;
|
186
|
-
|
187
183
|
super.updateInfo(info);
|
188
184
|
|
189
185
|
// we are getting a list of all available tracks, reconcile in here
|
@@ -212,12 +208,10 @@ export default class RemoteParticipant extends Participant {
|
|
212
208
|
validTracks.set(ti.sid, publication);
|
213
209
|
});
|
214
210
|
|
215
|
-
//
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
});
|
220
|
-
}
|
211
|
+
// always emit events for new publications, Room will not forward them unless it's ready
|
212
|
+
newTracks.forEach((publication) => {
|
213
|
+
this.emit(ParticipantEvent.TrackPublished, publication);
|
214
|
+
});
|
221
215
|
|
222
216
|
// detect removed tracks
|
223
217
|
this.tracks.forEach((publication) => {
|
@@ -18,9 +18,9 @@ export function mediaTrackToLocalTrack(
|
|
18
18
|
): LocalVideoTrack | LocalAudioTrack {
|
19
19
|
switch (mediaStreamTrack.kind) {
|
20
20
|
case 'audio':
|
21
|
-
return new LocalAudioTrack(mediaStreamTrack, constraints);
|
21
|
+
return new LocalAudioTrack(mediaStreamTrack, constraints, false);
|
22
22
|
case 'video':
|
23
|
-
return new LocalVideoTrack(mediaStreamTrack, constraints);
|
23
|
+
return new LocalVideoTrack(mediaStreamTrack, constraints, false);
|
24
24
|
default:
|
25
25
|
throw new TrackInvalidError(`unsupported track type: ${mediaStreamTrack.kind}`);
|
26
26
|
}
|
@@ -15,8 +15,12 @@ export default class LocalAudioTrack extends LocalTrack {
|
|
15
15
|
|
16
16
|
private prevStats?: AudioSenderStats;
|
17
17
|
|
18
|
-
constructor(
|
19
|
-
|
18
|
+
constructor(
|
19
|
+
mediaTrack: MediaStreamTrack,
|
20
|
+
constraints?: MediaTrackConstraints,
|
21
|
+
userProvidedTrack = true,
|
22
|
+
) {
|
23
|
+
super(mediaTrack, Track.Kind.Audio, constraints, userProvidedTrack);
|
20
24
|
this.checkForSilence();
|
21
25
|
}
|
22
26
|
|
@@ -42,7 +46,7 @@ export default class LocalAudioTrack extends LocalTrack {
|
|
42
46
|
}
|
43
47
|
|
44
48
|
async unmute(): Promise<LocalAudioTrack> {
|
45
|
-
if (this.source === Track.Source.Microphone && this.stopOnMute) {
|
49
|
+
if (this.source === Track.Source.Microphone && this.stopOnMute && !this.isUserProvided) {
|
46
50
|
log.debug('reacquiring mic track');
|
47
51
|
await this.restartTrack();
|
48
52
|
}
|
@@ -19,16 +19,20 @@ export default class LocalTrack extends Track {
|
|
19
19
|
|
20
20
|
protected reacquireTrack: boolean;
|
21
21
|
|
22
|
+
protected providedByUser: boolean;
|
23
|
+
|
22
24
|
protected constructor(
|
23
25
|
mediaTrack: MediaStreamTrack,
|
24
26
|
kind: Track.Kind,
|
25
27
|
constraints?: MediaTrackConstraints,
|
28
|
+
userProvidedTrack = false,
|
26
29
|
) {
|
27
30
|
super(mediaTrack, kind);
|
28
31
|
this._mediaStreamTrack.addEventListener('ended', this.handleEnded);
|
29
32
|
this.constraints = constraints ?? mediaTrack.getConstraints();
|
30
33
|
this.reacquireTrack = false;
|
31
34
|
this.wasMuted = false;
|
35
|
+
this.providedByUser = userProvidedTrack;
|
32
36
|
}
|
33
37
|
|
34
38
|
get id(): string {
|
@@ -56,6 +60,10 @@ export default class LocalTrack extends Track {
|
|
56
60
|
return this._isUpstreamPaused;
|
57
61
|
}
|
58
62
|
|
63
|
+
get isUserProvided() {
|
64
|
+
return this.providedByUser;
|
65
|
+
}
|
66
|
+
|
59
67
|
/**
|
60
68
|
* @returns DeviceID of the device that is currently being used for this track
|
61
69
|
*/
|
@@ -80,7 +88,7 @@ export default class LocalTrack extends Track {
|
|
80
88
|
return this;
|
81
89
|
}
|
82
90
|
|
83
|
-
async replaceTrack(track: MediaStreamTrack): Promise<LocalTrack> {
|
91
|
+
async replaceTrack(track: MediaStreamTrack, userProvidedTrack = true): Promise<LocalTrack> {
|
84
92
|
if (!this.sender) {
|
85
93
|
throw new TrackInvalidError('unable to replace an unpublished track');
|
86
94
|
}
|
@@ -108,6 +116,7 @@ export default class LocalTrack extends Track {
|
|
108
116
|
});
|
109
117
|
|
110
118
|
this.mediaStream = new MediaStream([track]);
|
119
|
+
this.providedByUser = userProvidedTrack;
|
111
120
|
return this;
|
112
121
|
}
|
113
122
|
|
@@ -184,7 +193,7 @@ export default class LocalTrack extends Track {
|
|
184
193
|
if (!isMobile()) return;
|
185
194
|
log.debug(`visibility changed, is in Background: ${this.isInBackground}`);
|
186
195
|
|
187
|
-
if (!this.isInBackground && this.needsReAcquisition) {
|
196
|
+
if (!this.isInBackground && this.needsReAcquisition && !this.isUserProvided) {
|
188
197
|
log.debug(`track needs to be reaquired, restarting ${this.source}`);
|
189
198
|
await this.restart();
|
190
199
|
this.reacquireTrack = false;
|