livekit-client 1.2.4 → 1.2.5

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 (40) hide show
  1. package/dist/livekit-client.esm.mjs +980 -760
  2. package/dist/livekit-client.esm.mjs.map +1 -1
  3. package/dist/livekit-client.umd.js +1 -1
  4. package/dist/livekit-client.umd.js.map +1 -1
  5. package/dist/src/proto/google/protobuf/timestamp.d.ts +1 -1
  6. package/dist/src/proto/google/protobuf/timestamp.d.ts.map +1 -1
  7. package/dist/src/proto/livekit_models.d.ts +34 -34
  8. package/dist/src/proto/livekit_models.d.ts.map +1 -1
  9. package/dist/src/proto/livekit_rtc.d.ts +124 -124
  10. package/dist/src/proto/livekit_rtc.d.ts.map +1 -1
  11. package/dist/src/room/DeviceManager.d.ts +1 -0
  12. package/dist/src/room/DeviceManager.d.ts.map +1 -1
  13. package/dist/src/room/PCTransport.d.ts +4 -1
  14. package/dist/src/room/PCTransport.d.ts.map +1 -1
  15. package/dist/src/room/RTCEngine.d.ts +1 -0
  16. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  17. package/dist/src/room/Room.d.ts +4 -1
  18. package/dist/src/room/Room.d.ts.map +1 -1
  19. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  20. package/dist/src/room/track/LocalAudioTrack.d.ts +0 -1
  21. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  22. package/dist/src/room/track/LocalVideoTrack.d.ts +1 -1
  23. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  24. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
  25. package/dist/src/room/track/RemoteVideoTrack.d.ts +0 -2
  26. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  27. package/dist/src/room/track/create.d.ts.map +1 -1
  28. package/dist/src/room/utils.d.ts +1 -1
  29. package/dist/src/room/utils.d.ts.map +1 -1
  30. package/package.json +36 -34
  31. package/src/room/DeviceManager.ts +23 -1
  32. package/src/room/RTCEngine.ts +30 -6
  33. package/src/room/Room.ts +143 -145
  34. package/src/room/participant/LocalParticipant.ts +13 -3
  35. package/src/room/track/LocalAudioTrack.ts +0 -2
  36. package/src/room/track/LocalVideoTrack.ts +3 -7
  37. package/src/room/track/RemoteTrackPublication.ts +1 -1
  38. package/src/room/track/RemoteVideoTrack.ts +0 -3
  39. package/src/room/track/create.ts +16 -1
  40. package/src/room/utils.ts +7 -5
package/src/room/Room.ts CHANGED
@@ -99,8 +99,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
99
99
  /** used for aborting pending connections to a LiveKit server */
100
100
  private abortController?: AbortController;
101
101
 
102
+ /** future holding client initiated connection attempt */
102
103
  private connectFuture?: Future<void>;
103
104
 
105
+ /** future holding sdk initiated reconnection attempt */
106
+ private reconnectFuture?: Future<void>;
107
+
104
108
  /**
105
109
  * Creates a new Room, the primary construct for a LiveKit session.
106
110
  * @param options
@@ -156,12 +160,17 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
156
160
  .on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate)
157
161
  .on(EngineEvent.DataPacketReceived, this.handleDataPacket)
158
162
  .on(EngineEvent.Resuming, () => {
163
+ if (!this.reconnectFuture) {
164
+ this.reconnectFuture = new Future();
165
+ }
159
166
  if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
160
167
  this.emit(RoomEvent.Reconnecting);
161
168
  }
162
169
  })
163
170
  .on(EngineEvent.Resumed, () => {
164
171
  this.setAndEmitConnectionState(ConnectionState.Connected);
172
+ this.reconnectFuture?.resolve();
173
+ this.reconnectFuture = undefined;
165
174
  this.emit(RoomEvent.Reconnected);
166
175
  this.updateSubscriptions();
167
176
  })
@@ -193,145 +202,145 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
193
202
  return DeviceManager.getInstance().getDevices(kind, requestPermissions);
194
203
  }
195
204
 
196
- connect = async (url: string, token: string, opts?: RoomConnectOptions): Promise<void> => {
205
+ connect = (url: string, token: string, opts?: RoomConnectOptions): Promise<void> => {
197
206
  if (this.state === ConnectionState.Connected) {
198
207
  // when the state is reconnecting or connected, this function returns immediately
199
208
  log.warn(`already connected to room ${this.name}`);
200
- return;
209
+ return Promise.resolve();
201
210
  }
202
211
 
203
212
  if (this.connectFuture) {
204
213
  return this.connectFuture.promise;
214
+ } else if (this.reconnectFuture) {
215
+ this.connectFuture = this.reconnectFuture;
216
+ return this.connectFuture.promise;
205
217
  }
206
- this.setAndEmitConnectionState(ConnectionState.Connecting);
218
+ const connectPromise = new Promise<void>(async (resolve, reject) => {
219
+ this.setAndEmitConnectionState(ConnectionState.Connecting);
220
+ if (!this.abortController || this.abortController.signal.aborted) {
221
+ this.abortController = new AbortController();
222
+ }
207
223
 
208
- if (!this.abortController || this.abortController.signal.aborted) {
209
- this.abortController = new AbortController();
210
- }
224
+ // recreate engine if previously disconnected
225
+ this.createEngine();
211
226
 
212
- // recreate engine if previously disconnected
213
- this.createEngine();
227
+ this.acquireAudioContext();
214
228
 
215
- this.acquireAudioContext();
229
+ if (opts?.rtcConfig) {
230
+ this.engine.rtcConfig = opts.rtcConfig;
231
+ }
216
232
 
217
- if (opts?.rtcConfig) {
218
- this.engine.rtcConfig = opts.rtcConfig;
219
- }
233
+ this.connOptions = opts;
234
+
235
+ try {
236
+ const joinResponse = await this.engine.join(
237
+ url,
238
+ token,
239
+ {
240
+ autoSubscribe: opts?.autoSubscribe,
241
+ publishOnly: opts?.publishOnly,
242
+ adaptiveStream:
243
+ typeof this.options?.adaptiveStream === 'object'
244
+ ? true
245
+ : this.options?.adaptiveStream,
246
+ },
247
+ this.abortController.signal,
248
+ );
249
+ log.debug(
250
+ `connected to Livekit Server version: ${joinResponse.serverVersion}, region: ${joinResponse.serverRegion}`,
251
+ );
220
252
 
221
- this.connOptions = opts;
253
+ if (!joinResponse.serverVersion) {
254
+ throw new UnsupportedServer('unknown server version');
255
+ }
222
256
 
223
- try {
224
- const joinResponse = await this.engine.join(
225
- url,
226
- token,
227
- {
228
- autoSubscribe: opts?.autoSubscribe,
229
- publishOnly: opts?.publishOnly,
230
- adaptiveStream:
231
- typeof this.options?.adaptiveStream === 'object' ? true : this.options?.adaptiveStream,
232
- },
233
- this.abortController.signal,
234
- );
235
- log.debug(
236
- `connected to Livekit Server version: ${joinResponse.serverVersion}, region: ${joinResponse.serverRegion}`,
237
- );
257
+ if (joinResponse.serverVersion === '0.15.1' && this.options.dynacast) {
258
+ log.debug('disabling dynacast due to server version');
259
+ // dynacast has a bug in 0.15.1, so we cannot use it then
260
+ this.options.dynacast = false;
261
+ }
238
262
 
239
- if (!joinResponse.serverVersion) {
240
- throw new UnsupportedServer('unknown server version');
241
- }
263
+ const pi = joinResponse.participant!;
264
+
265
+ this.localParticipant.sid = pi.sid;
266
+ this.localParticipant.identity = pi.identity;
267
+
268
+ this.localParticipant.updateInfo(pi);
269
+ // forward metadata changed for the local participant
270
+ this.localParticipant
271
+ .on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
272
+ .on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
273
+ .on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
274
+ .on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
275
+ .on(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished)
276
+ .on(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged)
277
+ .on(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError)
278
+ .on(
279
+ ParticipantEvent.ParticipantPermissionsChanged,
280
+ this.onLocalParticipantPermissionsChanged,
281
+ );
242
282
 
243
- if (joinResponse.serverVersion === '0.15.1' && this.options.dynacast) {
244
- log.debug('disabling dynacast due to server version');
245
- // dynacast has a bug in 0.15.1, so we cannot use it then
246
- this.options.dynacast = false;
247
- }
283
+ // populate remote participants, these should not trigger new events
284
+ joinResponse.otherParticipants.forEach((info) => {
285
+ if (
286
+ info.sid !== this.localParticipant.sid &&
287
+ info.identity !== this.localParticipant.identity
288
+ ) {
289
+ this.getOrCreateParticipant(info.sid, info);
290
+ } else {
291
+ log.warn('received info to create local participant as remote participant', {
292
+ info,
293
+ localParticipant: this.localParticipant,
294
+ });
295
+ }
296
+ });
248
297
 
249
- const pi = joinResponse.participant!;
250
-
251
- this.localParticipant.sid = pi.sid;
252
- this.localParticipant.identity = pi.identity;
253
-
254
- this.localParticipant.updateInfo(pi);
255
- // forward metadata changed for the local participant
256
- this.localParticipant
257
- .on(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged)
258
- .on(ParticipantEvent.TrackMuted, this.onLocalTrackMuted)
259
- .on(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted)
260
- .on(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished)
261
- .on(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished)
262
- .on(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged)
263
- .on(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError)
264
- .on(
265
- ParticipantEvent.ParticipantPermissionsChanged,
266
- this.onLocalParticipantPermissionsChanged,
267
- );
298
+ this.name = joinResponse.room!.name;
299
+ this.sid = joinResponse.room!.sid;
300
+ this.metadata = joinResponse.room!.metadata;
301
+ this.emit(RoomEvent.SignalConnected);
302
+ } catch (err) {
303
+ this.recreateEngine();
304
+ this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
305
+ reject(new ConnectionError('could not establish signal connection'));
306
+ }
268
307
 
269
- // populate remote participants, these should not trigger new events
270
- joinResponse.otherParticipants.forEach((info) => {
271
- if (
272
- info.sid !== this.localParticipant.sid &&
273
- info.identity !== this.localParticipant.identity
274
- ) {
275
- this.getOrCreateParticipant(info.sid, info);
276
- } else {
277
- log.warn('received info to create local participant as remote participant', {
278
- info,
279
- localParticipant: this.localParticipant,
280
- });
308
+ // don't return until ICE connected
309
+ const connectTimeout = setTimeout(() => {
310
+ // timeout
311
+ this.recreateEngine();
312
+ this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
313
+ reject(new ConnectionError('could not connect PeerConnection after timeout'));
314
+ }, maxICEConnectTimeout);
315
+ const abortHandler = () => {
316
+ log.warn('closing engine');
317
+ clearTimeout(connectTimeout);
318
+ this.recreateEngine();
319
+ this.handleDisconnect(this.options.stopLocalTrackOnUnpublish);
320
+ reject(new ConnectionError('room connection has been cancelled'));
321
+ };
322
+ if (this.abortController?.signal.aborted) {
323
+ abortHandler();
324
+ }
325
+ this.abortController?.signal.addEventListener('abort', abortHandler);
326
+
327
+ this.engine.once(EngineEvent.Connected, () => {
328
+ clearTimeout(connectTimeout);
329
+ this.abortController?.signal.removeEventListener('abort', abortHandler);
330
+ // also hook unload event
331
+ if (isWeb()) {
332
+ window.addEventListener('beforeunload', this.onBeforeUnload);
333
+ navigator.mediaDevices?.addEventListener('devicechange', this.handleDeviceChange);
281
334
  }
335
+ this.setAndEmitConnectionState(ConnectionState.Connected);
336
+ resolve();
282
337
  });
283
-
284
- this.name = joinResponse.room!.name;
285
- this.sid = joinResponse.room!.sid;
286
- this.metadata = joinResponse.room!.metadata;
287
- this.emit(RoomEvent.SignalConnected);
288
- } catch (err) {
289
- this.recreateEngine();
290
- this.setAndEmitConnectionState(
291
- ConnectionState.Disconnected,
292
- new ConnectionError('could not establish signal connection'),
293
- );
294
- throw err;
295
- }
296
-
297
- // don't return until ICE connected
298
- const connectTimeout = setTimeout(() => {
299
- // timeout
300
- this.recreateEngine();
301
- this.setAndEmitConnectionState(
302
- ConnectionState.Disconnected,
303
- new ConnectionError('could not connect PeerConnection after timeout'),
304
- );
305
- }, maxICEConnectTimeout);
306
- const abortHandler = () => {
307
- log.warn('closing engine');
308
- clearTimeout(connectTimeout);
309
- this.recreateEngine();
310
- this.setAndEmitConnectionState(
311
- ConnectionState.Disconnected,
312
- new ConnectionError('room connection has been cancelled'),
313
- );
314
- };
315
- if (this.abortController?.signal.aborted) {
316
- abortHandler();
317
- }
318
- this.abortController?.signal.addEventListener('abort', abortHandler);
319
-
320
- this.engine.once(EngineEvent.Connected, () => {
321
- clearTimeout(connectTimeout);
322
- this.abortController?.signal.removeEventListener('abort', abortHandler);
323
- // also hook unload event
324
- if (isWeb()) {
325
- window.addEventListener('beforeunload', this.onBeforeUnload);
326
- navigator.mediaDevices?.addEventListener('devicechange', this.handleDeviceChange);
327
- }
328
- this.setAndEmitConnectionState(ConnectionState.Connected);
329
338
  });
339
+ this.connectFuture = new Future(connectPromise);
330
340
 
331
- if (this.connectFuture) {
332
- /** @ts-ignore */
333
- return this.connectFuture.promise;
334
- }
341
+ this.connectFuture.promise.finally(() => (this.connectFuture = undefined));
342
+
343
+ return this.connectFuture.promise;
335
344
  };
336
345
 
337
346
  /**
@@ -339,11 +348,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
339
348
  */
340
349
  disconnect = async (stopTracks = true) => {
341
350
  log.info('disconnect from room', { identity: this.localParticipant.identity });
342
- if (this.state === ConnectionState.Connecting) {
351
+ if (this.state === ConnectionState.Connecting || this.state === ConnectionState.Reconnecting) {
343
352
  // try aborting pending connection attempt
344
353
  log.warn('abort connection attempt');
345
354
  this.abortController?.abort();
346
- return;
355
+ // in case the abort controller didn't manage to cancel the connection attempt, reject the connect promise explicitly
356
+ this.connectFuture?.reject(new ConnectionError('Client initiated disconnect'));
357
+ this.connectFuture = undefined;
347
358
  }
348
359
  // send leave
349
360
  if (this.engine?.client.isConnected) {
@@ -353,7 +364,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
353
364
  if (this.engine) {
354
365
  this.engine.close();
355
366
  }
356
-
357
367
  this.handleDisconnect(stopTracks, DisconnectReason.CLIENT_INITIATED);
358
368
  /* @ts-ignore */
359
369
  this.engine = undefined;
@@ -571,6 +581,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
571
581
  }
572
582
 
573
583
  private handleRestarting = () => {
584
+ if (!this.reconnectFuture) {
585
+ this.reconnectFuture = new Future();
586
+ }
574
587
  // also unwind existing participants & existing subscriptions
575
588
  for (const p of this.participants.values()) {
576
589
  this.handleParticipantDisconnected(p.sid, p);
@@ -587,6 +600,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
587
600
  });
588
601
  this.setAndEmitConnectionState(ConnectionState.Connected);
589
602
  this.emit(RoomEvent.Reconnected);
603
+ this.reconnectFuture?.resolve();
604
+ this.reconnectFuture = undefined;
590
605
 
591
606
  // rehydrate participants
592
607
  if (joinResponse.participant) {
@@ -630,6 +645,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
630
645
  if (this.state === ConnectionState.Disconnected) {
631
646
  return;
632
647
  }
648
+ // reject potentially ongoing reconnection attempt
649
+ if (this.connectFuture === this.reconnectFuture) {
650
+ this.connectFuture?.reject(undefined);
651
+ this.connectFuture = undefined;
652
+ this.reconnectFuture = undefined;
653
+ }
654
+
633
655
  this.participants.forEach((p) => {
634
656
  p.tracks.forEach((pub) => {
635
657
  p.unpublishTrack(pub.trackSid);
@@ -1030,35 +1052,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1030
1052
  }
1031
1053
  }
1032
1054
 
1033
- private setAndEmitConnectionState(state: ConnectionState, error?: Error): boolean {
1055
+ private setAndEmitConnectionState(state: ConnectionState): boolean {
1034
1056
  if (state === this.state) {
1035
1057
  // unchanged
1036
1058
  return false;
1037
1059
  }
1038
- switch (state) {
1039
- case ConnectionState.Connecting:
1040
- case ConnectionState.Reconnecting:
1041
- if (!this.connectFuture) {
1042
- // reuse existing connect future if possible
1043
- this.connectFuture = new Future<void>();
1044
- }
1045
- break;
1046
- case ConnectionState.Connected:
1047
- if (this.connectFuture) {
1048
- this.connectFuture.resolve();
1049
- this.connectFuture = undefined;
1050
- }
1051
- break;
1052
- case ConnectionState.Disconnected:
1053
- if (this.connectFuture) {
1054
- error ??= new Error('disconnected from Room');
1055
- this.connectFuture.reject(error);
1056
- this.connectFuture = undefined;
1057
- }
1058
- break;
1059
- default:
1060
- // nothing
1061
- }
1062
1060
  this.state = state;
1063
1061
  this.emit(RoomEvent.ConnectionStateChanged, this.state);
1064
1062
  return true;
@@ -722,10 +722,20 @@ export default class LocalParticipant extends Participant {
722
722
  track.sender
723
723
  ) {
724
724
  try {
725
- this.engine.publisher.pc.removeTrack(track.sender);
726
- this.engine.negotiate();
725
+ this.engine.removeTrack(track.sender);
726
+ if (track instanceof LocalVideoTrack) {
727
+ for (const [, trackInfo] of track.simulcastCodecs) {
728
+ if (trackInfo.sender) {
729
+ this.engine.removeTrack(trackInfo.sender);
730
+ trackInfo.sender = undefined;
731
+ }
732
+ }
733
+ track.simulcastCodecs.clear();
734
+ }
727
735
  } catch (e) {
728
- log.warn('failed to remove track', { error: e, method: 'unpublishTrack' });
736
+ log.warn('failed to unpublish track', { error: e, method: 'unpublishTrack' });
737
+ } finally {
738
+ this.engine.negotiate();
729
739
  }
730
740
  }
731
741
 
@@ -8,8 +8,6 @@ import { Track } from './Track';
8
8
  import { constraintsForOptions, detectSilence } from './utils';
9
9
 
10
10
  export default class LocalAudioTrack extends LocalTrack {
11
- sender?: RTCRtpSender;
12
-
13
11
  /** @internal */
14
12
  stopOnMute: boolean = false;
15
13
 
@@ -27,17 +27,15 @@ export class SimulcastTrackInfo {
27
27
  const refreshSubscribedCodecAfterNewCodec = 5000;
28
28
 
29
29
  export default class LocalVideoTrack extends LocalTrack {
30
- /* internal */
30
+ /* @internal */
31
31
  signalClient?: SignalClient;
32
32
 
33
33
  private prevStats?: Map<string, VideoSenderStats>;
34
34
 
35
35
  private encodings?: RTCRtpEncodingParameters[];
36
36
 
37
- private simulcastCodecs: Map<VideoCodec, SimulcastTrackInfo> = new Map<
38
- VideoCodec,
39
- SimulcastTrackInfo
40
- >();
37
+ /* @internal */
38
+ simulcastCodecs: Map<VideoCodec, SimulcastTrackInfo> = new Map<VideoCodec, SimulcastTrackInfo>();
41
39
 
42
40
  private subscribedCodecs?: SubscribedCodec[];
43
41
 
@@ -78,9 +76,7 @@ export default class LocalVideoTrack extends LocalTrack {
78
76
  this._mediaStreamTrack.getConstraints();
79
77
  this.simulcastCodecs.forEach((trackInfo) => {
80
78
  trackInfo.mediaStreamTrack.stop();
81
- trackInfo.sender = undefined;
82
79
  });
83
- this.simulcastCodecs.clear();
84
80
  super.stop();
85
81
  }
86
82
 
@@ -8,7 +8,7 @@ import { TrackPublication } from './TrackPublication';
8
8
  import { RemoteTrack } from './types';
9
9
 
10
10
  export default class RemoteTrackPublication extends TrackPublication {
11
- track?: RemoteTrack;
11
+ track?: RemoteTrack = undefined;
12
12
 
13
13
  /** @internal */
14
14
  protected allowed = true;
@@ -10,9 +10,6 @@ import { AdaptiveStreamSettings } from './types';
10
10
  const REACTION_DELAY = 100;
11
11
 
12
12
  export default class RemoteVideoTrack extends RemoteTrack {
13
- /** @internal */
14
- receiver?: RTCRtpReceiver;
15
-
16
13
  private prevStats?: VideoReceiverStats;
17
14
 
18
15
  private elementInfos: ElementInfo[] = [];
@@ -1,3 +1,4 @@
1
+ import DeviceManager from '../DeviceManager';
1
2
  import { TrackInvalidError } from '../errors';
2
3
  import { mediaTrackToLocalTrack } from '../participant/publishUtils';
3
4
  import { audioDefaults, videoDefaults } from './defaults';
@@ -30,7 +31,21 @@ export async function createLocalTracks(
30
31
 
31
32
  const opts = mergeDefaultOptions(options, audioDefaults, videoDefaults);
32
33
  const constraints = constraintsForOptions(opts);
33
- const stream = await navigator.mediaDevices.getUserMedia(constraints);
34
+
35
+ // Keep a reference to the promise on DeviceManager and await it in getLocalDevices()
36
+ // works around iOS Safari Bug https://bugs.webkit.org/show_bug.cgi?id=179363
37
+ const mediaPromise = navigator.mediaDevices.getUserMedia(constraints);
38
+
39
+ if (options.audio) {
40
+ DeviceManager.userMediaPromiseMap.set('audioinput', mediaPromise);
41
+ mediaPromise.catch(() => DeviceManager.userMediaPromiseMap.delete('audioinput'));
42
+ }
43
+ if (options.video) {
44
+ DeviceManager.userMediaPromiseMap.set('videoinput', mediaPromise);
45
+ mediaPromise.catch(() => DeviceManager.userMediaPromiseMap.delete('videoinput'));
46
+ }
47
+
48
+ const stream = await mediaPromise;
34
49
  return stream.getTracks().map((mediaStreamTrack) => {
35
50
  const isAudio = mediaStreamTrack.kind === 'audio';
36
51
  let trackOptions = isAudio ? options!.audio : options!.video;
package/src/room/utils.ts CHANGED
@@ -122,10 +122,12 @@ export class Future<T> {
122
122
 
123
123
  reject!: (e: any) => void;
124
124
 
125
- constructor() {
126
- this.promise = new Promise<T>((resolve, reject) => {
127
- this.resolve = resolve;
128
- this.reject = reject;
129
- });
125
+ constructor(promise?: Promise<T>) {
126
+ this.promise =
127
+ promise ??
128
+ new Promise<T>((resolve, reject) => {
129
+ this.resolve = resolve;
130
+ this.reject = reject;
131
+ });
130
132
  }
131
133
  }