livekit-client 1.14.1 → 1.14.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.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +25 -44
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +399 -196
- 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/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/utils.d.ts +0 -1
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
- package/dist/src/proto/livekit_models_pb.d.ts +87 -11
- package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
- package/dist/src/proto/livekit_rtc_pb.d.ts +0 -4
- package/dist/src/proto/livekit_rtc_pb.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +20 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +1 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/defaults.d.ts +1 -0
- package/dist/src/room/defaults.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/timers.d.ts +1 -1
- package/dist/src/room/timers.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 +3 -3
- package/dist/src/room/track/LocalTrack.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/options.d.ts +0 -1
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts +2 -1
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/utils/cloneDeep.d.ts +2 -0
- package/dist/src/utils/cloneDeep.d.ts.map +1 -0
- package/dist/ts4.2/src/e2ee/utils.d.ts +0 -1
- package/dist/ts4.2/src/proto/livekit_models_pb.d.ts +87 -11
- package/dist/ts4.2/src/proto/livekit_rtc_pb.d.ts +0 -4
- package/dist/ts4.2/src/room/PCTransport.d.ts +20 -1
- package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -1
- package/dist/ts4.2/src/room/defaults.d.ts +1 -0
- package/dist/ts4.2/src/room/events.d.ts +1 -1
- package/dist/ts4.2/src/room/timers.d.ts +1 -1
- package/dist/ts4.2/src/room/track/LocalAudioTrack.d.ts +1 -1
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +3 -3
- package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +2 -1
- package/dist/ts4.2/src/room/track/options.d.ts +0 -1
- package/dist/ts4.2/src/room/track/utils.d.ts +1 -0
- package/dist/ts4.2/src/utils/cloneDeep.d.ts +2 -0
- package/package.json +14 -14
- package/src/connectionHelper/checks/webrtc.ts +1 -1
- package/src/e2ee/E2eeManager.ts +2 -1
- package/src/e2ee/utils.ts +0 -10
- package/src/e2ee/worker/FrameCryptor.ts +13 -14
- package/src/e2ee/worker/ParticipantKeyHandler.ts +4 -5
- package/src/e2ee/worker/e2ee.worker.ts +3 -1
- package/src/proto/livekit_models_pb.ts +140 -15
- package/src/proto/livekit_rtc_pb.ts +1 -7
- package/src/room/PCTransport.ts +116 -1
- package/src/room/RTCEngine.ts +49 -85
- package/src/room/Room.ts +10 -9
- package/src/room/defaults.ts +4 -2
- package/src/room/events.ts +1 -1
- package/src/room/participant/LocalParticipant.ts +44 -56
- package/src/room/track/LocalAudioTrack.ts +1 -1
- package/src/room/track/LocalTrack.ts +8 -5
- package/src/room/track/LocalVideoTrack.ts +2 -1
- package/src/room/track/options.ts +0 -7
- package/src/room/track/utils.ts +17 -8
- package/src/utils/cloneDeep.test.ts +54 -0
- package/src/utils/cloneDeep.ts +11 -0
package/src/room/RTCEngine.ts
CHANGED
@@ -108,7 +108,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
108
108
|
|
109
109
|
private subscriberPrimary: boolean = false;
|
110
110
|
|
111
|
-
private
|
111
|
+
private primaryTransport?: PCTransport;
|
112
112
|
|
113
113
|
private pcState: PCState = PCState.New;
|
114
114
|
|
@@ -247,12 +247,12 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
247
247
|
}
|
248
248
|
|
249
249
|
async cleanupPeerConnections() {
|
250
|
-
if (this.publisher && this.publisher.
|
251
|
-
this.publisher.
|
250
|
+
if (this.publisher && this.publisher.getSignallingState() !== 'closed') {
|
251
|
+
this.publisher.getSenders().forEach((sender) => {
|
252
252
|
try {
|
253
253
|
// TODO: react-native-webrtc doesn't have removeTrack yet.
|
254
|
-
if (this.publisher?.
|
255
|
-
this.publisher?.
|
254
|
+
if (this.publisher?.canRemoveTrack()) {
|
255
|
+
this.publisher?.removeTrack(sender);
|
256
256
|
}
|
257
257
|
} catch (e) {
|
258
258
|
log.warn('could not removeTrack', { error: e });
|
@@ -268,7 +268,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
268
268
|
this.subscriber = undefined;
|
269
269
|
}
|
270
270
|
this.hasPublished = false;
|
271
|
-
this.
|
271
|
+
this.primaryTransport = undefined;
|
272
272
|
|
273
273
|
const dcCleanup = (dc: RTCDataChannel | undefined) => {
|
274
274
|
if (!dc) return;
|
@@ -336,7 +336,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
336
336
|
delete this.pendingTrackResolvers[sender.track.id];
|
337
337
|
}
|
338
338
|
try {
|
339
|
-
this.publisher?.
|
339
|
+
this.publisher?.removeTrack(sender);
|
340
340
|
return true;
|
341
341
|
} catch (e: unknown) {
|
342
342
|
log.warn('failed to remove track', { error: e, method: 'removeTrack' });
|
@@ -353,10 +353,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
353
353
|
}
|
354
354
|
|
355
355
|
async getConnectedServerAddress(): Promise<string | undefined> {
|
356
|
-
if (this.
|
356
|
+
if (this.primaryTransport === undefined) {
|
357
357
|
return undefined;
|
358
358
|
}
|
359
|
-
return getConnectedAddress(
|
359
|
+
return this.primaryTransport.getConnectedAddress();
|
360
360
|
}
|
361
361
|
|
362
362
|
/* @internal */
|
@@ -387,40 +387,38 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
387
387
|
|
388
388
|
this.emit(EngineEvent.TransportsCreated, this.publisher, this.subscriber);
|
389
389
|
|
390
|
-
this.publisher.
|
391
|
-
|
392
|
-
|
393
|
-
this.client.sendIceCandidate(ev.candidate, SignalTarget.PUBLISHER);
|
390
|
+
this.publisher.onIceCandidate = (candidate) => {
|
391
|
+
log.trace('adding ICE candidate for peer', candidate);
|
392
|
+
this.client.sendIceCandidate(candidate, SignalTarget.PUBLISHER);
|
394
393
|
};
|
395
394
|
|
396
|
-
this.subscriber.
|
397
|
-
|
398
|
-
this.client.sendIceCandidate(ev.candidate, SignalTarget.SUBSCRIBER);
|
395
|
+
this.subscriber.onIceCandidate = (candidate) => {
|
396
|
+
this.client.sendIceCandidate(candidate, SignalTarget.SUBSCRIBER);
|
399
397
|
};
|
400
398
|
|
401
399
|
this.publisher.onOffer = (offer) => {
|
402
400
|
this.client.sendOffer(offer);
|
403
401
|
};
|
404
402
|
|
405
|
-
let
|
406
|
-
let
|
403
|
+
let primaryTransport = this.publisher;
|
404
|
+
let secondaryTransport = this.subscriber;
|
407
405
|
let subscriberPrimary = joinResponse.subscriberPrimary;
|
408
406
|
if (subscriberPrimary) {
|
409
|
-
|
410
|
-
|
407
|
+
primaryTransport = this.subscriber;
|
408
|
+
secondaryTransport = this.publisher;
|
411
409
|
// in subscriber primary mode, server side opens sub data channels.
|
412
|
-
this.subscriber.
|
410
|
+
this.subscriber.onDataChannel = this.handleDataChannel;
|
413
411
|
}
|
414
|
-
this.
|
415
|
-
|
416
|
-
log.debug(`primary PC state changed ${
|
417
|
-
if (
|
412
|
+
this.primaryTransport = primaryTransport;
|
413
|
+
primaryTransport.onConnectionStateChange = async (connectionState) => {
|
414
|
+
log.debug(`primary PC state changed ${connectionState}`);
|
415
|
+
if (connectionState === 'connected') {
|
418
416
|
const shouldEmit = this.pcState === PCState.New;
|
419
417
|
this.pcState = PCState.Connected;
|
420
418
|
if (shouldEmit) {
|
421
419
|
this.emit(EngineEvent.Connected, joinResponse);
|
422
420
|
}
|
423
|
-
} else if (
|
421
|
+
} else if (connectionState === 'failed') {
|
424
422
|
// on Safari, PeerConnection will switch to 'disconnected' during renegotiation
|
425
423
|
if (this.pcState === PCState.Connected) {
|
426
424
|
this.pcState = PCState.Disconnected;
|
@@ -434,10 +432,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
434
432
|
}
|
435
433
|
}
|
436
434
|
};
|
437
|
-
|
438
|
-
log.debug(`secondary PC state changed ${
|
435
|
+
secondaryTransport.onConnectionStateChange = async (connectionState) => {
|
436
|
+
log.debug(`secondary PC state changed ${connectionState}`);
|
439
437
|
// also reconnect if secondary peerconnection fails
|
440
|
-
if (
|
438
|
+
if (connectionState === 'failed') {
|
441
439
|
this.handleDisconnect(
|
442
440
|
'secondary peerconnection',
|
443
441
|
subscriberPrimary
|
@@ -447,7 +445,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
447
445
|
}
|
448
446
|
};
|
449
447
|
|
450
|
-
this.subscriber.
|
448
|
+
this.subscriber.onTrack = (ev: RTCTrackEvent) => {
|
451
449
|
this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
|
452
450
|
};
|
453
451
|
|
@@ -462,7 +460,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
462
460
|
}
|
463
461
|
log.debug('received server answer', {
|
464
462
|
RTCSdpType: sd.type,
|
465
|
-
signalingState: this.publisher.
|
463
|
+
signalingState: this.publisher.getSignallingState().toString(),
|
466
464
|
});
|
467
465
|
await this.publisher.setRemoteDescription(sd);
|
468
466
|
};
|
@@ -487,7 +485,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
487
485
|
}
|
488
486
|
log.debug('received server offer', {
|
489
487
|
RTCSdpType: sd.type,
|
490
|
-
signalingState: this.subscriber.
|
488
|
+
signalingState: this.subscriber.getSignallingState().toString(),
|
491
489
|
});
|
492
490
|
await this.subscriber.setRemoteDescription(sd);
|
493
491
|
|
@@ -518,7 +516,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
518
516
|
this.client.onLeave = (leave?: LeaveRequest) => {
|
519
517
|
if (leave?.canReconnect) {
|
520
518
|
this.fullReconnectOnNext = true;
|
521
|
-
this.
|
519
|
+
this.primaryTransport = undefined;
|
522
520
|
// reconnect immediately instead of waiting for next attempt
|
523
521
|
this.handleDisconnect(leaveReconnect);
|
524
522
|
} else {
|
@@ -579,12 +577,12 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
579
577
|
}
|
580
578
|
|
581
579
|
// create data channels
|
582
|
-
this.lossyDC = this.publisher.
|
580
|
+
this.lossyDC = this.publisher.createDataChannel(lossyDataChannel, {
|
583
581
|
// will drop older packets that arrive
|
584
582
|
ordered: true,
|
585
583
|
maxRetransmits: 0,
|
586
584
|
});
|
587
|
-
this.reliableDC = this.publisher.
|
585
|
+
this.reliableDC = this.publisher.createDataChannel(reliableDataChannel, {
|
588
586
|
ordered: true,
|
589
587
|
});
|
590
588
|
|
@@ -765,7 +763,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
765
763
|
transceiverInit.sendEncodings = encodings;
|
766
764
|
}
|
767
765
|
// addTransceiver for react-native is async. web is synchronous, but await won't effect it.
|
768
|
-
const transceiver = await this.publisher.
|
766
|
+
const transceiver = await this.publisher.addTransceiver(
|
769
767
|
track.mediaStreamTrack,
|
770
768
|
transceiverInit,
|
771
769
|
);
|
@@ -791,7 +789,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
791
789
|
transceiverInit.sendEncodings = encodings;
|
792
790
|
}
|
793
791
|
// addTransceiver for react-native is async. web is synchronous, but await won't effect it.
|
794
|
-
const transceiver = await this.publisher.
|
792
|
+
const transceiver = await this.publisher.addTransceiver(
|
795
793
|
simulcastTrack.mediaStreamTrack,
|
796
794
|
transceiverInit,
|
797
795
|
);
|
@@ -807,7 +805,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
807
805
|
if (!this.publisher) {
|
808
806
|
throw new UnexpectedConnectionState('publisher is closed');
|
809
807
|
}
|
810
|
-
return this.publisher.
|
808
|
+
return this.publisher.addTrack(track);
|
811
809
|
}
|
812
810
|
|
813
811
|
// websocket reconnect behavior. if websocket is interrupted, and the PeerConnection
|
@@ -872,7 +870,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
872
870
|
this.clientConfiguration?.resumeConnection === ClientConfigSetting.DISABLED ||
|
873
871
|
// signaling state could change to closed due to hardware sleep
|
874
872
|
// those connections cannot be resumed
|
875
|
-
(this.
|
873
|
+
(this.primaryTransport?.getSignallingState() ?? 'closed') === 'closed'
|
876
874
|
) {
|
877
875
|
this.fullReconnectOnNext = true;
|
878
876
|
}
|
@@ -999,8 +997,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
999
997
|
const res = await this.client.reconnect(this.url, this.token, this.participantSid, reason);
|
1000
998
|
if (res) {
|
1001
999
|
const rtcConfig = this.makeRTCConfiguration(res);
|
1002
|
-
this.publisher.
|
1003
|
-
this.subscriber.
|
1000
|
+
this.publisher.setConfiguration(rtcConfig);
|
1001
|
+
this.subscriber.setConfiguration(rtcConfig);
|
1004
1002
|
}
|
1005
1003
|
} catch (e) {
|
1006
1004
|
let message = '';
|
@@ -1084,7 +1082,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1084
1082
|
|
1085
1083
|
log.debug('waiting for peer connection to reconnect');
|
1086
1084
|
while (now - startTime < this.peerConnectionTimeout) {
|
1087
|
-
if (this.
|
1085
|
+
if (this.primaryTransport === undefined) {
|
1088
1086
|
// we can abort early, connection is hosed
|
1089
1087
|
break;
|
1090
1088
|
} else if (
|
@@ -1092,8 +1090,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1092
1090
|
// this means we'd have to check its status manually and update address
|
1093
1091
|
// manually
|
1094
1092
|
now - startTime > minReconnectWait &&
|
1095
|
-
this.
|
1096
|
-
(!this.hasPublished || this.publisher?.
|
1093
|
+
this.primaryTransport?.getConnectionState() === 'connected' &&
|
1094
|
+
(!this.hasPublished || this.publisher?.getConnectionState() === 'connected')
|
1097
1095
|
) {
|
1098
1096
|
this.pcState = PCState.Connected;
|
1099
1097
|
}
|
@@ -1172,7 +1170,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1172
1170
|
if (
|
1173
1171
|
!subscriber &&
|
1174
1172
|
!this.publisher?.isICEConnected &&
|
1175
|
-
this.publisher?.
|
1173
|
+
this.publisher?.getICEConnectionState() !== 'checking'
|
1176
1174
|
) {
|
1177
1175
|
// start negotiation
|
1178
1176
|
this.negotiate();
|
@@ -1196,7 +1194,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1196
1194
|
}
|
1197
1195
|
|
1198
1196
|
throw new ConnectionError(
|
1199
|
-
`could not establish ${transportName} connection, state: ${transport.
|
1197
|
+
`could not establish ${transportName} connection, state: ${transport.getICEConnectionState()}`,
|
1200
1198
|
);
|
1201
1199
|
}
|
1202
1200
|
|
@@ -1207,12 +1205,12 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1207
1205
|
/* @internal */
|
1208
1206
|
verifyTransport(): boolean {
|
1209
1207
|
// primary connection
|
1210
|
-
if (!this.
|
1208
|
+
if (!this.primaryTransport) {
|
1211
1209
|
return false;
|
1212
1210
|
}
|
1213
1211
|
if (
|
1214
|
-
this.
|
1215
|
-
this.
|
1212
|
+
this.primaryTransport.getConnectionState() === 'closed' ||
|
1213
|
+
this.primaryTransport.getConnectionState() === 'failed'
|
1216
1214
|
) {
|
1217
1215
|
return false;
|
1218
1216
|
}
|
@@ -1223,8 +1221,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1223
1221
|
return false;
|
1224
1222
|
}
|
1225
1223
|
if (
|
1226
|
-
this.publisher.
|
1227
|
-
this.publisher.
|
1224
|
+
this.publisher.getConnectionState() === 'closed' ||
|
1225
|
+
this.publisher.getConnectionState() === 'failed'
|
1228
1226
|
) {
|
1229
1227
|
return false;
|
1230
1228
|
}
|
@@ -1355,40 +1353,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1355
1353
|
}
|
1356
1354
|
}
|
1357
1355
|
|
1358
|
-
async function getConnectedAddress(pc: RTCPeerConnection): Promise<string | undefined> {
|
1359
|
-
let selectedCandidatePairId = '';
|
1360
|
-
const candidatePairs = new Map<string, RTCIceCandidatePairStats>();
|
1361
|
-
// id -> candidate ip
|
1362
|
-
const candidates = new Map<string, string>();
|
1363
|
-
const stats: RTCStatsReport = await pc.getStats();
|
1364
|
-
stats.forEach((v) => {
|
1365
|
-
switch (v.type) {
|
1366
|
-
case 'transport':
|
1367
|
-
selectedCandidatePairId = v.selectedCandidatePairId;
|
1368
|
-
break;
|
1369
|
-
case 'candidate-pair':
|
1370
|
-
if (selectedCandidatePairId === '' && v.selected) {
|
1371
|
-
selectedCandidatePairId = v.id;
|
1372
|
-
}
|
1373
|
-
candidatePairs.set(v.id, v);
|
1374
|
-
break;
|
1375
|
-
case 'remote-candidate':
|
1376
|
-
candidates.set(v.id, `${v.address}:${v.port}`);
|
1377
|
-
break;
|
1378
|
-
default:
|
1379
|
-
}
|
1380
|
-
});
|
1381
|
-
|
1382
|
-
if (selectedCandidatePairId === '') {
|
1383
|
-
return undefined;
|
1384
|
-
}
|
1385
|
-
const selectedID = candidatePairs.get(selectedCandidatePairId)?.remoteCandidateId;
|
1386
|
-
if (selectedID === undefined) {
|
1387
|
-
return undefined;
|
1388
|
-
}
|
1389
|
-
return candidates.get(selectedID);
|
1390
|
-
}
|
1391
|
-
|
1392
1356
|
class SignalReconnectError extends Error {}
|
1393
1357
|
|
1394
1358
|
export type EngineEventCallbacks = {
|
package/src/room/Room.ts
CHANGED
@@ -452,6 +452,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
452
452
|
error instanceof ConnectionError &&
|
453
453
|
(error.status === 401 || error.reason === ConnectionErrorReason.Cancelled)
|
454
454
|
) {
|
455
|
+
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
455
456
|
reject(error);
|
456
457
|
return;
|
457
458
|
}
|
@@ -462,9 +463,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
462
463
|
);
|
463
464
|
await connectFn(resolve, reject, nextUrl);
|
464
465
|
} else {
|
466
|
+
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
465
467
|
reject(e);
|
466
468
|
}
|
467
469
|
} else {
|
470
|
+
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
468
471
|
reject(e);
|
469
472
|
}
|
470
473
|
}
|
@@ -593,8 +596,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
593
596
|
this.setupLocalParticipantEvents();
|
594
597
|
this.emit(RoomEvent.SignalConnected);
|
595
598
|
} catch (err) {
|
599
|
+
await this.engine.close();
|
596
600
|
this.recreateEngine();
|
597
|
-
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
598
601
|
const resultingError = new ConnectionError(`could not establish signal connection`);
|
599
602
|
if (err instanceof Error) {
|
600
603
|
resultingError.message = `${resultingError.message}: ${err.message}`;
|
@@ -608,8 +611,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
608
611
|
}
|
609
612
|
|
610
613
|
if (abortController.signal.aborted) {
|
614
|
+
await this.engine.close();
|
611
615
|
this.recreateEngine();
|
612
|
-
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
613
616
|
throw new ConnectionError(`Connection attempt aborted`);
|
614
617
|
}
|
615
618
|
|
@@ -619,8 +622,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
619
622
|
abortController,
|
620
623
|
);
|
621
624
|
} catch (e) {
|
625
|
+
await this.engine.close();
|
622
626
|
this.recreateEngine();
|
623
|
-
this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
|
624
627
|
throw e;
|
625
628
|
}
|
626
629
|
|
@@ -1546,14 +1549,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1546
1549
|
}
|
1547
1550
|
|
1548
1551
|
private sendSyncState() {
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
) {
|
1552
|
+
const previousAnswer = this.engine.subscriber?.getLocalDescription();
|
1553
|
+
const previousOffer = this.engine.subscriber?.getRemoteDescription();
|
1554
|
+
|
1555
|
+
if (!previousAnswer) {
|
1553
1556
|
return;
|
1554
1557
|
}
|
1555
|
-
const previousAnswer = this.engine.subscriber.pc.localDescription;
|
1556
|
-
const previousOffer = this.engine.subscriber.pc.remoteDescription;
|
1557
1558
|
|
1558
1559
|
/* 1. autosubscribe on, so subscribed tracks = all tracks - unsub tracks,
|
1559
1560
|
in this case, we send unsub tracks, so server add all tracks to this
|
package/src/room/defaults.ts
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
import type { InternalRoomConnectOptions, InternalRoomOptions } from '../options';
|
2
2
|
import DefaultReconnectPolicy from './DefaultReconnectPolicy';
|
3
|
-
import { AudioPresets, ScreenSharePresets, VideoPresets } from './track/options';
|
4
3
|
import type {
|
5
4
|
AudioCaptureOptions,
|
6
5
|
TrackPublishDefaults,
|
7
6
|
VideoCaptureOptions,
|
8
7
|
} from './track/options';
|
8
|
+
import { AudioPresets, ScreenSharePresets, VideoPresets } from './track/options';
|
9
|
+
|
10
|
+
export const defaultVideoCodec = 'vp8';
|
9
11
|
|
10
12
|
export const publishDefaults: TrackPublishDefaults = {
|
11
13
|
/**
|
@@ -19,7 +21,7 @@ export const publishDefaults: TrackPublishDefaults = {
|
|
19
21
|
simulcast: true,
|
20
22
|
screenShareEncoding: ScreenSharePresets.h1080fps15.encoding,
|
21
23
|
stopMicTrackOnMute: false,
|
22
|
-
videoCodec:
|
24
|
+
videoCodec: defaultVideoCodec,
|
23
25
|
backupCodec: false,
|
24
26
|
} as const;
|
25
27
|
|
package/src/room/events.ts
CHANGED
@@ -223,7 +223,7 @@ export enum RoomEvent {
|
|
223
223
|
* be emitted.
|
224
224
|
*
|
225
225
|
* args: (pub: [[RemoteTrackPublication]],
|
226
|
-
* status: [[TrackPublication.
|
226
|
+
* status: [[TrackPublication.PermissionStatus]],
|
227
227
|
* participant: [[RemoteParticipant]])
|
228
228
|
*/
|
229
229
|
TrackSubscriptionPermissionChanged = 'trackSubscriptionPermissionChanged',
|
@@ -18,6 +18,7 @@ import {
|
|
18
18
|
TrackUnpublishedResponse,
|
19
19
|
} from '../../proto/livekit_rtc_pb';
|
20
20
|
import type RTCEngine from '../RTCEngine';
|
21
|
+
import { defaultVideoCodec } from '../defaults';
|
21
22
|
import { DeviceUnsupportedError, TrackInvalidError, UnexpectedConnectionState } from '../errors';
|
22
23
|
import { EngineEvent, ParticipantEvent, TrackEvent } from '../events';
|
23
24
|
import LocalAudioTrack from '../track/LocalAudioTrack';
|
@@ -33,10 +34,11 @@ import type {
|
|
33
34
|
TrackPublishOptions,
|
34
35
|
VideoCaptureOptions,
|
35
36
|
} from '../track/options';
|
36
|
-
import { VideoPresets, isBackupCodec
|
37
|
+
import { VideoPresets, isBackupCodec } from '../track/options';
|
37
38
|
import {
|
38
39
|
constraintsForOptions,
|
39
40
|
mergeDefaultOptions,
|
41
|
+
mimeTypeToVideoCodecString,
|
40
42
|
screenCaptureToDisplayMediaStreamOptions,
|
41
43
|
} from '../track/utils';
|
42
44
|
import type { DataPublishOptions } from '../types';
|
@@ -629,6 +631,10 @@ export default class LocalParticipant extends Participant {
|
|
629
631
|
if (opts.videoCodec === 'vp9' && !supportsVP9()) {
|
630
632
|
opts.videoCodec = undefined;
|
631
633
|
}
|
634
|
+
if (opts.videoCodec === undefined) {
|
635
|
+
opts.videoCodec = defaultVideoCodec;
|
636
|
+
}
|
637
|
+
const videoCodec = opts.videoCodec;
|
632
638
|
|
633
639
|
// handle track actions
|
634
640
|
track.on(TrackEvent.Muted, this.onTrackMuted);
|
@@ -654,7 +660,6 @@ export default class LocalParticipant extends Participant {
|
|
654
660
|
|
655
661
|
// compute encodings and layers for video
|
656
662
|
let encodings: RTCRtpEncodingParameters[] | undefined;
|
657
|
-
let simEncodings: RTCRtpEncodingParameters[] | undefined;
|
658
663
|
if (track.kind === Track.Kind.Video) {
|
659
664
|
let dims: Track.Dimensions = {
|
660
665
|
width: 0,
|
@@ -679,53 +684,40 @@ export default class LocalParticipant extends Participant {
|
|
679
684
|
req.height = dims.height;
|
680
685
|
// for svc codecs, disable simulcast and use vp8 for backup codec
|
681
686
|
if (track instanceof LocalVideoTrack) {
|
682
|
-
if (isSVCCodec(
|
687
|
+
if (isSVCCodec(videoCodec)) {
|
683
688
|
// vp9 svc with screenshare has problem to encode, always use L1T3 here
|
684
|
-
if (track.source === Track.Source.ScreenShare &&
|
689
|
+
if (track.source === Track.Source.ScreenShare && videoCodec === 'vp9') {
|
685
690
|
opts.scalabilityMode = 'L1T3';
|
686
691
|
}
|
687
692
|
// set scalabilityMode to 'L3T3_KEY' by default
|
688
693
|
opts.scalabilityMode = opts.scalabilityMode ?? 'L3T3_KEY';
|
689
694
|
}
|
690
695
|
|
696
|
+
req.simulcastCodecs = [
|
697
|
+
new SimulcastCodec({
|
698
|
+
codec: videoCodec,
|
699
|
+
cid: track.mediaStreamTrack.id,
|
700
|
+
}),
|
701
|
+
];
|
702
|
+
|
691
703
|
// set up backup
|
692
|
-
if (opts.
|
704
|
+
if (opts.backupCodec && videoCodec !== opts.backupCodec.codec) {
|
693
705
|
if (!this.roomOptions.dynacast) {
|
694
706
|
this.roomOptions.dynacast = true;
|
695
707
|
}
|
696
|
-
|
697
|
-
simOpts.simulcast = true;
|
698
|
-
simEncodings = computeTrackBackupEncodings(track, opts.backupCodec.codec, simOpts);
|
699
|
-
|
700
|
-
req.simulcastCodecs = [
|
701
|
-
new SimulcastCodec({
|
702
|
-
codec: opts.videoCodec,
|
703
|
-
cid: track.mediaStreamTrack.id,
|
704
|
-
enableSimulcastLayers: true,
|
705
|
-
}),
|
708
|
+
req.simulcastCodecs.push(
|
706
709
|
new SimulcastCodec({
|
707
710
|
codec: opts.backupCodec.codec,
|
708
711
|
cid: '',
|
709
|
-
enableSimulcastLayers: true,
|
710
712
|
}),
|
711
|
-
|
712
|
-
} else if (opts.videoCodec) {
|
713
|
-
// pass codec info to sfu so it can prefer codec for the client which don't support
|
714
|
-
// setCodecPreferences
|
715
|
-
req.simulcastCodecs = [
|
716
|
-
new SimulcastCodec({
|
717
|
-
codec: opts.videoCodec,
|
718
|
-
cid: track.mediaStreamTrack.id,
|
719
|
-
enableSimulcastLayers: opts.simulcast ?? false,
|
720
|
-
}),
|
721
|
-
];
|
713
|
+
);
|
722
714
|
}
|
723
715
|
}
|
724
716
|
|
725
717
|
encodings = computeVideoEncodings(
|
726
718
|
track.source === Track.Source.ScreenShare,
|
727
|
-
|
728
|
-
|
719
|
+
req.width,
|
720
|
+
req.height,
|
729
721
|
opts,
|
730
722
|
);
|
731
723
|
req.layers = videoLayersFromEncodings(
|
@@ -749,30 +741,28 @@ export default class LocalParticipant extends Participant {
|
|
749
741
|
}
|
750
742
|
|
751
743
|
const ti = await this.engine.addTrack(req);
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
backupCodecSupported = true;
|
744
|
+
// server might not support the codec the client has requested, in that case, fallback
|
745
|
+
// to a supported codec
|
746
|
+
let primaryCodecMime: string | undefined;
|
747
|
+
ti.codecs.forEach((codec) => {
|
748
|
+
if (primaryCodecMime === undefined) {
|
749
|
+
primaryCodecMime = codec.mimeType;
|
759
750
|
}
|
760
751
|
});
|
761
|
-
|
762
|
-
|
763
|
-
if (
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
752
|
+
if (primaryCodecMime && track.kind === Track.Kind.Video) {
|
753
|
+
const updatedCodec = mimeTypeToVideoCodecString(primaryCodecMime);
|
754
|
+
if (updatedCodec !== videoCodec) {
|
755
|
+
log.debug('falling back to server selected codec', { codec: updatedCodec });
|
756
|
+
/* @ts-ignore */
|
757
|
+
opts.videoCodec = updatedCodec;
|
758
|
+
|
759
|
+
// recompute encodings since bitrates/etc could have changed
|
760
|
+
encodings = computeVideoEncodings(
|
761
|
+
track.source === Track.Source.ScreenShare,
|
762
|
+
req.width,
|
763
|
+
req.height,
|
764
|
+
opts,
|
772
765
|
);
|
773
|
-
opts.videoCodec = backupCodec.codec;
|
774
|
-
opts.videoEncoding = backupCodec.encoding;
|
775
|
-
encodings = simEncodings;
|
776
766
|
}
|
777
767
|
}
|
778
768
|
|
@@ -786,20 +776,19 @@ export default class LocalParticipant extends Participant {
|
|
786
776
|
}
|
787
777
|
log.debug(`publishing ${track.kind} with encodings`, { encodings, trackInfo: ti });
|
788
778
|
|
789
|
-
// store RTPSender
|
790
779
|
track.sender = await this.engine.createSender(track, opts, encodings);
|
791
780
|
|
792
781
|
if (encodings) {
|
793
782
|
if (isFireFox() && track.kind === Track.Kind.Audio) {
|
794
783
|
/* Refer to RFC https://datatracker.ietf.org/doc/html/rfc7587#section-6.1,
|
795
|
-
livekit-server uses maxaveragebitrate=
|
784
|
+
livekit-server uses maxaveragebitrate=510000 in the answer sdp to permit client to
|
796
785
|
publish high quality audio track. But firefox always uses this value as the actual
|
797
786
|
bitrates, causing the audio bitrates to rise to 510Kbps in any stereo case unexpectedly.
|
798
787
|
So the client need to modify maxaverragebitrates in answer sdp to user provided value to
|
799
788
|
fix the issue.
|
800
789
|
*/
|
801
790
|
let trackTransceiver: RTCRtpTransceiver | undefined = undefined;
|
802
|
-
for (const transceiver of this.engine.publisher.
|
791
|
+
for (const transceiver of this.engine.publisher.getTransceivers()) {
|
803
792
|
if (transceiver.sender === track.sender) {
|
804
793
|
trackTransceiver = transceiver;
|
805
794
|
break;
|
@@ -889,7 +878,6 @@ export default class LocalParticipant extends Participant {
|
|
889
878
|
{
|
890
879
|
codec: opts.videoCodec,
|
891
880
|
cid: simulcastTrack.mediaStreamTrack.id,
|
892
|
-
enableSimulcastLayers: opts.simulcast,
|
893
881
|
},
|
894
882
|
],
|
895
883
|
});
|
@@ -947,11 +935,11 @@ export default class LocalParticipant extends Participant {
|
|
947
935
|
track.sender = undefined;
|
948
936
|
if (
|
949
937
|
this.engine.publisher &&
|
950
|
-
this.engine.publisher.
|
938
|
+
this.engine.publisher.getConnectionState() !== 'closed' &&
|
951
939
|
trackSender
|
952
940
|
) {
|
953
941
|
try {
|
954
|
-
for (const transceiver of this.engine.publisher.
|
942
|
+
for (const transceiver of this.engine.publisher.getTransceivers()) {
|
955
943
|
// if sender is not currently sending (after replaceTrack(null))
|
956
944
|
// removeTrack would have no effect.
|
957
945
|
// to ensure we end up successfully removing the track, manually set
|
@@ -138,7 +138,7 @@ export default class LocalAudioTrack extends LocalTrack {
|
|
138
138
|
this.prevStats = stats;
|
139
139
|
};
|
140
140
|
|
141
|
-
async setProcessor(processor: TrackProcessor<
|
141
|
+
async setProcessor(processor: TrackProcessor<this['kind']>) {
|
142
142
|
const unlock = await this.processorLock.lock();
|
143
143
|
try {
|
144
144
|
if (!this.audioContext) {
|
@@ -34,7 +34,7 @@ export default abstract class LocalTrack extends Track {
|
|
34
34
|
|
35
35
|
protected processorElement?: HTMLMediaElement;
|
36
36
|
|
37
|
-
protected processor?: TrackProcessor<
|
37
|
+
protected processor?: TrackProcessor<this['kind']>;
|
38
38
|
|
39
39
|
protected processorLock: Mutex;
|
40
40
|
|
@@ -163,6 +163,12 @@ export default abstract class LocalTrack extends Track {
|
|
163
163
|
throw new Error('cannot get dimensions for audio tracks');
|
164
164
|
}
|
165
165
|
|
166
|
+
if (getBrowser()?.os === 'iOS') {
|
167
|
+
// browsers report wrong initial resolution on iOS.
|
168
|
+
// when slightly delaying the call to .getSettings(), the correct resolution is being reported
|
169
|
+
await sleep(10);
|
170
|
+
}
|
171
|
+
|
166
172
|
const started = Date.now();
|
167
173
|
while (Date.now() - started < timeout) {
|
168
174
|
const dims = this.dimensions;
|
@@ -396,10 +402,7 @@ export default abstract class LocalTrack extends Track {
|
|
396
402
|
* @param showProcessedStreamLocally
|
397
403
|
* @returns
|
398
404
|
*/
|
399
|
-
async setProcessor(
|
400
|
-
processor: TrackProcessor<typeof this.kind>,
|
401
|
-
showProcessedStreamLocally = true,
|
402
|
-
) {
|
405
|
+
async setProcessor(processor: TrackProcessor<this['kind']>, showProcessedStreamLocally = true) {
|
403
406
|
const unlock = await this.processorLock.lock();
|
404
407
|
try {
|
405
408
|
log.debug('setting up processor');
|