livekit-client 1.14.1 → 1.14.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. package/dist/livekit-client.e2ee.worker.js +1 -1
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs +25 -44
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +399 -196
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.umd.js +1 -1
  8. package/dist/livekit-client.umd.js.map +1 -1
  9. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  10. package/dist/src/e2ee/utils.d.ts +0 -1
  11. package/dist/src/e2ee/utils.d.ts.map +1 -1
  12. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  13. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
  14. package/dist/src/proto/livekit_models_pb.d.ts +87 -11
  15. package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
  16. package/dist/src/proto/livekit_rtc_pb.d.ts +0 -4
  17. package/dist/src/proto/livekit_rtc_pb.d.ts.map +1 -1
  18. package/dist/src/room/PCTransport.d.ts +20 -1
  19. package/dist/src/room/PCTransport.d.ts.map +1 -1
  20. package/dist/src/room/RTCEngine.d.ts +1 -1
  21. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  22. package/dist/src/room/Room.d.ts.map +1 -1
  23. package/dist/src/room/defaults.d.ts +1 -0
  24. package/dist/src/room/defaults.d.ts.map +1 -1
  25. package/dist/src/room/events.d.ts +1 -1
  26. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  27. package/dist/src/room/timers.d.ts +1 -1
  28. package/dist/src/room/timers.d.ts.map +1 -1
  29. package/dist/src/room/track/LocalAudioTrack.d.ts +1 -1
  30. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  31. package/dist/src/room/track/LocalTrack.d.ts +3 -3
  32. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  33. package/dist/src/room/track/LocalVideoTrack.d.ts +2 -1
  34. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  35. package/dist/src/room/track/options.d.ts +0 -1
  36. package/dist/src/room/track/options.d.ts.map +1 -1
  37. package/dist/src/room/track/utils.d.ts +2 -1
  38. package/dist/src/room/track/utils.d.ts.map +1 -1
  39. package/dist/src/utils/cloneDeep.d.ts +2 -0
  40. package/dist/src/utils/cloneDeep.d.ts.map +1 -0
  41. package/dist/ts4.2/src/e2ee/utils.d.ts +0 -1
  42. package/dist/ts4.2/src/proto/livekit_models_pb.d.ts +87 -11
  43. package/dist/ts4.2/src/proto/livekit_rtc_pb.d.ts +0 -4
  44. package/dist/ts4.2/src/room/PCTransport.d.ts +20 -1
  45. package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -1
  46. package/dist/ts4.2/src/room/defaults.d.ts +1 -0
  47. package/dist/ts4.2/src/room/events.d.ts +1 -1
  48. package/dist/ts4.2/src/room/timers.d.ts +1 -1
  49. package/dist/ts4.2/src/room/track/LocalAudioTrack.d.ts +1 -1
  50. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +3 -3
  51. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +2 -1
  52. package/dist/ts4.2/src/room/track/options.d.ts +0 -1
  53. package/dist/ts4.2/src/room/track/utils.d.ts +1 -0
  54. package/dist/ts4.2/src/utils/cloneDeep.d.ts +2 -0
  55. package/package.json +14 -14
  56. package/src/connectionHelper/checks/webrtc.ts +1 -1
  57. package/src/e2ee/E2eeManager.ts +2 -1
  58. package/src/e2ee/utils.ts +0 -10
  59. package/src/e2ee/worker/FrameCryptor.ts +13 -14
  60. package/src/e2ee/worker/ParticipantKeyHandler.ts +4 -5
  61. package/src/e2ee/worker/e2ee.worker.ts +3 -1
  62. package/src/proto/livekit_models_pb.ts +140 -15
  63. package/src/proto/livekit_rtc_pb.ts +1 -7
  64. package/src/room/PCTransport.ts +116 -1
  65. package/src/room/RTCEngine.ts +49 -85
  66. package/src/room/Room.ts +10 -9
  67. package/src/room/defaults.ts +4 -2
  68. package/src/room/events.ts +1 -1
  69. package/src/room/participant/LocalParticipant.ts +44 -56
  70. package/src/room/track/LocalAudioTrack.ts +1 -1
  71. package/src/room/track/LocalTrack.ts +8 -5
  72. package/src/room/track/LocalVideoTrack.ts +2 -1
  73. package/src/room/track/options.ts +0 -7
  74. package/src/room/track/utils.ts +17 -8
  75. package/src/utils/cloneDeep.test.ts +54 -0
  76. package/src/utils/cloneDeep.ts +11 -0
@@ -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 primaryPC?: RTCPeerConnection;
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.pc.signalingState !== 'closed') {
251
- this.publisher.pc.getSenders().forEach((sender) => {
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?.pc.removeTrack) {
255
- this.publisher?.pc.removeTrack(sender);
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.primaryPC = undefined;
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?.pc.removeTrack(sender);
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.primaryPC === undefined) {
356
+ if (this.primaryTransport === undefined) {
357
357
  return undefined;
358
358
  }
359
- return getConnectedAddress(this.primaryPC);
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.pc.onicecandidate = (ev) => {
391
- if (!ev.candidate) return;
392
- log.trace('adding ICE candidate for peer', ev.candidate);
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.pc.onicecandidate = (ev) => {
397
- if (!ev.candidate) return;
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 primaryPC = this.publisher.pc;
406
- let secondaryPC = this.subscriber.pc;
403
+ let primaryTransport = this.publisher;
404
+ let secondaryTransport = this.subscriber;
407
405
  let subscriberPrimary = joinResponse.subscriberPrimary;
408
406
  if (subscriberPrimary) {
409
- primaryPC = this.subscriber.pc;
410
- secondaryPC = this.publisher.pc;
407
+ primaryTransport = this.subscriber;
408
+ secondaryTransport = this.publisher;
411
409
  // in subscriber primary mode, server side opens sub data channels.
412
- this.subscriber.pc.ondatachannel = this.handleDataChannel;
410
+ this.subscriber.onDataChannel = this.handleDataChannel;
413
411
  }
414
- this.primaryPC = primaryPC;
415
- primaryPC.onconnectionstatechange = async () => {
416
- log.debug(`primary PC state changed ${primaryPC.connectionState}`);
417
- if (primaryPC.connectionState === 'connected') {
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 (primaryPC.connectionState === 'failed') {
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
- secondaryPC.onconnectionstatechange = async () => {
438
- log.debug(`secondary PC state changed ${secondaryPC.connectionState}`);
435
+ secondaryTransport.onConnectionStateChange = async (connectionState) => {
436
+ log.debug(`secondary PC state changed ${connectionState}`);
439
437
  // also reconnect if secondary peerconnection fails
440
- if (secondaryPC.connectionState === 'failed') {
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.pc.ontrack = (ev: RTCTrackEvent) => {
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.pc.signalingState.toString(),
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.pc.signalingState.toString(),
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.primaryPC = undefined;
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.pc.createDataChannel(lossyDataChannel, {
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.pc.createDataChannel(reliableDataChannel, {
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.pc.addTransceiver(
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.pc.addTransceiver(
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.pc.addTrack(track);
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.primaryPC?.signalingState ?? 'closed') === 'closed'
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.pc.setConfiguration(rtcConfig);
1003
- this.subscriber.pc.setConfiguration(rtcConfig);
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.primaryPC === undefined) {
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.primaryPC?.connectionState === 'connected' &&
1096
- (!this.hasPublished || this.publisher?.pc.connectionState === 'connected')
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?.pc.iceConnectionState !== 'checking'
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.pc.iceConnectionState}`,
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.primaryPC) {
1208
+ if (!this.primaryTransport) {
1211
1209
  return false;
1212
1210
  }
1213
1211
  if (
1214
- this.primaryPC.connectionState === 'closed' ||
1215
- this.primaryPC.connectionState === 'failed'
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.pc.connectionState === 'closed' ||
1227
- this.publisher.pc.connectionState === 'failed'
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
- if (
1550
- this.engine.subscriber === undefined ||
1551
- this.engine.subscriber.pc.localDescription === null
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
@@ -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: 'vp8',
24
+ videoCodec: defaultVideoCodec,
23
25
  backupCodec: false,
24
26
  } as const;
25
27
 
@@ -223,7 +223,7 @@ export enum RoomEvent {
223
223
  * be emitted.
224
224
  *
225
225
  * args: (pub: [[RemoteTrackPublication]],
226
- * status: [[TrackPublication.SubscriptionStatus]],
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, isCodecEqual } from '../track/options';
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(opts.videoCodec)) {
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 && opts.videoCodec === 'vp9') {
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.videoCodec && opts.backupCodec && opts.videoCodec !== opts.backupCodec.codec) {
704
+ if (opts.backupCodec && videoCodec !== opts.backupCodec.codec) {
693
705
  if (!this.roomOptions.dynacast) {
694
706
  this.roomOptions.dynacast = true;
695
707
  }
696
- const simOpts = { ...opts };
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
- dims.width,
728
- dims.height,
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
- let primaryCodecSupported = false;
753
- let backupCodecSupported = false;
754
- ti.codecs.forEach((c) => {
755
- if (isCodecEqual(c.mimeType, opts.videoCodec)) {
756
- primaryCodecSupported = true;
757
- } else if (opts.backupCodec && isCodecEqual(c.mimeType, opts.backupCodec.codec)) {
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
- if (req.simulcastCodecs.length > 0) {
763
- if (!primaryCodecSupported && !backupCodecSupported) {
764
- throw Error('cannot publish track, codec not supported by server');
765
- }
766
-
767
- if (!primaryCodecSupported && opts.backupCodec) {
768
- const backupCodec = opts.backupCodec;
769
- opts = { ...opts };
770
- log.debug(
771
- `primary codec ${opts.videoCodec} not supported, fallback to ${backupCodec.codec}`,
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=510000in the answer sdp to permit client to
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.pc.getTransceivers()) {
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.pc.connectionState !== 'closed' &&
938
+ this.engine.publisher.getConnectionState() !== 'closed' &&
951
939
  trackSender
952
940
  ) {
953
941
  try {
954
- for (const transceiver of this.engine.publisher.pc.getTransceivers()) {
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<typeof this.kind>) {
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<typeof this.kind>;
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');