livekit-client 1.12.1 → 1.12.3

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.
Files changed (167) hide show
  1. package/README.md +7 -3
  2. package/dist/livekit-client.e2ee.worker.js +1 -1
  3. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  4. package/dist/livekit-client.e2ee.worker.mjs +389 -301
  5. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  6. package/dist/livekit-client.esm.mjs +11279 -13498
  7. package/dist/livekit-client.esm.mjs.map +1 -1
  8. package/dist/livekit-client.umd.js +1 -1
  9. package/dist/livekit-client.umd.js.map +1 -1
  10. package/dist/src/api/SignalClient.d.ts +2 -2
  11. package/dist/src/api/SignalClient.d.ts.map +1 -1
  12. package/dist/src/connectionHelper/ConnectionCheck.d.ts +3 -2
  13. package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
  14. package/dist/src/connectionHelper/checks/Checker.d.ts +3 -2
  15. package/dist/src/connectionHelper/checks/Checker.d.ts.map +1 -1
  16. package/dist/src/connectionHelper/checks/webrtc.d.ts.map +1 -1
  17. package/dist/src/connectionHelper/checks/websocket.d.ts.map +1 -1
  18. package/dist/src/e2ee/E2eeManager.d.ts +4 -2
  19. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  20. package/dist/src/e2ee/KeyProvider.d.ts +4 -2
  21. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
  22. package/dist/src/e2ee/constants.d.ts +1 -0
  23. package/dist/src/e2ee/constants.d.ts.map +1 -1
  24. package/dist/src/e2ee/types.d.ts +1 -0
  25. package/dist/src/e2ee/types.d.ts.map +1 -1
  26. package/dist/src/e2ee/worker/FrameCryptor.d.ts +4 -2
  27. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  28. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +14 -3
  29. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
  30. package/dist/src/index.d.ts +1 -1
  31. package/dist/src/index.d.ts.map +1 -1
  32. package/dist/src/proto/livekit_models_pb.d.ts +1264 -0
  33. package/dist/src/proto/livekit_models_pb.d.ts.map +1 -0
  34. package/dist/src/proto/livekit_rtc_pb.d.ts +1373 -0
  35. package/dist/src/proto/livekit_rtc_pb.d.ts.map +1 -0
  36. package/dist/src/room/PCTransport.d.ts +2 -1
  37. package/dist/src/room/PCTransport.d.ts.map +1 -1
  38. package/dist/src/room/RTCEngine.d.ts +9 -5
  39. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  40. package/dist/src/room/RegionUrlProvider.d.ts +4 -1
  41. package/dist/src/room/RegionUrlProvider.d.ts.map +1 -1
  42. package/dist/src/room/Room.d.ts +15 -11
  43. package/dist/src/room/Room.d.ts.map +1 -1
  44. package/dist/src/room/participant/LocalParticipant.d.ts +2 -2
  45. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  46. package/dist/src/room/participant/Participant.d.ts +5 -3
  47. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  48. package/dist/src/room/participant/ParticipantTrackPermission.d.ts +1 -1
  49. package/dist/src/room/participant/ParticipantTrackPermission.d.ts.map +1 -1
  50. package/dist/src/room/participant/RemoteParticipant.d.ts +2 -3
  51. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  52. package/dist/src/room/participant/publishUtils.d.ts +1 -1
  53. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  54. package/dist/src/room/timers.d.ts +5 -4
  55. package/dist/src/room/timers.d.ts.map +1 -1
  56. package/dist/src/room/track/LocalTrack.d.ts +3 -0
  57. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  58. package/dist/src/room/track/LocalTrackPublication.d.ts +1 -1
  59. package/dist/src/room/track/LocalTrackPublication.d.ts.map +1 -1
  60. package/dist/src/room/track/LocalVideoTrack.d.ts +2 -2
  61. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  62. package/dist/src/room/track/RemoteTrackPublication.d.ts +1 -1
  63. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
  64. package/dist/src/room/track/Track.d.ts +6 -4
  65. package/dist/src/room/track/Track.d.ts.map +1 -1
  66. package/dist/src/room/track/TrackPublication.d.ts +7 -5
  67. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  68. package/dist/src/room/track/create.d.ts.map +1 -1
  69. package/dist/src/room/track/options.d.ts +8 -0
  70. package/dist/src/room/track/options.d.ts.map +1 -1
  71. package/dist/src/room/track/utils.d.ts +5 -1
  72. package/dist/src/room/track/utils.d.ts.map +1 -1
  73. package/dist/src/room/utils.d.ts +3 -1
  74. package/dist/src/room/utils.d.ts.map +1 -1
  75. package/dist/src/test/mocks.d.ts +4 -3
  76. package/dist/src/test/mocks.d.ts.map +1 -1
  77. package/dist/ts4.2/src/api/SignalClient.d.ts +2 -2
  78. package/dist/ts4.2/src/connectionHelper/ConnectionCheck.d.ts +3 -2
  79. package/dist/ts4.2/src/connectionHelper/checks/Checker.d.ts +3 -2
  80. package/dist/ts4.2/src/e2ee/E2eeManager.d.ts +4 -2
  81. package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +4 -2
  82. package/dist/ts4.2/src/e2ee/constants.d.ts +1 -0
  83. package/dist/ts4.2/src/e2ee/types.d.ts +1 -0
  84. package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +4 -2
  85. package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +14 -3
  86. package/dist/ts4.2/src/index.d.ts +1 -1
  87. package/dist/ts4.2/src/proto/livekit_models_pb.d.ts +1264 -0
  88. package/dist/ts4.2/src/proto/livekit_rtc_pb.d.ts +1373 -0
  89. package/dist/ts4.2/src/room/PCTransport.d.ts +2 -1
  90. package/dist/ts4.2/src/room/RTCEngine.d.ts +9 -5
  91. package/dist/ts4.2/src/room/RegionUrlProvider.d.ts +4 -1
  92. package/dist/ts4.2/src/room/Room.d.ts +15 -11
  93. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +2 -2
  94. package/dist/ts4.2/src/room/participant/Participant.d.ts +5 -3
  95. package/dist/ts4.2/src/room/participant/ParticipantTrackPermission.d.ts +1 -1
  96. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +2 -3
  97. package/dist/ts4.2/src/room/participant/publishUtils.d.ts +1 -1
  98. package/dist/ts4.2/src/room/timers.d.ts +5 -4
  99. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +3 -0
  100. package/dist/ts4.2/src/room/track/LocalTrackPublication.d.ts +1 -1
  101. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +2 -2
  102. package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +1 -1
  103. package/dist/ts4.2/src/room/track/Track.d.ts +6 -4
  104. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +7 -5
  105. package/dist/ts4.2/src/room/track/options.d.ts +8 -0
  106. package/dist/ts4.2/src/room/track/utils.d.ts +5 -1
  107. package/dist/ts4.2/src/room/utils.d.ts +3 -1
  108. package/dist/ts4.2/src/test/mocks.d.ts +4 -3
  109. package/package.json +10 -10
  110. package/src/api/SignalClient.ts +104 -101
  111. package/src/connectionHelper/ConnectionCheck.ts +3 -2
  112. package/src/connectionHelper/checks/Checker.ts +3 -3
  113. package/src/connectionHelper/checks/webrtc.ts +66 -2
  114. package/src/connectionHelper/checks/websocket.ts +4 -0
  115. package/src/e2ee/E2eeManager.ts +4 -3
  116. package/src/e2ee/KeyProvider.ts +3 -2
  117. package/src/e2ee/constants.ts +4 -0
  118. package/src/e2ee/types.ts +1 -0
  119. package/src/e2ee/worker/FrameCryptor.test.ts +1 -3
  120. package/src/e2ee/worker/FrameCryptor.ts +5 -5
  121. package/src/e2ee/worker/ParticipantKeyHandler.ts +37 -6
  122. package/src/e2ee/worker/e2ee.worker.ts +1 -1
  123. package/src/index.ts +1 -1
  124. package/src/proto/livekit_models_pb.ts +2096 -0
  125. package/src/proto/livekit_rtc_pb.ts +2332 -0
  126. package/src/room/PCTransport.ts +1 -1
  127. package/src/room/RTCEngine.ts +28 -22
  128. package/src/room/RegionUrlProvider.ts +11 -2
  129. package/src/room/Room.test.ts +1 -0
  130. package/src/room/Room.ts +158 -79
  131. package/src/room/participant/LocalParticipant.ts +43 -59
  132. package/src/room/participant/Participant.ts +6 -4
  133. package/src/room/participant/ParticipantTrackPermission.ts +3 -3
  134. package/src/room/participant/RemoteParticipant.ts +5 -6
  135. package/src/room/participant/publishUtils.test.ts +1 -0
  136. package/src/room/participant/publishUtils.ts +4 -2
  137. package/src/room/track/LocalTrack.ts +24 -9
  138. package/src/room/track/LocalTrackPublication.ts +1 -1
  139. package/src/room/track/LocalVideoTrack.test.ts +2 -1
  140. package/src/room/track/LocalVideoTrack.ts +28 -26
  141. package/src/room/track/RemoteTrackPublication.ts +12 -7
  142. package/src/room/track/RemoteVideoTrack.test.ts +5 -4
  143. package/src/room/track/Track.ts +9 -6
  144. package/src/room/track/TrackPublication.ts +7 -5
  145. package/src/room/track/create.ts +9 -17
  146. package/src/room/track/facingMode.test.ts +1 -0
  147. package/src/room/track/options.ts +23 -16
  148. package/src/room/track/utils.test.ts +1 -0
  149. package/src/room/track/utils.ts +44 -2
  150. package/src/room/utils.test.ts +16 -0
  151. package/src/room/utils.ts +20 -4
  152. package/src/test/mocks.ts +7 -5
  153. package/src/utils/AsyncQueue.test.ts +1 -0
  154. package/src/utils/browserParser.test.ts +33 -3
  155. package/src/utils/browserParser.ts +1 -1
  156. package/dist/src/proto/google/protobuf/timestamp.d.ts +0 -146
  157. package/dist/src/proto/google/protobuf/timestamp.d.ts.map +0 -1
  158. package/dist/src/proto/livekit_models.d.ts +0 -2399
  159. package/dist/src/proto/livekit_models.d.ts.map +0 -1
  160. package/dist/src/proto/livekit_rtc.d.ts +0 -14352
  161. package/dist/src/proto/livekit_rtc.d.ts.map +0 -1
  162. package/dist/ts4.2/src/proto/google/protobuf/timestamp.d.ts +0 -150
  163. package/dist/ts4.2/src/proto/livekit_models.d.ts +0 -2659
  164. package/dist/ts4.2/src/proto/livekit_rtc.d.ts +0 -15764
  165. package/src/proto/google/protobuf/timestamp.ts +0 -230
  166. package/src/proto/livekit_models.ts +0 -4006
  167. package/src/proto/livekit_rtc.ts +0 -4672
@@ -1,4 +1,4 @@
1
- import EventEmitter from 'eventemitter3';
1
+ import { EventEmitter } from 'events';
2
2
  import { parse, write } from 'sdp-transform';
3
3
  import type { MediaDescription } from 'sdp-transform';
4
4
  import { debounce } from 'ts-debounce';
@@ -1,7 +1,8 @@
1
- import EventEmitter from 'eventemitter3';
1
+ import { EventEmitter } from 'events';
2
2
  import type { MediaAttributes } from 'sdp-transform';
3
- import { SignalClient } from '../api/SignalClient';
3
+ import type TypedEventEmitter from 'typed-emitter';
4
4
  import type { SignalOptions } from '../api/SignalClient';
5
+ import { SignalClient } from '../api/SignalClient';
5
6
  import log from '../logger';
6
7
  import type { InternalRoomOptions } from '../options';
7
8
  import {
@@ -16,7 +17,7 @@ import {
16
17
  SpeakerInfo,
17
18
  TrackInfo,
18
19
  UserPacket,
19
- } from '../proto/livekit_models';
20
+ } from '../proto/livekit_models_pb';
20
21
  import {
21
22
  AddTrackRequest,
22
23
  ConnectionQualityUpdate,
@@ -28,10 +29,10 @@ import {
28
29
  SubscriptionPermissionUpdate,
29
30
  SubscriptionResponse,
30
31
  TrackPublishedResponse,
31
- } from '../proto/livekit_rtc';
32
+ } from '../proto/livekit_rtc_pb';
32
33
  import PCTransport, { PCEvents } from './PCTransport';
33
34
  import type { ReconnectContext, ReconnectPolicy } from './ReconnectPolicy';
34
- import { RegionUrlProvider } from './RegionUrlProvider';
35
+ import type { RegionUrlProvider } from './RegionUrlProvider';
35
36
  import { roomConnectOptionDefaults } from './defaults';
36
37
  import {
37
38
  ConnectionError,
@@ -49,7 +50,6 @@ import { Track } from './track/Track';
49
50
  import type { TrackPublishOptions, VideoCodec } from './track/options';
50
51
  import {
51
52
  Mutex,
52
- isCloud,
53
53
  isVideoCodec,
54
54
  isWeb,
55
55
  sleep,
@@ -72,7 +72,7 @@ enum PCState {
72
72
  }
73
73
 
74
74
  /** @internal */
75
- export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
75
+ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmitter<EngineEventCallbacks>) {
76
76
  publisher?: PCTransport;
77
77
 
78
78
  subscriber?: PCTransport;
@@ -122,7 +122,7 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
122
122
  // this is helpful to know if we need to restart ICE on the publisher connection
123
123
  private hasPublished: boolean = false;
124
124
 
125
- // keep join info around for reconnect
125
+ // keep join info around for reconnect, this could be a region url
126
126
  private url?: string;
127
127
 
128
128
  private token?: string;
@@ -358,6 +358,11 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
358
358
  return getConnectedAddress(this.primaryPC);
359
359
  }
360
360
 
361
+ /* @internal */
362
+ setRegionUrlProvider(provider: RegionUrlProvider) {
363
+ this.regionUrlProvider = provider;
364
+ }
365
+
361
366
  private configure(joinResponse: JoinResponse) {
362
367
  // already configured
363
368
  if (this.publisher || this.subscriber) {
@@ -492,11 +497,11 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
492
497
 
493
498
  this.client.onLocalTrackPublished = (res: TrackPublishedResponse) => {
494
499
  log.debug('received trackPublishedResponse', res);
495
- const { resolve } = this.pendingTrackResolvers[res.cid];
496
- if (!resolve) {
500
+ if (!this.pendingTrackResolvers[res.cid]) {
497
501
  log.error(`missing track resolver for ${res.cid}`);
498
502
  return;
499
503
  }
504
+ const { resolve } = this.pendingTrackResolvers[res.cid];
500
505
  delete this.pendingTrackResolvers[res.cid];
501
506
  resolve(res.track!);
502
507
  };
@@ -628,12 +633,12 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
628
633
  log.error('unsupported data type', message.data);
629
634
  return;
630
635
  }
631
- const dp = DataPacket.decode(new Uint8Array(buffer));
632
- if (dp.value?.$case === 'speaker') {
636
+ const dp = DataPacket.fromBinary(new Uint8Array(buffer));
637
+ if (dp.value?.case === 'speaker') {
633
638
  // dispatch speaker updates
634
- this.emit(EngineEvent.ActiveSpeakersUpdate, dp.value.speaker.speakers);
635
- } else if (dp.value?.$case === 'user') {
636
- this.emit(EngineEvent.DataPacketReceived, dp.value.user, dp.kind);
639
+ this.emit(EngineEvent.ActiveSpeakersUpdate, dp.value.value.speakers);
640
+ } else if (dp.value?.case === 'user') {
641
+ this.emit(EngineEvent.DataPacketReceived, dp.value.value, dp.kind);
637
642
  }
638
643
  } finally {
639
644
  unlock();
@@ -644,11 +649,11 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
644
649
  const channel = event.currentTarget as RTCDataChannel;
645
650
  const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable';
646
651
 
647
- if (event instanceof ErrorEvent) {
652
+ if (event instanceof ErrorEvent && event.error) {
648
653
  const { error } = event.error;
649
654
  log.error(`DataChannel error on ${channelKind}: ${event.message}`, error);
650
655
  } else {
651
- log.error(`Unknown DataChannel Error on ${channelKind}`, event);
656
+ log.error(`Unknown DataChannel error on ${channelKind}`, event);
652
657
  }
653
658
  };
654
659
 
@@ -843,8 +848,10 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
843
848
  log.debug(`reconnecting in ${delay}ms`);
844
849
 
845
850
  this.clearReconnectTimeout();
846
- if (this.url && this.token && isCloud(new URL(this.url))) {
847
- this.regionUrlProvider = new RegionUrlProvider(this.url, this.token);
851
+ if (this.token && this.regionUrlProvider) {
852
+ // token may have been refreshed, we do not want to recreate the regionUrlProvider
853
+ // since the current engine may have inherited a regional url
854
+ this.regionUrlProvider.updateToken(this.token);
848
855
  }
849
856
  this.reconnectTimeout = CriticalTimers.setTimeout(
850
857
  () => this.attemptReconnect(disconnectReason),
@@ -1114,13 +1121,12 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
1114
1121
  };
1115
1122
  this.once(EngineEvent.Restarted, onRestarted);
1116
1123
  this.once(EngineEvent.Disconnected, onDisconnected);
1117
- this.once(EngineEvent.Closing, onDisconnected);
1118
1124
  });
1119
1125
  };
1120
1126
 
1121
1127
  /* @internal */
1122
1128
  async sendDataPacket(packet: DataPacket, kind: DataPacket_Kind) {
1123
- const msg = DataPacket.encode(packet).finish();
1129
+ const msg = packet.toBinary();
1124
1130
 
1125
1131
  // make sure we do have a data connection
1126
1132
  await this.ensurePublisherConnected(kind);
@@ -1241,7 +1247,7 @@ export default class RTCEngine extends EventEmitter<EngineEventCallbacks> {
1241
1247
  this.hasPublished = true;
1242
1248
 
1243
1249
  const handleClosed = () => {
1244
- log.warn('engine disconnected while negotiation was ongoing');
1250
+ log.debug('engine disconnected while negotiation was ongoing');
1245
1251
  cleanup();
1246
1252
  resolve();
1247
1253
  return;
@@ -1,5 +1,5 @@
1
1
  import log from '../logger';
2
- import type { RegionInfo, RegionSettings } from '../proto/livekit_rtc';
2
+ import type { RegionInfo, RegionSettings } from '../proto/livekit_rtc_pb';
3
3
  import { ConnectionError, ConnectionErrorReason } from './errors';
4
4
  import { isCloud } from './utils';
5
5
 
@@ -21,10 +21,18 @@ export class RegionUrlProvider {
21
21
  this.token = token;
22
22
  }
23
23
 
24
+ updateToken(token: string) {
25
+ this.token = token;
26
+ }
27
+
24
28
  isCloud() {
25
29
  return isCloud(this.serverUrl);
26
30
  }
27
31
 
32
+ getServerUrl() {
33
+ return this.serverUrl;
34
+ }
35
+
28
36
  async getNextBestRegionUrl(abortSignal?: AbortSignal) {
29
37
  if (!this.isCloud()) {
30
38
  throw Error('region availability is only supported for LiveKit Cloud domains');
@@ -49,7 +57,8 @@ export class RegionUrlProvider {
49
57
  this.attemptedRegions = [];
50
58
  }
51
59
 
52
- private async fetchRegionSettings(signal?: AbortSignal) {
60
+ /* @internal */
61
+ async fetchRegionSettings(signal?: AbortSignal) {
53
62
  const regionSettingsResponse = await fetch(`${getCloudConfigUrl(this.serverUrl)}/regions`, {
54
63
  headers: { authorization: `Bearer ${this.token}` },
55
64
  signal,
@@ -1,3 +1,4 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import Room from './Room';
2
3
  import { RoomEvent } from './events';
3
4
 
package/src/room/Room.ts CHANGED
@@ -1,4 +1,6 @@
1
- import EventEmitter from 'eventemitter3';
1
+ import { protoInt64 } from '@bufbuild/protobuf';
2
+ import { EventEmitter } from 'events';
3
+ import type TypedEmitter from 'typed-emitter';
2
4
  import 'webrtc-adapter';
3
5
  import { toProtoSessionDescription } from '../api/SignalClient';
4
6
  import { EncryptionEvent } from '../e2ee';
@@ -24,15 +26,18 @@ import {
24
26
  TrackSource,
25
27
  TrackType,
26
28
  UserPacket,
27
- } from '../proto/livekit_models';
29
+ } from '../proto/livekit_models_pb';
28
30
  import {
29
31
  ConnectionQualityUpdate,
30
32
  JoinResponse,
33
+ LeaveRequest,
31
34
  SimulateScenario,
32
35
  StreamStateUpdate,
33
36
  SubscriptionPermissionUpdate,
34
37
  SubscriptionResponse,
35
- } from '../proto/livekit_rtc';
38
+ SyncState,
39
+ UpdateSubscription,
40
+ } from '../proto/livekit_rtc_pb';
36
41
  import { getBrowser } from '../utils/browserParser';
37
42
  import DeviceManager from './DeviceManager';
38
43
  import RTCEngine from './RTCEngine';
@@ -69,6 +74,7 @@ import {
69
74
  isCloud,
70
75
  isWeb,
71
76
  supportsSetSinkId,
77
+ toHttpUrl,
72
78
  unpackStreamId,
73
79
  unwrapConstraint,
74
80
  } from './utils';
@@ -93,7 +99,7 @@ export const RoomState = ConnectionState;
93
99
  *
94
100
  * @noInheritDoc
95
101
  */
96
- class Room extends EventEmitter<RoomEventCallbacks> {
102
+ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>) {
97
103
  state: ConnectionState = ConnectionState.Disconnected;
98
104
 
99
105
  /** map of sid: [[RemoteParticipant]] */
@@ -142,12 +148,17 @@ class Room extends EventEmitter<RoomEventCallbacks> {
142
148
 
143
149
  private connectionReconcileInterval?: ReturnType<typeof setInterval>;
144
150
 
151
+ private regionUrlProvider?: RegionUrlProvider;
152
+
153
+ private regionUrl?: string;
154
+
145
155
  /**
146
156
  * Creates a new Room, the primary construct for a LiveKit session.
147
157
  * @param options
148
158
  */
149
159
  constructor(options?: RoomOptions) {
150
160
  super();
161
+ this.setMaxListeners(100);
151
162
  this.participants = new Map();
152
163
  this.cachedParticipantSids = [];
153
164
  this.identityToSid = new Map();
@@ -337,15 +348,36 @@ class Room extends EventEmitter<RoomEventCallbacks> {
337
348
  }
338
349
 
339
350
  /**
340
- * prepares the connection to the livekit server by sending a HEAD request in order to
341
- * 1. speed up DNS resolution
342
- * 2. speed up TLS setup
343
- * on the actual connection request
344
- * throws an error if server is not reachable after the request timeout
345
- * @experimental
351
+ * prepareConnection should be called as soon as the page is loaded, in order
352
+ * to speed up the connection attempt. This function will
353
+ * - perform DNS resolution and pre-warm the DNS cache
354
+ * - establish TLS connection and cache TLS keys
355
+ *
356
+ * With LiveKit Cloud, it will also determine the best edge data center for
357
+ * the current client to connect to if a token is provided.
346
358
  */
347
- async prepareConnection(url: string) {
348
- await fetch(`http${url.substring(2)}`, { method: 'HEAD' });
359
+ async prepareConnection(url: string, token?: string) {
360
+ if (this.state !== ConnectionState.Disconnected) {
361
+ return;
362
+ }
363
+ log.debug(`prepareConnection to ${url}`);
364
+ try {
365
+ if (isCloud(new URL(url)) && token) {
366
+ this.regionUrlProvider = new RegionUrlProvider(url, token);
367
+ const regionUrl = await this.regionUrlProvider.getNextBestRegionUrl();
368
+ // we will not replace the regionUrl if an attempt had already started
369
+ // to avoid overriding regionUrl after a new connection attempt had started
370
+ if (regionUrl && this.state === ConnectionState.Disconnected) {
371
+ this.regionUrl = regionUrl;
372
+ await fetch(toHttpUrl(regionUrl), { method: 'HEAD' });
373
+ log.debug(`prepared connection to ${regionUrl}`);
374
+ }
375
+ } else {
376
+ await fetch(toHttpUrl(url), { method: 'HEAD' });
377
+ }
378
+ } catch (e) {
379
+ log.warn('could not prepare connection', { error: e });
380
+ }
349
381
  }
350
382
 
351
383
  connect = async (url: string, token: string, opts?: RoomConnectOptions): Promise<void> => {
@@ -365,8 +397,23 @@ class Room extends EventEmitter<RoomEventCallbacks> {
365
397
  }
366
398
 
367
399
  this.setAndEmitConnectionState(ConnectionState.Connecting);
368
-
369
- const urlProvider = new RegionUrlProvider(url, token);
400
+ if (this.regionUrlProvider?.getServerUrl().toString() !== url) {
401
+ this.regionUrl = undefined;
402
+ this.regionUrlProvider = undefined;
403
+ }
404
+ if (isCloud(new URL(url))) {
405
+ if (this.regionUrlProvider === undefined) {
406
+ this.regionUrlProvider = new RegionUrlProvider(url, token);
407
+ } else {
408
+ this.regionUrlProvider.updateToken(token);
409
+ }
410
+ // trigger the first fetch without waiting for a response
411
+ // if initial connection fails, this will speed up picking regional url
412
+ // on subsequent runs
413
+ this.regionUrlProvider.fetchRegionSettings().catch((e) => {
414
+ log.warn('could not fetch region settings', { error: e });
415
+ });
416
+ }
370
417
 
371
418
  const connectFn = async (
372
419
  resolve: () => void,
@@ -376,6 +423,7 @@ class Room extends EventEmitter<RoomEventCallbacks> {
376
423
  if (this.abortController) {
377
424
  this.abortController.abort();
378
425
  }
426
+
379
427
  this.abortController = new AbortController();
380
428
 
381
429
  // at this point the intention to connect has been signalled so we can allow cancelling of the connection via disconnect() again
@@ -387,13 +435,16 @@ class Room extends EventEmitter<RoomEventCallbacks> {
387
435
  resolve();
388
436
  } catch (e) {
389
437
  if (
390
- isCloud(new URL(url)) &&
438
+ this.regionUrlProvider &&
391
439
  e instanceof ConnectionError &&
392
- e.reason !== ConnectionErrorReason.Cancelled
440
+ e.reason !== ConnectionErrorReason.Cancelled &&
441
+ e.reason !== ConnectionErrorReason.NotAllowed
393
442
  ) {
394
443
  let nextUrl: string | null = null;
395
444
  try {
396
- nextUrl = await urlProvider.getNextBestRegionUrl(this.abortController?.signal);
445
+ nextUrl = await this.regionUrlProvider.getNextBestRegionUrl(
446
+ this.abortController?.signal,
447
+ );
397
448
  } catch (error) {
398
449
  if (
399
450
  error instanceof ConnectionError &&
@@ -404,7 +455,9 @@ class Room extends EventEmitter<RoomEventCallbacks> {
404
455
  }
405
456
  }
406
457
  if (nextUrl) {
407
- log.debug('initial connection failed, retrying with another region');
458
+ log.info('initial connection failed, retrying with another region', {
459
+ nextUrl,
460
+ });
408
461
  await connectFn(resolve, reject, nextUrl);
409
462
  } else {
410
463
  reject(e);
@@ -414,9 +467,17 @@ class Room extends EventEmitter<RoomEventCallbacks> {
414
467
  }
415
468
  }
416
469
  };
417
- this.connectFuture = new Future(connectFn, () => {
418
- this.clearConnectionFutures();
419
- });
470
+
471
+ const regionUrl = this.regionUrl;
472
+ this.regionUrl = undefined;
473
+ this.connectFuture = new Future(
474
+ (resolve, reject) => {
475
+ connectFn(resolve, reject, regionUrl);
476
+ },
477
+ () => {
478
+ this.clearConnectionFutures();
479
+ },
480
+ );
420
481
 
421
482
  return this.connectFuture.promise;
422
483
  };
@@ -495,6 +556,9 @@ class Room extends EventEmitter<RoomEventCallbacks> {
495
556
  // create engine if previously disconnected
496
557
  this.maybeCreateEngine();
497
558
  }
559
+ if (this.regionUrlProvider?.isCloud()) {
560
+ this.engine.setRegionUrlProvider(this.regionUrlProvider);
561
+ }
498
562
 
499
563
  this.acquireAudioContext();
500
564
 
@@ -637,34 +701,34 @@ class Room extends EventEmitter<RoomEventCallbacks> {
637
701
  await this.engine.client.handleOnClose('simulate disconnect');
638
702
  break;
639
703
  case 'speaker':
640
- req = SimulateScenario.fromPartial({
704
+ req = new SimulateScenario({
641
705
  scenario: {
642
- $case: 'speakerUpdate',
643
- speakerUpdate: 3,
706
+ case: 'speakerUpdate',
707
+ value: 3,
644
708
  },
645
709
  });
646
710
  break;
647
711
  case 'node-failure':
648
- req = SimulateScenario.fromPartial({
712
+ req = new SimulateScenario({
649
713
  scenario: {
650
- $case: 'nodeFailure',
651
- nodeFailure: true,
714
+ case: 'nodeFailure',
715
+ value: true,
652
716
  },
653
717
  });
654
718
  break;
655
719
  case 'server-leave':
656
- req = SimulateScenario.fromPartial({
720
+ req = new SimulateScenario({
657
721
  scenario: {
658
- $case: 'serverLeave',
659
- serverLeave: true,
722
+ case: 'serverLeave',
723
+ value: true,
660
724
  },
661
725
  });
662
726
  break;
663
727
  case 'migration':
664
- req = SimulateScenario.fromPartial({
728
+ req = new SimulateScenario({
665
729
  scenario: {
666
- $case: 'migration',
667
- migration: true,
730
+ case: 'migration',
731
+ value: true,
668
732
  },
669
733
  });
670
734
  break;
@@ -680,19 +744,21 @@ class Room extends EventEmitter<RoomEventCallbacks> {
680
744
  break;
681
745
  case 'force-tcp':
682
746
  case 'force-tls':
683
- req = SimulateScenario.fromPartial({
747
+ req = new SimulateScenario({
684
748
  scenario: {
685
- $case: 'switchCandidateProtocol',
686
- switchCandidateProtocol: scenario === 'force-tls' ? 2 : 1,
749
+ case: 'switchCandidateProtocol',
750
+ value: scenario === 'force-tls' ? 2 : 1,
687
751
  },
688
752
  });
689
753
  postAction = async () => {
690
754
  const onLeave = this.engine.client.onLeave;
691
755
  if (onLeave) {
692
- onLeave({
693
- reason: DisconnectReason.CLIENT_INITIATED,
694
- canReconnect: true,
695
- });
756
+ onLeave(
757
+ new LeaveRequest({
758
+ reason: DisconnectReason.CLIENT_INITIATED,
759
+ canReconnect: true,
760
+ }),
761
+ );
696
762
  }
697
763
  };
698
764
  break;
@@ -737,8 +803,19 @@ class Room extends EventEmitter<RoomEventCallbacks> {
737
803
  dummyAudioEl.hidden = true;
738
804
  const track = getEmptyAudioStreamTrack();
739
805
  track.enabled = true;
740
- dummyAudioEl.srcObject = new MediaStream([track]);
806
+ const stream = new MediaStream([track]);
807
+ dummyAudioEl.srcObject = stream;
808
+ document.addEventListener('visibilitychange', () => {
809
+ if (!dummyAudioEl) {
810
+ return;
811
+ }
812
+ // set the srcObject to null on page hide in order to prevent lock screen controls to show up for it
813
+ dummyAudioEl.srcObject = document.hidden ? null : stream;
814
+ });
741
815
  document.body.append(dummyAudioEl);
816
+ this.once(RoomEvent.Disconnected, () => {
817
+ dummyAudioEl?.remove();
818
+ });
742
819
  }
743
820
  elements.push(dummyAudioEl);
744
821
  }
@@ -985,6 +1062,8 @@ class Room extends EventEmitter<RoomEventCallbacks> {
985
1062
  if (!track.isMuted) {
986
1063
  if (
987
1064
  (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) &&
1065
+ track.source !== Track.Source.ScreenShare &&
1066
+ track.source !== Track.Source.ScreenShareAudio &&
988
1067
  !track.isUserProvided
989
1068
  ) {
990
1069
  // we need to restart the track before publishing, often a full reconnect
@@ -1030,6 +1109,8 @@ class Room extends EventEmitter<RoomEventCallbacks> {
1030
1109
  return;
1031
1110
  }
1032
1111
 
1112
+ this.regionUrl = undefined;
1113
+
1033
1114
  try {
1034
1115
  this.participants.forEach((p) => {
1035
1116
  p.tracks.forEach((pub) => {
@@ -1461,25 +1542,27 @@ class Room extends EventEmitter<RoomEventCallbacks> {
1461
1542
  });
1462
1543
  });
1463
1544
 
1464
- this.engine.client.sendSyncState({
1465
- answer: toProtoSessionDescription({
1466
- sdp: previousAnswer.sdp,
1467
- type: previousAnswer.type,
1545
+ this.engine.client.sendSyncState(
1546
+ new SyncState({
1547
+ answer: toProtoSessionDescription({
1548
+ sdp: previousAnswer.sdp,
1549
+ type: previousAnswer.type,
1550
+ }),
1551
+ offer: previousOffer
1552
+ ? toProtoSessionDescription({
1553
+ sdp: previousOffer.sdp,
1554
+ type: previousOffer.type,
1555
+ })
1556
+ : undefined,
1557
+ subscription: new UpdateSubscription({
1558
+ trackSids,
1559
+ subscribe: !autoSubscribe,
1560
+ participantTracks: [],
1561
+ }),
1562
+ publishTracks: this.localParticipant.publishedTracksInfo(),
1563
+ dataChannels: this.localParticipant.dataChannelsInfo(),
1468
1564
  }),
1469
- offer: previousOffer
1470
- ? toProtoSessionDescription({
1471
- sdp: previousOffer.sdp,
1472
- type: previousOffer.type,
1473
- })
1474
- : undefined,
1475
- subscription: {
1476
- trackSids,
1477
- subscribe: !autoSubscribe,
1478
- participantTracks: [],
1479
- },
1480
- publishTracks: this.localParticipant.publishedTracksInfo(),
1481
- dataChannels: this.localParticipant.dataChannelsInfo(),
1482
- });
1565
+ );
1483
1566
  }
1484
1567
 
1485
1568
  /**
@@ -1539,9 +1622,9 @@ class Room extends EventEmitter<RoomEventCallbacks> {
1539
1622
  return true;
1540
1623
  }
1541
1624
 
1542
- private emitWhenConnected<T extends EventEmitter.EventNames<RoomEventCallbacks>>(
1543
- event: T,
1544
- ...args: EventEmitter.EventArgs<RoomEventCallbacks, T>
1625
+ private emitWhenConnected<E extends keyof RoomEventCallbacks>(
1626
+ event: E,
1627
+ ...args: Parameters<RoomEventCallbacks[E]>
1545
1628
  ): boolean {
1546
1629
  if (this.state === ConnectionState.Connected) {
1547
1630
  return this.emit(event, ...args);
@@ -1621,22 +1704,22 @@ class Room extends EventEmitter<RoomEventCallbacks> {
1621
1704
  ...options.participants,
1622
1705
  };
1623
1706
  this.handleDisconnect();
1624
- this.roomInfo = {
1707
+ this.roomInfo = new RoomModel({
1625
1708
  sid: 'RM_SIMULATED',
1626
1709
  name: 'simulated-room',
1627
1710
  emptyTimeout: 0,
1628
1711
  maxParticipants: 0,
1629
- creationTime: new Date().getTime(),
1712
+ creationTime: protoInt64.parse(new Date().getTime()),
1630
1713
  metadata: '',
1631
1714
  numParticipants: 1,
1632
1715
  numPublishers: 1,
1633
1716
  turnPassword: '',
1634
1717
  enabledCodecs: [],
1635
1718
  activeRecording: false,
1636
- };
1719
+ });
1637
1720
 
1638
1721
  this.localParticipant.updateInfo(
1639
- ParticipantInfo.fromPartial({
1722
+ new ParticipantInfo({
1640
1723
  identity: 'simulated-local',
1641
1724
  name: 'local-name',
1642
1725
  }),
@@ -1648,7 +1731,7 @@ class Room extends EventEmitter<RoomEventCallbacks> {
1648
1731
  if (publishOptions.video) {
1649
1732
  const camPub = new LocalTrackPublication(
1650
1733
  Track.Kind.Video,
1651
- TrackInfo.fromPartial({
1734
+ new TrackInfo({
1652
1735
  source: TrackSource.CAMERA,
1653
1736
  sid: Math.floor(Math.random() * 10_000).toString(),
1654
1737
  type: TrackType.AUDIO,
@@ -1674,7 +1757,7 @@ class Room extends EventEmitter<RoomEventCallbacks> {
1674
1757
  if (publishOptions.audio) {
1675
1758
  const audioPub = new LocalTrackPublication(
1676
1759
  Track.Kind.Audio,
1677
- TrackInfo.fromPartial({
1760
+ new TrackInfo({
1678
1761
  source: TrackSource.MICROPHONE,
1679
1762
  sid: Math.floor(Math.random() * 10_000).toString(),
1680
1763
  type: TrackType.AUDIO,
@@ -1691,12 +1774,12 @@ class Room extends EventEmitter<RoomEventCallbacks> {
1691
1774
  }
1692
1775
 
1693
1776
  for (let i = 0; i < participantOptions.count - 1; i += 1) {
1694
- let info: ParticipantInfo = ParticipantInfo.fromPartial({
1777
+ let info: ParticipantInfo = new ParticipantInfo({
1695
1778
  sid: Math.floor(Math.random() * 10_000).toString(),
1696
1779
  identity: `simulated-${i}`,
1697
1780
  state: ParticipantInfo_State.ACTIVE,
1698
1781
  tracks: [],
1699
- joinedAt: Date.now(),
1782
+ joinedAt: protoInt64.parse(Date.now()),
1700
1783
  });
1701
1784
  const p = this.getOrCreateParticipant(info.identity, info);
1702
1785
  if (participantOptions.video) {
@@ -1706,7 +1789,7 @@ class Room extends EventEmitter<RoomEventCallbacks> {
1706
1789
  false,
1707
1790
  true,
1708
1791
  );
1709
- const videoTrack = TrackInfo.fromPartial({
1792
+ const videoTrack = new TrackInfo({
1710
1793
  source: TrackSource.CAMERA,
1711
1794
  sid: Math.floor(Math.random() * 10_000).toString(),
1712
1795
  type: TrackType.AUDIO,
@@ -1716,7 +1799,7 @@ class Room extends EventEmitter<RoomEventCallbacks> {
1716
1799
  }
1717
1800
  if (participantOptions.audio) {
1718
1801
  const dummyTrack = getEmptyAudioStreamTrack();
1719
- const audioTrack = TrackInfo.fromPartial({
1802
+ const audioTrack = new TrackInfo({
1720
1803
  source: TrackSource.MICROPHONE,
1721
1804
  sid: Math.floor(Math.random() * 10_000).toString(),
1722
1805
  type: TrackType.AUDIO,
@@ -1730,14 +1813,10 @@ class Room extends EventEmitter<RoomEventCallbacks> {
1730
1813
  }
1731
1814
 
1732
1815
  // /** @internal */
1733
- emit<T extends EventEmitter.EventNames<RoomEventCallbacks>>(
1734
- event: T,
1735
- ...args: EventEmitter.EventArgs<RoomEventCallbacks, T>
1816
+ emit<E extends keyof RoomEventCallbacks>(
1817
+ event: E,
1818
+ ...args: Parameters<RoomEventCallbacks[E]>
1736
1819
  ): boolean {
1737
- // emit<E extends keyof RoomEventCallbacks>(
1738
- // event: E,
1739
- // ...args: Parameters<RoomEventCallbacks[E]>
1740
- // ): boolean {
1741
1820
  // active speaker updates are too spammy
1742
1821
  if (event !== RoomEvent.ActiveSpeakersChanged) {
1743
1822
  log.debug(`room event ${event}`, { event, args });