livekit-client 1.12.1 → 1.12.3

Sign up to get free protection for your applications and to get access to all the features.
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 });