livekit-client 1.14.1 → 1.14.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) 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 +555 -306
  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 +1 -1
  23. package/dist/src/room/Room.d.ts.map +1 -1
  24. package/dist/src/room/defaults.d.ts +1 -0
  25. package/dist/src/room/defaults.d.ts.map +1 -1
  26. package/dist/src/room/events.d.ts +3 -1
  27. package/dist/src/room/events.d.ts.map +1 -1
  28. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  29. package/dist/src/room/participant/Participant.d.ts +1 -0
  30. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  31. package/dist/src/room/timers.d.ts +1 -1
  32. package/dist/src/room/timers.d.ts.map +1 -1
  33. package/dist/src/room/track/LocalAudioTrack.d.ts +1 -1
  34. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  35. package/dist/src/room/track/LocalTrack.d.ts +3 -3
  36. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  37. package/dist/src/room/track/LocalVideoTrack.d.ts +2 -1
  38. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  39. package/dist/src/room/track/Track.d.ts.map +1 -1
  40. package/dist/src/room/track/options.d.ts +0 -1
  41. package/dist/src/room/track/options.d.ts.map +1 -1
  42. package/dist/src/room/track/utils.d.ts +2 -1
  43. package/dist/src/room/track/utils.d.ts.map +1 -1
  44. package/dist/src/utils/cloneDeep.d.ts +2 -0
  45. package/dist/src/utils/cloneDeep.d.ts.map +1 -0
  46. package/dist/ts4.2/src/e2ee/utils.d.ts +0 -1
  47. package/dist/ts4.2/src/proto/livekit_models_pb.d.ts +87 -11
  48. package/dist/ts4.2/src/proto/livekit_rtc_pb.d.ts +0 -4
  49. package/dist/ts4.2/src/room/PCTransport.d.ts +20 -1
  50. package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -1
  51. package/dist/ts4.2/src/room/Room.d.ts +1 -1
  52. package/dist/ts4.2/src/room/defaults.d.ts +1 -0
  53. package/dist/ts4.2/src/room/events.d.ts +3 -1
  54. package/dist/ts4.2/src/room/participant/Participant.d.ts +1 -0
  55. package/dist/ts4.2/src/room/timers.d.ts +1 -1
  56. package/dist/ts4.2/src/room/track/LocalAudioTrack.d.ts +1 -1
  57. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +3 -3
  58. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +2 -1
  59. package/dist/ts4.2/src/room/track/options.d.ts +0 -1
  60. package/dist/ts4.2/src/room/track/utils.d.ts +1 -0
  61. package/dist/ts4.2/src/utils/cloneDeep.d.ts +2 -0
  62. package/package.json +15 -15
  63. package/src/connectionHelper/checks/webrtc.ts +1 -1
  64. package/src/e2ee/E2eeManager.ts +2 -1
  65. package/src/e2ee/utils.ts +0 -10
  66. package/src/e2ee/worker/FrameCryptor.ts +13 -14
  67. package/src/e2ee/worker/ParticipantKeyHandler.ts +4 -5
  68. package/src/e2ee/worker/e2ee.worker.ts +3 -1
  69. package/src/proto/livekit_models_pb.ts +140 -15
  70. package/src/proto/livekit_rtc_pb.ts +1 -7
  71. package/src/room/PCTransport.ts +122 -5
  72. package/src/room/RTCEngine.ts +56 -92
  73. package/src/room/Room.ts +14 -11
  74. package/src/room/defaults.ts +4 -2
  75. package/src/room/events.ts +5 -1
  76. package/src/room/participant/LocalParticipant.ts +47 -68
  77. package/src/room/participant/Participant.ts +1 -0
  78. package/src/room/track/LocalAudioTrack.ts +1 -1
  79. package/src/room/track/LocalTrack.ts +8 -5
  80. package/src/room/track/LocalVideoTrack.ts +2 -1
  81. package/src/room/track/Track.ts +6 -1
  82. package/src/room/track/options.ts +0 -7
  83. package/src/room/track/utils.ts +17 -8
  84. package/src/utils/cloneDeep.test.ts +54 -0
  85. 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 */
@@ -374,53 +374,44 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
374
374
 
375
375
  const rtcConfig = this.makeRTCConfiguration(joinResponse);
376
376
 
377
- if (this.signalOpts?.e2eeEnabled) {
378
- log.debug('E2EE - setting up transports with insertable streams');
379
- // this makes sure that no data is sent before the transforms are ready
380
- // @ts-ignore
381
- rtcConfig.encodedInsertableStreams = true;
382
- }
383
-
384
377
  const googConstraints = { optional: [{ googDscp: true }] };
385
378
  this.publisher = new PCTransport(rtcConfig, googConstraints);
386
379
  this.subscriber = new PCTransport(rtcConfig);
387
380
 
388
381
  this.emit(EngineEvent.TransportsCreated, this.publisher, this.subscriber);
389
382
 
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);
383
+ this.publisher.onIceCandidate = (candidate) => {
384
+ log.trace('adding ICE candidate for peer', candidate);
385
+ this.client.sendIceCandidate(candidate, SignalTarget.PUBLISHER);
394
386
  };
395
387
 
396
- this.subscriber.pc.onicecandidate = (ev) => {
397
- if (!ev.candidate) return;
398
- this.client.sendIceCandidate(ev.candidate, SignalTarget.SUBSCRIBER);
388
+ this.subscriber.onIceCandidate = (candidate) => {
389
+ this.client.sendIceCandidate(candidate, SignalTarget.SUBSCRIBER);
399
390
  };
400
391
 
401
392
  this.publisher.onOffer = (offer) => {
402
393
  this.client.sendOffer(offer);
403
394
  };
404
395
 
405
- let primaryPC = this.publisher.pc;
406
- let secondaryPC = this.subscriber.pc;
396
+ let primaryTransport = this.publisher;
397
+ let secondaryTransport = this.subscriber;
407
398
  let subscriberPrimary = joinResponse.subscriberPrimary;
408
399
  if (subscriberPrimary) {
409
- primaryPC = this.subscriber.pc;
410
- secondaryPC = this.publisher.pc;
400
+ primaryTransport = this.subscriber;
401
+ secondaryTransport = this.publisher;
411
402
  // in subscriber primary mode, server side opens sub data channels.
412
- this.subscriber.pc.ondatachannel = this.handleDataChannel;
403
+ this.subscriber.onDataChannel = this.handleDataChannel;
413
404
  }
414
- this.primaryPC = primaryPC;
415
- primaryPC.onconnectionstatechange = async () => {
416
- log.debug(`primary PC state changed ${primaryPC.connectionState}`);
417
- if (primaryPC.connectionState === 'connected') {
405
+ this.primaryTransport = primaryTransport;
406
+ primaryTransport.onConnectionStateChange = async (connectionState) => {
407
+ log.debug(`primary PC state changed ${connectionState}`);
408
+ if (connectionState === 'connected') {
418
409
  const shouldEmit = this.pcState === PCState.New;
419
410
  this.pcState = PCState.Connected;
420
411
  if (shouldEmit) {
421
412
  this.emit(EngineEvent.Connected, joinResponse);
422
413
  }
423
- } else if (primaryPC.connectionState === 'failed') {
414
+ } else if (connectionState === 'failed') {
424
415
  // on Safari, PeerConnection will switch to 'disconnected' during renegotiation
425
416
  if (this.pcState === PCState.Connected) {
426
417
  this.pcState = PCState.Disconnected;
@@ -434,10 +425,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
434
425
  }
435
426
  }
436
427
  };
437
- secondaryPC.onconnectionstatechange = async () => {
438
- log.debug(`secondary PC state changed ${secondaryPC.connectionState}`);
428
+ secondaryTransport.onConnectionStateChange = async (connectionState) => {
429
+ log.debug(`secondary PC state changed ${connectionState}`);
439
430
  // also reconnect if secondary peerconnection fails
440
- if (secondaryPC.connectionState === 'failed') {
431
+ if (connectionState === 'failed') {
441
432
  this.handleDisconnect(
442
433
  'secondary peerconnection',
443
434
  subscriberPrimary
@@ -447,7 +438,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
447
438
  }
448
439
  };
449
440
 
450
- this.subscriber.pc.ontrack = (ev: RTCTrackEvent) => {
441
+ this.subscriber.onTrack = (ev: RTCTrackEvent) => {
451
442
  this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
452
443
  };
453
444
 
@@ -462,7 +453,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
462
453
  }
463
454
  log.debug('received server answer', {
464
455
  RTCSdpType: sd.type,
465
- signalingState: this.publisher.pc.signalingState.toString(),
456
+ signalingState: this.publisher.getSignallingState().toString(),
466
457
  });
467
458
  await this.publisher.setRemoteDescription(sd);
468
459
  };
@@ -487,7 +478,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
487
478
  }
488
479
  log.debug('received server offer', {
489
480
  RTCSdpType: sd.type,
490
- signalingState: this.subscriber.pc.signalingState.toString(),
481
+ signalingState: this.subscriber.getSignallingState().toString(),
491
482
  });
492
483
  await this.subscriber.setRemoteDescription(sd);
493
484
 
@@ -518,7 +509,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
518
509
  this.client.onLeave = (leave?: LeaveRequest) => {
519
510
  if (leave?.canReconnect) {
520
511
  this.fullReconnectOnNext = true;
521
- this.primaryPC = undefined;
512
+ this.primaryTransport = undefined;
522
513
  // reconnect immediately instead of waiting for next attempt
523
514
  this.handleDisconnect(leaveReconnect);
524
515
  } else {
@@ -531,6 +522,12 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
531
522
 
532
523
  private makeRTCConfiguration(serverResponse: JoinResponse | ReconnectResponse): RTCConfiguration {
533
524
  const rtcConfig = { ...this.rtcConfig };
525
+ if (this.signalOpts?.e2eeEnabled) {
526
+ log.debug('E2EE - setting up transports with insertable streams');
527
+ // this makes sure that no data is sent before the transforms are ready
528
+ // @ts-ignore
529
+ rtcConfig.encodedInsertableStreams = true;
530
+ }
534
531
 
535
532
  // update ICE servers before creating PeerConnection
536
533
  if (serverResponse.iceServers && !rtcConfig.iceServers) {
@@ -579,12 +576,12 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
579
576
  }
580
577
 
581
578
  // create data channels
582
- this.lossyDC = this.publisher.pc.createDataChannel(lossyDataChannel, {
579
+ this.lossyDC = this.publisher.createDataChannel(lossyDataChannel, {
583
580
  // will drop older packets that arrive
584
581
  ordered: true,
585
582
  maxRetransmits: 0,
586
583
  });
587
- this.reliableDC = this.publisher.pc.createDataChannel(reliableDataChannel, {
584
+ this.reliableDC = this.publisher.createDataChannel(reliableDataChannel, {
588
585
  ordered: true,
589
586
  });
590
587
 
@@ -765,7 +762,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
765
762
  transceiverInit.sendEncodings = encodings;
766
763
  }
767
764
  // addTransceiver for react-native is async. web is synchronous, but await won't effect it.
768
- const transceiver = await this.publisher.pc.addTransceiver(
765
+ const transceiver = await this.publisher.addTransceiver(
769
766
  track.mediaStreamTrack,
770
767
  transceiverInit,
771
768
  );
@@ -791,7 +788,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
791
788
  transceiverInit.sendEncodings = encodings;
792
789
  }
793
790
  // addTransceiver for react-native is async. web is synchronous, but await won't effect it.
794
- const transceiver = await this.publisher.pc.addTransceiver(
791
+ const transceiver = await this.publisher.addTransceiver(
795
792
  simulcastTrack.mediaStreamTrack,
796
793
  transceiverInit,
797
794
  );
@@ -807,7 +804,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
807
804
  if (!this.publisher) {
808
805
  throw new UnexpectedConnectionState('publisher is closed');
809
806
  }
810
- return this.publisher.pc.addTrack(track);
807
+ return this.publisher.addTrack(track);
811
808
  }
812
809
 
813
810
  // websocket reconnect behavior. if websocket is interrupted, and the PeerConnection
@@ -872,7 +869,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
872
869
  this.clientConfiguration?.resumeConnection === ClientConfigSetting.DISABLED ||
873
870
  // signaling state could change to closed due to hardware sleep
874
871
  // those connections cannot be resumed
875
- (this.primaryPC?.signalingState ?? 'closed') === 'closed'
872
+ (this.primaryTransport?.getSignallingState() ?? 'closed') === 'closed'
876
873
  ) {
877
874
  this.fullReconnectOnNext = true;
878
875
  }
@@ -999,13 +996,14 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
999
996
  const res = await this.client.reconnect(this.url, this.token, this.participantSid, reason);
1000
997
  if (res) {
1001
998
  const rtcConfig = this.makeRTCConfiguration(res);
1002
- this.publisher.pc.setConfiguration(rtcConfig);
1003
- this.subscriber.pc.setConfiguration(rtcConfig);
999
+ this.publisher.setConfiguration(rtcConfig);
1000
+ this.subscriber.setConfiguration(rtcConfig);
1004
1001
  }
1005
1002
  } catch (e) {
1006
1003
  let message = '';
1007
1004
  if (e instanceof Error) {
1008
1005
  message = e.message;
1006
+ log.error(e.message);
1009
1007
  }
1010
1008
  if (e instanceof ConnectionError && e.reason === ConnectionErrorReason.NotAllowed) {
1011
1009
  throw new UnexpectedConnectionState('could not reconnect, token might be expired');
@@ -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
 
@@ -799,7 +802,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
799
802
  * - `startAudio`
800
803
  * - `getUserMedia`
801
804
  */
802
- async startAudio() {
805
+ startAudio = async () => {
803
806
  const elements: Array<HTMLMediaElement> = [];
804
807
  const browser = getBrowser();
805
808
  if (browser && browser.os === 'iOS') {
@@ -860,7 +863,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
860
863
  this.handleAudioPlaybackFailed(err);
861
864
  throw err;
862
865
  }
863
- }
866
+ };
864
867
 
865
868
  /**
866
869
  * Returns true if audio playback is enabled
@@ -970,6 +973,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
970
973
  .on(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished)
971
974
  .on(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged)
972
975
  .on(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError)
976
+ .on(ParticipantEvent.AudioStreamAcquired, this.startAudio)
973
977
  .on(
974
978
  ParticipantEvent.ParticipantPermissionsChanged,
975
979
  this.onLocalParticipantPermissionsChanged,
@@ -1166,6 +1170,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1166
1170
  .off(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished)
1167
1171
  .off(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged)
1168
1172
  .off(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError)
1173
+ .off(ParticipantEvent.AudioStreamAcquired, this.startAudio)
1169
1174
  .off(
1170
1175
  ParticipantEvent.ParticipantPermissionsChanged,
1171
1176
  this.onLocalParticipantPermissionsChanged,
@@ -1546,14 +1551,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1546
1551
  }
1547
1552
 
1548
1553
  private sendSyncState() {
1549
- if (
1550
- this.engine.subscriber === undefined ||
1551
- this.engine.subscriber.pc.localDescription === null
1552
- ) {
1554
+ const previousAnswer = this.engine.subscriber?.getLocalDescription();
1555
+ const previousOffer = this.engine.subscriber?.getRemoteDescription();
1556
+
1557
+ if (!previousAnswer) {
1553
1558
  return;
1554
1559
  }
1555
- const previousAnswer = this.engine.subscriber.pc.localDescription;
1556
- const previousOffer = this.engine.subscriber.pc.remoteDescription;
1557
1560
 
1558
1561
  /* 1. autosubscribe on, so subscribed tracks = all tracks - unsub tracks,
1559
1562
  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',
@@ -441,6 +441,10 @@ export enum ParticipantEvent {
441
441
  /** @internal */
442
442
  MediaDevicesError = 'mediaDevicesError',
443
443
 
444
+ // fired only on LocalParticipant
445
+ /** @internal */
446
+ AudioStreamAcquired = 'audioStreamAcquired',
447
+
444
448
  /**
445
449
  * A participant's permission has changed. Currently only fired on LocalParticipant.
446
450
  * args: (prevPermissions: [[ParticipantPermission]])