livekit-client 1.0.1 → 1.0.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 +792 -197
- 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/{api → src/api}/RequestQueue.d.ts +0 -0
- package/dist/src/api/RequestQueue.d.ts.map +1 -0
- package/dist/{api → src/api}/SignalClient.d.ts +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -0
- package/dist/{index.d.ts → src/index.d.ts} +0 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/{logger.d.ts → src/logger.d.ts} +0 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/{options.d.ts → src/options.d.ts} +0 -0
- package/dist/src/options.d.ts.map +1 -0
- package/dist/{proto → src/proto}/google/protobuf/timestamp.d.ts +0 -0
- package/dist/src/proto/google/protobuf/timestamp.d.ts.map +1 -0
- package/dist/{proto → src/proto}/livekit_models.d.ts +80 -0
- package/dist/src/proto/livekit_models.d.ts.map +1 -0
- package/dist/{proto → src/proto}/livekit_rtc.d.ts +661 -0
- package/dist/src/proto/livekit_rtc.d.ts.map +1 -0
- package/dist/{room → src/room}/DeviceManager.d.ts +0 -0
- package/dist/src/room/DeviceManager.d.ts.map +1 -0
- package/dist/{room → src/room}/PCTransport.d.ts +0 -0
- package/dist/src/room/PCTransport.d.ts.map +1 -0
- package/dist/{room → src/room}/RTCEngine.d.ts +1 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -0
- package/dist/{room → src/room}/Room.d.ts +2 -0
- package/dist/src/room/Room.d.ts.map +1 -0
- package/dist/{room → src/room}/errors.d.ts +0 -0
- package/dist/src/room/errors.d.ts.map +1 -0
- package/dist/{room → src/room}/events.d.ts +5 -1
- package/dist/src/room/events.d.ts.map +1 -0
- package/dist/{room → src/room}/participant/LocalParticipant.d.ts +4 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -0
- package/dist/{room → src/room}/participant/Participant.d.ts +0 -0
- package/dist/src/room/participant/Participant.d.ts.map +1 -0
- package/dist/{room → src/room}/participant/ParticipantTrackPermission.d.ts +0 -0
- package/dist/src/room/participant/ParticipantTrackPermission.d.ts.map +1 -0
- package/dist/{room → src/room}/participant/RemoteParticipant.d.ts +0 -0
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -0
- package/dist/{room → src/room}/participant/publishUtils.d.ts +0 -0
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -0
- package/dist/{room → src/room}/stats.d.ts +1 -0
- package/dist/src/room/stats.d.ts.map +1 -0
- package/dist/{room → src/room}/track/LocalAudioTrack.d.ts +0 -0
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -0
- package/dist/{room → src/room}/track/LocalTrack.d.ts +3 -0
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -0
- package/dist/{room → src/room}/track/LocalTrackPublication.d.ts +0 -0
- package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -0
- package/dist/{room → src/room}/track/LocalVideoTrack.d.ts +17 -2
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -0
- package/dist/{room → src/room}/track/RemoteAudioTrack.d.ts +0 -0
- package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -0
- package/dist/{room → src/room}/track/RemoteTrack.d.ts +0 -1
- package/dist/src/room/track/RemoteTrack.d.ts.map +1 -0
- package/dist/{room → src/room}/track/RemoteTrackPublication.d.ts +0 -0
- package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -0
- package/dist/{room → src/room}/track/RemoteVideoTrack.d.ts +2 -0
- package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -0
- package/dist/{room → src/room}/track/Track.d.ts +4 -0
- package/dist/src/room/track/Track.d.ts.map +1 -0
- package/dist/{room → src/room}/track/TrackPublication.d.ts +0 -0
- package/dist/src/room/track/TrackPublication.d.ts.map +1 -0
- package/dist/{room → src/room}/track/create.d.ts +0 -0
- package/dist/src/room/track/create.d.ts.map +1 -0
- package/dist/{room → src/room}/track/defaults.d.ts +0 -0
- package/dist/src/room/track/defaults.d.ts.map +1 -0
- package/dist/{room → src/room}/track/options.d.ts +2 -1
- package/dist/src/room/track/options.d.ts.map +1 -0
- package/dist/{room → src/room}/track/types.d.ts +0 -0
- package/dist/src/room/track/types.d.ts.map +1 -0
- package/dist/{room → src/room}/track/utils.d.ts +0 -0
- package/dist/src/room/track/utils.d.ts.map +1 -0
- package/dist/{room → src/room}/utils.d.ts +0 -0
- package/dist/src/room/utils.d.ts.map +1 -0
- package/dist/{test → src/test}/MockMediaStreamTrack.d.ts +0 -0
- package/dist/src/test/MockMediaStreamTrack.d.ts.map +1 -0
- package/dist/{test → src/test}/mocks.d.ts +0 -0
- package/dist/src/test/mocks.d.ts.map +1 -0
- package/dist/src/version.d.ts +3 -0
- package/dist/src/version.d.ts.map +1 -0
- package/package.json +5 -2
- package/src/api/SignalClient.ts +2 -2
- package/src/proto/livekit_models.ts +90 -0
- package/src/proto/livekit_rtc.ts +235 -1
- package/src/room/RTCEngine.ts +30 -2
- package/src/room/Room.ts +60 -15
- package/src/room/events.ts +5 -0
- package/src/room/participant/LocalParticipant.ts +104 -23
- package/src/room/participant/RemoteParticipant.ts +1 -0
- package/src/room/stats.ts +2 -0
- package/src/room/track/LocalAudioTrack.ts +4 -0
- package/src/room/track/LocalTrack.ts +12 -5
- package/src/room/track/LocalVideoTrack.ts +143 -55
- package/src/room/track/RemoteTrack.ts +0 -2
- package/src/room/track/RemoteVideoTrack.ts +6 -0
- package/src/room/track/Track.ts +5 -0
- package/src/room/track/options.ts +2 -1
- package/src/version.ts +4 -2
- package/dist/api/RequestQueue.d.ts.map +0 -1
- package/dist/api/SignalClient.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/options.d.ts.map +0 -1
- package/dist/proto/google/protobuf/timestamp.d.ts.map +0 -1
- package/dist/proto/livekit_models.d.ts.map +0 -1
- package/dist/proto/livekit_rtc.d.ts.map +0 -1
- package/dist/room/DeviceManager.d.ts.map +0 -1
- package/dist/room/PCTransport.d.ts.map +0 -1
- package/dist/room/RTCEngine.d.ts.map +0 -1
- package/dist/room/Room.d.ts.map +0 -1
- package/dist/room/errors.d.ts.map +0 -1
- package/dist/room/events.d.ts.map +0 -1
- package/dist/room/participant/LocalParticipant.d.ts.map +0 -1
- package/dist/room/participant/Participant.d.ts.map +0 -1
- package/dist/room/participant/ParticipantTrackPermission.d.ts.map +0 -1
- package/dist/room/participant/RemoteParticipant.d.ts.map +0 -1
- package/dist/room/participant/publishUtils.d.ts.map +0 -1
- package/dist/room/stats.d.ts.map +0 -1
- package/dist/room/track/LocalAudioTrack.d.ts.map +0 -1
- package/dist/room/track/LocalTrack.d.ts.map +0 -1
- package/dist/room/track/LocalTrackPublication.d.ts.map +0 -1
- package/dist/room/track/LocalVideoTrack.d.ts.map +0 -1
- package/dist/room/track/RemoteAudioTrack.d.ts.map +0 -1
- package/dist/room/track/RemoteTrack.d.ts.map +0 -1
- package/dist/room/track/RemoteTrackPublication.d.ts.map +0 -1
- package/dist/room/track/RemoteVideoTrack.d.ts.map +0 -1
- package/dist/room/track/Track.d.ts.map +0 -1
- package/dist/room/track/TrackPublication.d.ts.map +0 -1
- package/dist/room/track/create.d.ts.map +0 -1
- package/dist/room/track/defaults.d.ts.map +0 -1
- package/dist/room/track/options.d.ts.map +0 -1
- package/dist/room/track/types.d.ts.map +0 -1
- package/dist/room/track/utils.d.ts.map +0 -1
- package/dist/room/utils.d.ts.map +0 -1
- package/dist/test/MockMediaStreamTrack.d.ts.map +0 -1
- package/dist/test/mocks.d.ts.map +0 -1
- package/dist/version.d.ts +0 -3
- package/dist/version.d.ts.map +0 -1
package/src/room/Room.ts
CHANGED
@@ -27,7 +27,9 @@ import Participant, { ConnectionQuality } from './participant/Participant';
|
|
27
27
|
import RemoteParticipant from './participant/RemoteParticipant';
|
28
28
|
import RTCEngine, { maxICEConnectTimeout } from './RTCEngine';
|
29
29
|
import { audioDefaults, publishDefaults, videoDefaults } from './track/defaults';
|
30
|
+
import LocalAudioTrack from './track/LocalAudioTrack';
|
30
31
|
import LocalTrackPublication from './track/LocalTrackPublication';
|
32
|
+
import LocalVideoTrack from './track/LocalVideoTrack';
|
31
33
|
import RemoteTrackPublication from './track/RemoteTrackPublication';
|
32
34
|
import { Track } from './track/Track';
|
33
35
|
import { TrackPublication } from './track/TrackPublication';
|
@@ -84,6 +86,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
84
86
|
/** options of room */
|
85
87
|
options: RoomOptions;
|
86
88
|
|
89
|
+
private identityToSid: Map<string, string>;
|
90
|
+
|
87
91
|
/** connect options of room */
|
88
92
|
private connOptions?: RoomConnectOptions;
|
89
93
|
|
@@ -101,6 +105,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
101
105
|
constructor(options?: RoomOptions) {
|
102
106
|
super();
|
103
107
|
this.participants = new Map();
|
108
|
+
this.identityToSid = new Map();
|
104
109
|
this.options = options || {};
|
105
110
|
|
106
111
|
switch (this.options?.publishDefaults?.videoCodec) {
|
@@ -157,8 +162,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
157
162
|
.on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate)
|
158
163
|
.on(EngineEvent.DataPacketReceived, this.handleDataPacket)
|
159
164
|
.on(EngineEvent.Resuming, () => {
|
160
|
-
this.setAndEmitConnectionState(ConnectionState.Reconnecting)
|
161
|
-
|
165
|
+
if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
|
166
|
+
this.emit(RoomEvent.Reconnecting);
|
167
|
+
}
|
162
168
|
})
|
163
169
|
.on(EngineEvent.Resumed, () => {
|
164
170
|
this.setAndEmitConnectionState(ConnectionState.Connected);
|
@@ -287,6 +293,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
287
293
|
this.name = joinResponse.room!.name;
|
288
294
|
this.sid = joinResponse.room!.sid;
|
289
295
|
this.metadata = joinResponse.room!.metadata;
|
296
|
+
this.emit(RoomEvent.SignalConnected);
|
290
297
|
} catch (err) {
|
291
298
|
this.engine.close();
|
292
299
|
this.setAndEmitConnectionState(ConnectionState.Disconnected);
|
@@ -338,10 +345,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
338
345
|
return;
|
339
346
|
}
|
340
347
|
// send leave
|
341
|
-
if (this.engine) {
|
348
|
+
if (this.engine?.client.isConnected) {
|
342
349
|
this.engine.client.sendLeave();
|
350
|
+
}
|
351
|
+
// close engine (also closes client)
|
352
|
+
if (this.engine) {
|
343
353
|
this.engine.close();
|
344
354
|
}
|
355
|
+
|
345
356
|
this.handleDisconnect(stopTracks);
|
346
357
|
/* @ts-ignore */
|
347
358
|
this.engine = undefined;
|
@@ -353,20 +364,20 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
353
364
|
* @returns
|
354
365
|
*/
|
355
366
|
getParticipantByIdentity(identity: string): Participant | undefined {
|
356
|
-
for (const [, p] of this.participants) {
|
357
|
-
if (p.identity === identity) {
|
358
|
-
return p;
|
359
|
-
}
|
360
|
-
}
|
361
367
|
if (this.localParticipant.identity === identity) {
|
362
368
|
return this.localParticipant;
|
363
369
|
}
|
370
|
+
const sid = this.identityToSid.get(identity);
|
371
|
+
if (sid) {
|
372
|
+
return this.participants.get(sid);
|
373
|
+
}
|
364
374
|
}
|
365
375
|
|
366
376
|
/**
|
367
377
|
* @internal for testing
|
368
378
|
*/
|
369
379
|
simulateScenario(scenario: string) {
|
380
|
+
let postAction = () => {};
|
370
381
|
let req: SimulateScenario | undefined;
|
371
382
|
switch (scenario) {
|
372
383
|
case 'speaker':
|
@@ -389,10 +400,19 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
389
400
|
migration: true,
|
390
401
|
});
|
391
402
|
break;
|
403
|
+
case 'switch-candidate':
|
404
|
+
req = SimulateScenario.fromPartial({
|
405
|
+
switchCandidateProtocol: 1,
|
406
|
+
});
|
407
|
+
postAction = () => {
|
408
|
+
this.engine.publisher?.createAndSendOffer({ iceRestart: true });
|
409
|
+
};
|
410
|
+
break;
|
392
411
|
default:
|
393
412
|
}
|
394
413
|
if (req) {
|
395
414
|
this.engine.client.sendSimulateScenario(req);
|
415
|
+
postAction();
|
396
416
|
}
|
397
417
|
}
|
398
418
|
|
@@ -512,8 +532,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
512
532
|
}
|
513
533
|
|
514
534
|
private handleRestarting = () => {
|
515
|
-
this.setAndEmitConnectionState(ConnectionState.Reconnecting)
|
516
|
-
|
535
|
+
if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
|
536
|
+
this.emit(RoomEvent.Reconnecting);
|
537
|
+
}
|
517
538
|
|
518
539
|
// also unwind existing participants & existing subscriptions
|
519
540
|
for (const p of this.participants.values()) {
|
@@ -522,7 +543,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
522
543
|
};
|
523
544
|
|
524
545
|
private handleRestarted = async (joinResponse: JoinResponse) => {
|
525
|
-
log.debug(`reconnected to server
|
546
|
+
log.debug(`reconnected to server`, {
|
547
|
+
region: joinResponse.serverRegion,
|
548
|
+
});
|
526
549
|
this.setAndEmitConnectionState(ConnectionState.Connected);
|
527
550
|
this.emit(RoomEvent.Reconnected);
|
528
551
|
|
@@ -546,7 +569,17 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
546
569
|
localPubs.map(async (pub) => {
|
547
570
|
const track = pub.track!;
|
548
571
|
this.localParticipant.unpublishTrack(track, false);
|
549
|
-
|
572
|
+
if (!track.isMuted) {
|
573
|
+
if (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) {
|
574
|
+
// we need to restart the track before publishing, often a full reconnect
|
575
|
+
// is necessary because computer had gone to sleep.
|
576
|
+
log.debug('restarting existing track', {
|
577
|
+
track: pub.trackSid,
|
578
|
+
});
|
579
|
+
await track.restartTrack();
|
580
|
+
}
|
581
|
+
await this.localParticipant.publishTrack(track, pub.options);
|
582
|
+
}
|
550
583
|
}),
|
551
584
|
);
|
552
585
|
};
|
@@ -593,6 +626,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
593
626
|
return;
|
594
627
|
}
|
595
628
|
|
629
|
+
// ensure identity <=> sid mapping
|
630
|
+
const sid = this.identityToSid.get(info.identity);
|
631
|
+
if (sid && sid !== info.sid) {
|
632
|
+
// sid had changed, need to remove previous participant
|
633
|
+
this.handleParticipantDisconnected(sid, this.participants.get(sid));
|
634
|
+
}
|
635
|
+
|
596
636
|
let remoteParticipant = this.participants.get(info.sid);
|
597
637
|
const isNewParticipant = !remoteParticipant;
|
598
638
|
|
@@ -603,6 +643,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
603
643
|
if (info.state === ParticipantInfo_State.DISCONNECTED) {
|
604
644
|
this.handleParticipantDisconnected(info.sid, remoteParticipant);
|
605
645
|
} else if (isNewParticipant) {
|
646
|
+
this.identityToSid.set(info.identity, info.sid);
|
606
647
|
// fire connected event
|
607
648
|
this.emit(RoomEvent.ParticipantConnected, remoteParticipant);
|
608
649
|
} else {
|
@@ -619,8 +660,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
619
660
|
return;
|
620
661
|
}
|
621
662
|
|
663
|
+
this.identityToSid.delete(participant.identity);
|
622
664
|
participant.tracks.forEach((publication) => {
|
623
|
-
participant.unpublishTrack(publication.trackSid);
|
665
|
+
participant.unpublishTrack(publication.trackSid, true);
|
624
666
|
});
|
625
667
|
this.emit(RoomEvent.ParticipantDisconnected, participant);
|
626
668
|
}
|
@@ -913,12 +955,14 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
913
955
|
}
|
914
956
|
}
|
915
957
|
|
916
|
-
private setAndEmitConnectionState(state: ConnectionState) {
|
958
|
+
private setAndEmitConnectionState(state: ConnectionState): boolean {
|
917
959
|
if (state === this.state) {
|
918
|
-
|
960
|
+
// unchanged
|
961
|
+
return false;
|
919
962
|
}
|
920
963
|
this.state = state;
|
921
964
|
this.emit(RoomEvent.ConnectionStateChanged, this.state);
|
965
|
+
return true;
|
922
966
|
}
|
923
967
|
|
924
968
|
// /** @internal */
|
@@ -991,4 +1035,5 @@ export type RoomEventCallbacks = {
|
|
991
1035
|
participant: RemoteParticipant,
|
992
1036
|
) => void;
|
993
1037
|
audioPlaybackChanged: (playing: boolean) => void;
|
1038
|
+
signalConnected: () => void;
|
994
1039
|
};
|
package/src/room/events.ts
CHANGED
@@ -230,6 +230,11 @@ export enum RoomEvent {
|
|
230
230
|
* args: (prevPermissions: [[ParticipantPermission]], participant: [[Participant]])
|
231
231
|
*/
|
232
232
|
ParticipantPermissionsChanged = 'participantPermissionsChanged',
|
233
|
+
|
234
|
+
/**
|
235
|
+
* Signal connected, can publish tracks.
|
236
|
+
*/
|
237
|
+
SignalConnected = 'signalConnected',
|
233
238
|
}
|
234
239
|
|
235
240
|
export enum ParticipantEvent {
|
@@ -10,12 +10,15 @@ import {
|
|
10
10
|
TrackUnpublishedResponse,
|
11
11
|
} from '../../proto/livekit_rtc';
|
12
12
|
import { TrackInvalidError, UnexpectedConnectionState } from '../errors';
|
13
|
-
import { ParticipantEvent, TrackEvent } from '../events';
|
13
|
+
import { EngineEvent, ParticipantEvent, TrackEvent } from '../events';
|
14
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, {
|
18
|
+
import LocalVideoTrack, {
|
19
|
+
SimulcastTrackInfo,
|
20
|
+
videoLayersFromEncodings,
|
21
|
+
} from '../track/LocalVideoTrack';
|
19
22
|
import {
|
20
23
|
CreateLocalTracksOptions,
|
21
24
|
ScreenShareCaptureOptions,
|
@@ -31,6 +34,7 @@ import { ParticipantTrackPermission, trackPermissionToProto } from './Participan
|
|
31
34
|
import { computeVideoEncodings, mediaTrackToLocalTrack } from './publishUtils';
|
32
35
|
import RemoteParticipant from './RemoteParticipant';
|
33
36
|
|
37
|
+
const compatibleCodecForSVC = 'vp8';
|
34
38
|
export default class LocalParticipant extends Participant {
|
35
39
|
audioTracks: Map<string, LocalTrackPublication>;
|
36
40
|
|
@@ -47,6 +51,10 @@ export default class LocalParticipant extends Participant {
|
|
47
51
|
|
48
52
|
private engine: RTCEngine;
|
49
53
|
|
54
|
+
private participantTrackPermissions: Array<ParticipantTrackPermission> = [];
|
55
|
+
|
56
|
+
private allParticipantsAllowedToSubscribe: boolean = true;
|
57
|
+
|
50
58
|
// keep a pointer to room options
|
51
59
|
private roomOptions?: RoomOptions;
|
52
60
|
|
@@ -74,6 +82,11 @@ export default class LocalParticipant extends Participant {
|
|
74
82
|
this.engine.client.onSubscribedQualityUpdate = this.handleSubscribedQualityUpdate;
|
75
83
|
|
76
84
|
this.engine.client.onLocalTrackUnpublished = this.handleLocalTrackUnpublished;
|
85
|
+
|
86
|
+
this.engine
|
87
|
+
.on(EngineEvent.Connected, this.updateTrackSubscriptionPermissions)
|
88
|
+
.on(EngineEvent.Restarted, this.updateTrackSubscriptionPermissions)
|
89
|
+
.on(EngineEvent.Resumed, this.updateTrackSubscriptionPermissions);
|
77
90
|
}
|
78
91
|
|
79
92
|
get lastCameraError(): Error | undefined {
|
@@ -384,7 +397,7 @@ export default class LocalParticipant extends Participant {
|
|
384
397
|
// handle track actions
|
385
398
|
track.on(TrackEvent.Muted, this.onTrackMuted);
|
386
399
|
track.on(TrackEvent.Unmuted, this.onTrackUnmuted);
|
387
|
-
track.on(TrackEvent.Ended, this.
|
400
|
+
track.on(TrackEvent.Ended, this.handleTrackEnded);
|
388
401
|
track.on(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
|
389
402
|
track.on(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
|
390
403
|
|
@@ -401,6 +414,8 @@ export default class LocalParticipant extends Participant {
|
|
401
414
|
|
402
415
|
// compute encodings and layers for video
|
403
416
|
let encodings: RTCRtpEncodingParameters[] | undefined;
|
417
|
+
let simEncodings: RTCRtpEncodingParameters[] | undefined;
|
418
|
+
let simulcastTracks: SimulcastTrackInfo[] | undefined;
|
404
419
|
if (track.kind === Track.Kind.Video) {
|
405
420
|
// TODO: support react native, which doesn't expose getSettings
|
406
421
|
const settings = track.mediaStreamTrack.getSettings();
|
@@ -409,16 +424,38 @@ export default class LocalParticipant extends Participant {
|
|
409
424
|
// width and height should be defined for video
|
410
425
|
req.width = width ?? 0;
|
411
426
|
req.height = height ?? 0;
|
412
|
-
// for svc codecs, disable simulcast and
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
}
|
427
|
+
// for svc codecs, disable simulcast and use vp8 for backup codec
|
428
|
+
if (
|
429
|
+
track instanceof LocalVideoTrack &&
|
430
|
+
(opts?.videoCodec === 'vp9' || opts?.videoCodec === 'av1')
|
431
|
+
) {
|
432
|
+
// set scalabilityMode to 'L3T3' by default
|
433
|
+
opts.scalabilityMode = opts.scalabilityMode ?? 'L3T3';
|
434
|
+
|
435
|
+
// add backup codec track
|
436
|
+
const simOpts = { ...opts };
|
437
|
+
simOpts.simulcast = true;
|
438
|
+
simOpts.scalabilityMode = undefined;
|
439
|
+
simEncodings = computeVideoEncodings(
|
440
|
+
track.source === Track.Source.ScreenShare,
|
441
|
+
width,
|
442
|
+
height,
|
443
|
+
simOpts,
|
444
|
+
);
|
445
|
+
const simulcastTrack = track.addSimulcastTrack(compatibleCodecForSVC, simEncodings);
|
446
|
+
simulcastTracks = [simulcastTrack];
|
447
|
+
req.simulcastCodecs = [
|
448
|
+
{
|
449
|
+
codec: opts.videoCodec,
|
450
|
+
cid: track.mediaStreamTrack.id,
|
451
|
+
enableSimulcastLayers: true,
|
452
|
+
},
|
453
|
+
{
|
454
|
+
codec: simulcastTrack.codec,
|
455
|
+
cid: simulcastTrack.mediaStreamTrack.id,
|
456
|
+
enableSimulcastLayers: true,
|
457
|
+
},
|
458
|
+
];
|
422
459
|
}
|
423
460
|
|
424
461
|
encodings = computeVideoEncodings(
|
@@ -427,7 +464,7 @@ export default class LocalParticipant extends Participant {
|
|
427
464
|
height,
|
428
465
|
opts,
|
429
466
|
);
|
430
|
-
req.layers = videoLayersFromEncodings(req.width, req.height, encodings);
|
467
|
+
req.layers = videoLayersFromEncodings(req.width, req.height, simEncodings ?? encodings);
|
431
468
|
} else if (track.kind === Track.Kind.Audio && opts.audioBitrate) {
|
432
469
|
encodings = [
|
433
470
|
{
|
@@ -442,6 +479,8 @@ export default class LocalParticipant extends Participant {
|
|
442
479
|
|
443
480
|
const ti = await this.engine.addTrack(req);
|
444
481
|
const publication = new LocalTrackPublication(track.kind, ti, track);
|
482
|
+
// save options for when it needs to be republished again
|
483
|
+
publication.options = opts;
|
445
484
|
track.sid = ti.sid;
|
446
485
|
|
447
486
|
if (!this.engine.publisher) {
|
@@ -452,13 +491,32 @@ export default class LocalParticipant extends Participant {
|
|
452
491
|
if (encodings) {
|
453
492
|
transceiverInit.sendEncodings = encodings;
|
454
493
|
}
|
455
|
-
|
494
|
+
// addTransceiver for react-native is async. web is synchronous, but await won't effect it.
|
495
|
+
const transceiver = await this.engine.publisher.pc.addTransceiver(
|
456
496
|
track.mediaStreamTrack,
|
457
497
|
transceiverInit,
|
458
498
|
);
|
459
|
-
if (opts.videoCodec) {
|
499
|
+
if (track.kind === Track.Kind.Video && opts.videoCodec) {
|
460
500
|
this.setPreferredCodec(transceiver, track.kind, opts.videoCodec);
|
501
|
+
track.codec = opts.videoCodec;
|
461
502
|
}
|
503
|
+
|
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
|
+
|
462
520
|
this.engine.negotiate();
|
463
521
|
|
464
522
|
// store RTPSender
|
@@ -468,6 +526,7 @@ export default class LocalParticipant extends Participant {
|
|
468
526
|
} else if (track instanceof LocalAudioTrack) {
|
469
527
|
track.startMonitor();
|
470
528
|
}
|
529
|
+
|
471
530
|
this.addTrackPublication(publication);
|
472
531
|
|
473
532
|
// send event for publication
|
@@ -494,9 +553,10 @@ export default class LocalParticipant extends Participant {
|
|
494
553
|
|
495
554
|
track = publication.track;
|
496
555
|
|
556
|
+
track.sender = undefined;
|
497
557
|
track.off(TrackEvent.Muted, this.onTrackMuted);
|
498
558
|
track.off(TrackEvent.Unmuted, this.onTrackUnmuted);
|
499
|
-
track.off(TrackEvent.Ended, this.
|
559
|
+
track.off(TrackEvent.Ended, this.handleTrackEnded);
|
500
560
|
track.off(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
|
501
561
|
track.off(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
|
502
562
|
|
@@ -509,7 +569,7 @@ export default class LocalParticipant extends Participant {
|
|
509
569
|
|
510
570
|
const { mediaStreamTrack } = track;
|
511
571
|
|
512
|
-
if (this.engine.publisher) {
|
572
|
+
if (this.engine.publisher && this.engine.publisher.pc.connectionState !== 'closed') {
|
513
573
|
const senders = this.engine.publisher.pc.getSenders();
|
514
574
|
senders.forEach((sender) => {
|
515
575
|
if (sender.track === mediaStreamTrack) {
|
@@ -613,11 +673,23 @@ export default class LocalParticipant extends Participant {
|
|
613
673
|
allParticipantsAllowed: boolean,
|
614
674
|
participantTrackPermissions: ParticipantTrackPermission[] = [],
|
615
675
|
) {
|
676
|
+
this.participantTrackPermissions = participantTrackPermissions;
|
677
|
+
this.allParticipantsAllowedToSubscribe = allParticipantsAllowed;
|
678
|
+
if (this.engine.client.isConnected) {
|
679
|
+
this.updateTrackSubscriptionPermissions();
|
680
|
+
}
|
681
|
+
}
|
682
|
+
|
683
|
+
private updateTrackSubscriptionPermissions = () => {
|
684
|
+
log.debug('updating track subscription permissions', {
|
685
|
+
allParticipantsAllowed: this.allParticipantsAllowedToSubscribe,
|
686
|
+
participantTrackPermissions: this.participantTrackPermissions,
|
687
|
+
});
|
616
688
|
this.engine.client.sendUpdateSubscriptionPermissions(
|
617
|
-
|
618
|
-
participantTrackPermissions.map((p) => trackPermissionToProto(p)),
|
689
|
+
this.allParticipantsAllowedToSubscribe,
|
690
|
+
this.participantTrackPermissions.map((p) => trackPermissionToProto(p)),
|
619
691
|
);
|
620
|
-
}
|
692
|
+
};
|
621
693
|
|
622
694
|
/** @internal */
|
623
695
|
private onTrackUnmuted = (track: LocalTrack) => {
|
@@ -661,7 +733,11 @@ export default class LocalParticipant extends Participant {
|
|
661
733
|
});
|
662
734
|
return;
|
663
735
|
}
|
664
|
-
|
736
|
+
if (update.subscribedCodecs.length > 0) {
|
737
|
+
pub.videoTrack?.setPublishingCodecs(update.subscribedCodecs);
|
738
|
+
} else if (update.subscribedQualities.length > 0) {
|
739
|
+
pub.videoTrack?.setPublishingLayers(update.subscribedQualities);
|
740
|
+
}
|
665
741
|
};
|
666
742
|
|
667
743
|
private handleLocalTrackUnpublished = (unpublished: TrackUnpublishedResponse) => {
|
@@ -676,7 +752,10 @@ export default class LocalParticipant extends Participant {
|
|
676
752
|
this.unpublishTrack(track.track!);
|
677
753
|
};
|
678
754
|
|
679
|
-
private
|
755
|
+
private handleTrackEnded = (track: LocalTrack) => {
|
756
|
+
log.debug('unpublishing local track due to TrackEnded', {
|
757
|
+
track: track.sid,
|
758
|
+
});
|
680
759
|
this.unpublishTrack(track);
|
681
760
|
};
|
682
761
|
|
@@ -714,6 +793,7 @@ export default class LocalParticipant extends Participant {
|
|
714
793
|
}
|
715
794
|
const cap = RTCRtpSender.getCapabilities(kind);
|
716
795
|
if (!cap) return;
|
796
|
+
log.debug('get capabilities', cap);
|
717
797
|
let selected: RTCRtpCodecCapability | undefined;
|
718
798
|
const codecs: RTCRtpCodecCapability[] = [];
|
719
799
|
cap.codecs.forEach((c) => {
|
@@ -738,6 +818,7 @@ export default class LocalParticipant extends Participant {
|
|
738
818
|
}
|
739
819
|
codecs.push(c);
|
740
820
|
});
|
821
|
+
|
741
822
|
if (selected && 'setCodecPreferences' in transceiver) {
|
742
823
|
// @ts-ignore
|
743
824
|
codecs.unshift(selected);
|
@@ -42,6 +42,7 @@ export default class RemoteParticipant extends Participant {
|
|
42
42
|
|
43
43
|
// register action events
|
44
44
|
publication.on(TrackEvent.UpdateSettings, (settings: UpdateTrackSettings) => {
|
45
|
+
log.debug('send update settings', settings);
|
45
46
|
this.signalClient.sendUpdateTrackSettings(settings);
|
46
47
|
});
|
47
48
|
publication.on(TrackEvent.UpdateSubscription, (sub: UpdateSubscription) => {
|
package/src/room/stats.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import log from '../../logger';
|
2
2
|
import { TrackEvent } from '../events';
|
3
3
|
import { AudioSenderStats, computeBitrate, monitorFrequency } from '../stats';
|
4
|
+
import { isWeb } from '../utils';
|
4
5
|
import LocalTrack from './LocalTrack';
|
5
6
|
import { AudioCaptureOptions } from './options';
|
6
7
|
import { Track } from './Track';
|
@@ -68,6 +69,9 @@ export default class LocalAudioTrack extends LocalTrack {
|
|
68
69
|
|
69
70
|
/* @internal */
|
70
71
|
startMonitor() {
|
72
|
+
if (!isWeb()) {
|
73
|
+
return;
|
74
|
+
}
|
71
75
|
setTimeout(() => {
|
72
76
|
this.monitorSender();
|
73
77
|
}, monitorFrequency);
|
@@ -2,6 +2,7 @@ import log from '../../logger';
|
|
2
2
|
import DeviceManager from '../DeviceManager';
|
3
3
|
import { TrackInvalidError } from '../errors';
|
4
4
|
import { TrackEvent } from '../events';
|
5
|
+
import { VideoCodec } from './options';
|
5
6
|
import { getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, isMobile } from '../utils';
|
6
7
|
import { attachToElement, detachTrack, Track } from './Track';
|
7
8
|
|
@@ -9,6 +10,9 @@ export default class LocalTrack extends Track {
|
|
9
10
|
/** @internal */
|
10
11
|
sender?: RTCRtpSender;
|
11
12
|
|
13
|
+
/** @internal */
|
14
|
+
codec?: VideoCodec;
|
15
|
+
|
12
16
|
protected constraints: MediaTrackConstraints;
|
13
17
|
|
14
18
|
protected wasMuted: boolean;
|
@@ -94,7 +98,9 @@ export default class LocalTrack extends Track {
|
|
94
98
|
track.addEventListener('ended', this.handleEnded);
|
95
99
|
log.debug('replace MediaStreamTrack');
|
96
100
|
|
97
|
-
|
101
|
+
if (this.sender) {
|
102
|
+
await this.sender.replaceTrack(track);
|
103
|
+
}
|
98
104
|
this._mediaStreamTrack = track;
|
99
105
|
|
100
106
|
this.attachedElements.forEach((el) => {
|
@@ -106,9 +112,6 @@ export default class LocalTrack extends Track {
|
|
106
112
|
}
|
107
113
|
|
108
114
|
protected async restart(constraints?: MediaTrackConstraints): Promise<LocalTrack> {
|
109
|
-
if (!this.sender) {
|
110
|
-
throw new TrackInvalidError('unable to restart an unpublished track');
|
111
|
-
}
|
112
115
|
if (!constraints) {
|
113
116
|
constraints = this.constraints;
|
114
117
|
}
|
@@ -141,7 +144,11 @@ export default class LocalTrack extends Track {
|
|
141
144
|
newTrack.addEventListener('ended', this.handleEnded);
|
142
145
|
log.debug('re-acquired MediaStreamTrack');
|
143
146
|
|
144
|
-
|
147
|
+
if (this.sender) {
|
148
|
+
// Track can be restarted after it's unpublished
|
149
|
+
await this.sender.replaceTrack(newTrack);
|
150
|
+
}
|
151
|
+
|
145
152
|
this._mediaStreamTrack = newTrack;
|
146
153
|
|
147
154
|
this.attachedElements.forEach((el) => {
|