livekit-client 1.11.0 → 1.11.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. package/dist/livekit-client.esm.mjs +139 -89
  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/room/Room.d.ts +5 -4
  6. package/dist/src/room/Room.d.ts.map +1 -1
  7. package/dist/src/room/events.d.ts +6 -1
  8. package/dist/src/room/events.d.ts.map +1 -1
  9. package/dist/src/room/track/LocalAudioTrack.d.ts +1 -1
  10. package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
  11. package/dist/src/room/track/LocalTrack.d.ts +2 -2
  12. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  13. package/dist/src/room/track/LocalVideoTrack.d.ts +1 -1
  14. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  15. package/dist/src/room/track/RemoteVideoTrack.d.ts +3 -1
  16. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  17. package/dist/src/room/utils.d.ts +1 -0
  18. package/dist/src/room/utils.d.ts.map +1 -1
  19. package/dist/ts4.2/src/room/Room.d.ts +5 -4
  20. package/dist/ts4.2/src/room/events.d.ts +6 -1
  21. package/dist/ts4.2/src/room/track/LocalAudioTrack.d.ts +1 -1
  22. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +2 -2
  23. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +1 -1
  24. package/dist/ts4.2/src/room/track/RemoteVideoTrack.d.ts +3 -1
  25. package/dist/ts4.2/src/room/utils.d.ts +1 -0
  26. package/package.json +1 -1
  27. package/src/room/Room.test.ts +29 -0
  28. package/src/room/Room.ts +55 -10
  29. package/src/room/events.ts +6 -0
  30. package/src/room/track/LocalAudioTrack.ts +4 -3
  31. package/src/room/track/LocalTrack.ts +13 -10
  32. package/src/room/track/LocalVideoTrack.ts +8 -4
  33. package/src/room/track/RemoteVideoTrack.ts +3 -8
  34. package/src/room/utils.ts +23 -0
@@ -14187,7 +14187,7 @@ function getMatch(exp, ua) {
14187
14187
  return match && match.length >= id && match[id] || '';
14188
14188
  }
14189
14189
 
14190
- var version$1 = "1.11.0";
14190
+ var version$1 = "1.11.2";
14191
14191
 
14192
14192
  const version = version$1;
14193
14193
  const protocolVersion = 9;
@@ -14636,6 +14636,27 @@ class Mutex {
14636
14636
  return willUnlock;
14637
14637
  }
14638
14638
  }
14639
+ function unwrapConstraint(constraint) {
14640
+ if (typeof constraint === 'string') {
14641
+ return constraint;
14642
+ }
14643
+ if (Array.isArray(constraint)) {
14644
+ return constraint[0];
14645
+ }
14646
+ if (constraint.exact) {
14647
+ if (Array.isArray(constraint.exact)) {
14648
+ return constraint.exact[0];
14649
+ }
14650
+ return constraint.exact;
14651
+ }
14652
+ if (constraint.ideal) {
14653
+ if (Array.isArray(constraint.ideal)) {
14654
+ return constraint.ideal[0];
14655
+ }
14656
+ return constraint.ideal;
14657
+ }
14658
+ throw Error('could not unwrap constraint');
14659
+ }
14639
14660
 
14640
14661
  var QueueTaskStatus;
14641
14662
  (function (QueueTaskStatus) {
@@ -16734,6 +16755,11 @@ var RoomEvent;
16734
16755
  * args: (isLow: boolean, kind: [[DataPacket_Kind]])
16735
16756
  */
16736
16757
  RoomEvent["DCBufferStatusChanged"] = "dcBufferStatusChanged";
16758
+ /**
16759
+ * Triggered by a call to room.switchActiveDevice
16760
+ * args: (kind: MediaDeviceKind, deviceId: string)
16761
+ */
16762
+ RoomEvent["ActiveDeviceChanged"] = "activeDeviceChanged";
16737
16763
  })(RoomEvent || (RoomEvent = {}));
16738
16764
  var ParticipantEvent;
16739
16765
  (function (ParticipantEvent) {
@@ -18377,8 +18403,56 @@ class LocalTrack extends Track {
18377
18403
  if (this.isInBackground) {
18378
18404
  this.reacquireTrack = true;
18379
18405
  }
18406
+ this._mediaStreamTrack.removeEventListener('mute', this.pauseUpstream);
18407
+ this._mediaStreamTrack.removeEventListener('unmute', this.resumeUpstream);
18380
18408
  this.emit(TrackEvent.Ended, this);
18381
18409
  };
18410
+ /**
18411
+ * pauses publishing to the server without disabling the local MediaStreamTrack
18412
+ * this is used to display a user's own video locally while pausing publishing to
18413
+ * the server.
18414
+ * this API is unsupported on Safari < 12 due to a bug
18415
+ **/
18416
+ this.pauseUpstream = () => __awaiter(this, void 0, void 0, function* () {
18417
+ const unlock = yield this.pauseUpstreamLock.lock();
18418
+ try {
18419
+ if (this._isUpstreamPaused === true) {
18420
+ return;
18421
+ }
18422
+ if (!this.sender) {
18423
+ livekitLogger.warn('unable to pause upstream for an unpublished track');
18424
+ return;
18425
+ }
18426
+ this._isUpstreamPaused = true;
18427
+ this.emit(TrackEvent.UpstreamPaused, this);
18428
+ const browser = getBrowser();
18429
+ if ((browser === null || browser === void 0 ? void 0 : browser.name) === 'Safari' && compareVersions(browser.version, '12.0') < 0) {
18430
+ // https://bugs.webkit.org/show_bug.cgi?id=184911
18431
+ throw new DeviceUnsupportedError('pauseUpstream is not supported on Safari < 12.');
18432
+ }
18433
+ yield this.sender.replaceTrack(null);
18434
+ } finally {
18435
+ unlock();
18436
+ }
18437
+ });
18438
+ this.resumeUpstream = () => __awaiter(this, void 0, void 0, function* () {
18439
+ const unlock = yield this.pauseUpstreamLock.lock();
18440
+ try {
18441
+ if (this._isUpstreamPaused === false) {
18442
+ return;
18443
+ }
18444
+ if (!this.sender) {
18445
+ livekitLogger.warn('unable to resume upstream for an unpublished track');
18446
+ return;
18447
+ }
18448
+ this._isUpstreamPaused = false;
18449
+ this.emit(TrackEvent.UpstreamResumed, this);
18450
+ // this operation is noop if mediastreamtrack is already being sent
18451
+ yield this.sender.replaceTrack(this._mediaStreamTrack);
18452
+ } finally {
18453
+ unlock();
18454
+ }
18455
+ });
18382
18456
  this.reacquireTrack = false;
18383
18457
  this.providedByUser = userProvidedTrack;
18384
18458
  this.muteLock = new Mutex();
@@ -18444,10 +18518,7 @@ class LocalTrack extends Track {
18444
18518
  // the track is "muted"
18445
18519
  // note this is different from LocalTrack.mute because we do not want to
18446
18520
  // touch MediaStreamTrack.enabled
18447
- newTrack.addEventListener('mute', () => {
18448
- livekitLogger.info('pausing upstream due to device mute');
18449
- this.pauseUpstream();
18450
- });
18521
+ newTrack.addEventListener('mute', this.pauseUpstream);
18451
18522
  newTrack.addEventListener('unmute', this.resumeUpstream);
18452
18523
  this.constraints = newTrack.getConstraints();
18453
18524
  }
@@ -18519,7 +18590,7 @@ class LocalTrack extends Track {
18519
18590
  throw new TrackInvalidError('unable to replace an unpublished track');
18520
18591
  }
18521
18592
  livekitLogger.debug('replace MediaStreamTrack');
18522
- this.setMediaStreamTrack(track);
18593
+ yield this.setMediaStreamTrack(track);
18523
18594
  // this must be synced *after* setting mediaStreamTrack above, since it relies
18524
18595
  // on the previous state in order to cleanup
18525
18596
  this.providedByUser = userProvidedTrack;
@@ -18559,7 +18630,7 @@ class LocalTrack extends Track {
18559
18630
  const newTrack = mediaStream.getTracks()[0];
18560
18631
  newTrack.addEventListener('ended', this.handleEnded);
18561
18632
  livekitLogger.debug('re-acquired MediaStreamTrack');
18562
- this.setMediaStreamTrack(newTrack);
18633
+ yield this.setMediaStreamTrack(newTrack);
18563
18634
  this.constraints = constraints;
18564
18635
  if (this.processor) {
18565
18636
  const processor = this.processor;
@@ -18605,59 +18676,12 @@ class LocalTrack extends Track {
18605
18676
  stop() {
18606
18677
  var _a;
18607
18678
  super.stop();
18679
+ this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
18680
+ this._mediaStreamTrack.removeEventListener('mute', this.pauseUpstream);
18681
+ this._mediaStreamTrack.removeEventListener('unmute', this.resumeUpstream);
18608
18682
  (_a = this.processor) === null || _a === void 0 ? void 0 : _a.destroy();
18609
18683
  this.processor = undefined;
18610
18684
  }
18611
- /**
18612
- * pauses publishing to the server without disabling the local MediaStreamTrack
18613
- * this is used to display a user's own video locally while pausing publishing to
18614
- * the server.
18615
- * this API is unsupported on Safari < 12 due to a bug
18616
- **/
18617
- pauseUpstream() {
18618
- return __awaiter(this, void 0, void 0, function* () {
18619
- const unlock = yield this.pauseUpstreamLock.lock();
18620
- try {
18621
- if (this._isUpstreamPaused === true) {
18622
- return;
18623
- }
18624
- if (!this.sender) {
18625
- livekitLogger.warn('unable to pause upstream for an unpublished track');
18626
- return;
18627
- }
18628
- this._isUpstreamPaused = true;
18629
- this.emit(TrackEvent.UpstreamPaused, this);
18630
- const browser = getBrowser();
18631
- if ((browser === null || browser === void 0 ? void 0 : browser.name) === 'Safari' && compareVersions(browser.version, '12.0') < 0) {
18632
- // https://bugs.webkit.org/show_bug.cgi?id=184911
18633
- throw new DeviceUnsupportedError('pauseUpstream is not supported on Safari < 12.');
18634
- }
18635
- yield this.sender.replaceTrack(null);
18636
- } finally {
18637
- unlock();
18638
- }
18639
- });
18640
- }
18641
- resumeUpstream() {
18642
- return __awaiter(this, void 0, void 0, function* () {
18643
- const unlock = yield this.pauseUpstreamLock.lock();
18644
- try {
18645
- if (this._isUpstreamPaused === false) {
18646
- return;
18647
- }
18648
- if (!this.sender) {
18649
- livekitLogger.warn('unable to resume upstream for an unpublished track');
18650
- return;
18651
- }
18652
- this._isUpstreamPaused = false;
18653
- this.emit(TrackEvent.UpstreamResumed, this);
18654
- // this operation is noop if mediastreamtrack is already being sent
18655
- yield this.sender.replaceTrack(this._mediaStreamTrack);
18656
- } finally {
18657
- unlock();
18658
- }
18659
- });
18660
- }
18661
18685
  /**
18662
18686
  * Sets a processor on this track.
18663
18687
  * See https://github.com/livekit/track-processors-js for example usage
@@ -18768,12 +18792,13 @@ class LocalAudioTrack extends LocalTrack {
18768
18792
  setDeviceId(deviceId) {
18769
18793
  return __awaiter(this, void 0, void 0, function* () {
18770
18794
  if (this.constraints.deviceId === deviceId) {
18771
- return;
18795
+ return true;
18772
18796
  }
18773
18797
  this.constraints.deviceId = deviceId;
18774
18798
  if (!this.isMuted) {
18775
18799
  yield this.restartTrack();
18776
18800
  }
18801
+ return unwrapConstraint(deviceId) === this.mediaStreamTrack.getSettings().deviceId;
18777
18802
  });
18778
18803
  }
18779
18804
  mute() {
@@ -19361,8 +19386,8 @@ class LocalVideoTrack extends LocalTrack {
19361
19386
  }
19362
19387
  setDeviceId(deviceId) {
19363
19388
  return __awaiter(this, void 0, void 0, function* () {
19364
- if (this.constraints.deviceId === deviceId) {
19365
- return;
19389
+ if (this.constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)) {
19390
+ return true;
19366
19391
  }
19367
19392
  this.constraints.deviceId = deviceId;
19368
19393
  // when video is muted, underlying media stream track is stopped and
@@ -19370,6 +19395,7 @@ class LocalVideoTrack extends LocalTrack {
19370
19395
  if (!this.isMuted) {
19371
19396
  yield this.restartTrack();
19372
19397
  }
19398
+ return unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
19373
19399
  });
19374
19400
  }
19375
19401
  restartTrack(options) {
@@ -19864,7 +19890,6 @@ class RemoteVideoTrack extends RemoteTrack {
19864
19890
  constructor(mediaTrack, sid, receiver, adaptiveStreamSettings) {
19865
19891
  super(mediaTrack, sid, Track.Kind.Video, receiver);
19866
19892
  this.elementInfos = [];
19867
- this.isObserved = false;
19868
19893
  this.monitorReceiver = () => __awaiter(this, void 0, void 0, function* () {
19869
19894
  if (!this.receiver) {
19870
19895
  this._currentBitrate = 0;
@@ -19884,10 +19909,10 @@ class RemoteVideoTrack extends RemoteTrack {
19884
19909
  get isAdaptiveStream() {
19885
19910
  return this.adaptiveStreamSettings !== undefined;
19886
19911
  }
19912
+ /**
19913
+ * Note: When using adaptiveStream, you need to use remoteVideoTrack.attach() to add the track to a HTMLVideoElement, otherwise your video tracks might never start
19914
+ */
19887
19915
  get mediaStreamTrack() {
19888
- if (this.isAdaptiveStream && !this.isObserved) {
19889
- livekitLogger.warn('When using adaptiveStream, you need to use remoteVideoTrack.attach() to add the track to a HTMLVideoElement, otherwise your video tracks might never start');
19890
- }
19891
19916
  return this._mediaStreamTrack;
19892
19917
  }
19893
19918
  /** @internal */
@@ -19936,7 +19961,6 @@ class RemoteVideoTrack extends RemoteTrack {
19936
19961
  // the tab comes into focus for the first time.
19937
19962
  this.debouncedHandleResize();
19938
19963
  this.updateVisibility();
19939
- this.isObserved = true;
19940
19964
  } else {
19941
19965
  livekitLogger.warn('visibility resize observer not triggered');
19942
19966
  }
@@ -22064,6 +22088,7 @@ class Room extends EventEmitter {
22064
22088
  */
22065
22089
  constructor(options) {
22066
22090
  var _this;
22091
+ var _a;
22067
22092
  super();
22068
22093
  _this = this;
22069
22094
  this.state = ConnectionState.Disconnected;
@@ -22089,7 +22114,7 @@ class Room extends EventEmitter {
22089
22114
  this.setAndEmitConnectionState(ConnectionState.Connecting);
22090
22115
  const urlProvider = new RegionUrlProvider(url, token);
22091
22116
  const connectFn = (resolve, reject, regionUrl) => __awaiter(this, void 0, void 0, function* () {
22092
- var _a;
22117
+ var _b;
22093
22118
  if (this.abortController) {
22094
22119
  this.abortController.abort();
22095
22120
  }
@@ -22104,7 +22129,7 @@ class Room extends EventEmitter {
22104
22129
  if (isCloud(new URL(url)) && e instanceof ConnectionError && e.reason !== 3 /* ConnectionErrorReason.Cancelled */) {
22105
22130
  let nextUrl = null;
22106
22131
  try {
22107
- nextUrl = yield urlProvider.getNextBestRegionUrl((_a = this.abortController) === null || _a === void 0 ? void 0 : _a.signal);
22132
+ nextUrl = yield urlProvider.getNextBestRegionUrl((_b = this.abortController) === null || _b === void 0 ? void 0 : _b.signal);
22108
22133
  } catch (error) {
22109
22134
  if (error instanceof ConnectionError && (error.status === 401 || error.reason === 3 /* ConnectionErrorReason.Cancelled */)) {
22110
22135
  reject(error);
@@ -22166,7 +22191,7 @@ class Room extends EventEmitter {
22166
22191
  }
22167
22192
  };
22168
22193
  this.attemptConnection = (url, token, opts, abortController) => __awaiter(this, void 0, void 0, function* () {
22169
- var _b;
22194
+ var _c;
22170
22195
  if (this.state === ConnectionState.Reconnecting) {
22171
22196
  livekitLogger.info('Reconnection attempt replaced by new connection attempt');
22172
22197
  // make sure we close and recreate the existing engine in order to get rid of any potentially ongoing reconnection attempts
@@ -22225,7 +22250,7 @@ class Room extends EventEmitter {
22225
22250
  }
22226
22251
  if (isWeb()) {
22227
22252
  document.addEventListener('freeze', this.onPageLeave);
22228
- (_b = navigator.mediaDevices) === null || _b === void 0 ? void 0 : _b.addEventListener('devicechange', this.handleDeviceChange);
22253
+ (_c = navigator.mediaDevices) === null || _c === void 0 ? void 0 : _c.addEventListener('devicechange', this.handleDeviceChange);
22229
22254
  }
22230
22255
  this.setAndEmitConnectionState(ConnectionState.Connected);
22231
22256
  this.emit(RoomEvent.Connected);
@@ -22237,7 +22262,7 @@ class Room extends EventEmitter {
22237
22262
  this.disconnect = function () {
22238
22263
  let stopTracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
22239
22264
  return __awaiter(_this, void 0, void 0, function* () {
22240
- var _c, _d, _e, _f;
22265
+ var _d, _e, _f, _g;
22241
22266
  const unlock = yield this.disconnectLock.lock();
22242
22267
  try {
22243
22268
  if (this.state === ConnectionState.Disconnected) {
@@ -22250,13 +22275,13 @@ class Room extends EventEmitter {
22250
22275
  if (this.state === ConnectionState.Connecting || this.state === ConnectionState.Reconnecting) {
22251
22276
  // try aborting pending connection attempt
22252
22277
  livekitLogger.warn('abort connection attempt');
22253
- (_c = this.abortController) === null || _c === void 0 ? void 0 : _c.abort();
22278
+ (_d = this.abortController) === null || _d === void 0 ? void 0 : _d.abort();
22254
22279
  // in case the abort controller didn't manage to cancel the connection attempt, reject the connect promise explicitly
22255
- (_e = (_d = this.connectFuture) === null || _d === void 0 ? void 0 : _d.reject) === null || _e === void 0 ? void 0 : _e.call(_d, new ConnectionError('Client initiated disconnect'));
22280
+ (_f = (_e = this.connectFuture) === null || _e === void 0 ? void 0 : _e.reject) === null || _f === void 0 ? void 0 : _f.call(_e, new ConnectionError('Client initiated disconnect'));
22256
22281
  this.connectFuture = undefined;
22257
22282
  }
22258
22283
  // send leave
22259
- if ((_f = this.engine) === null || _f === void 0 ? void 0 : _f.client.isConnected) {
22284
+ if ((_g = this.engine) === null || _g === void 0 ? void 0 : _g.client.isConnected) {
22260
22285
  yield this.engine.client.sendLeave();
22261
22286
  }
22262
22287
  // close engine (also closes client)
@@ -22326,7 +22351,7 @@ class Room extends EventEmitter {
22326
22351
  livekitLogger.debug("fully reconnected to server", {
22327
22352
  region: joinResponse.serverRegion
22328
22353
  });
22329
- } catch (_g) {
22354
+ } catch (_h) {
22330
22355
  // reconnection failed, handleDisconnect is being invoked already, just return here
22331
22356
  return;
22332
22357
  }
@@ -22551,6 +22576,16 @@ class Room extends EventEmitter {
22551
22576
  this.options.publishDefaults = Object.assign(Object.assign({}, publishDefaults), options === null || options === void 0 ? void 0 : options.publishDefaults);
22552
22577
  this.maybeCreateEngine();
22553
22578
  this.disconnectLock = new Mutex();
22579
+ this.activeDeviceMap = new Map();
22580
+ if (this.options.videoCaptureDefaults.deviceId) {
22581
+ this.activeDeviceMap.set('videoinput', unwrapConstraint(this.options.videoCaptureDefaults.deviceId));
22582
+ }
22583
+ if (this.options.audioCaptureDefaults.deviceId) {
22584
+ this.activeDeviceMap.set('audioinput', unwrapConstraint(this.options.audioCaptureDefaults.deviceId));
22585
+ }
22586
+ if ((_a = this.options.audioOutput) === null || _a === void 0 ? void 0 : _a.deviceId) {
22587
+ this.switchActiveDevice('audiooutput', unwrapConstraint(this.options.audioOutput.deviceId));
22588
+ }
22554
22589
  this.localParticipant = new LocalParticipant('', '', this.engine, this.options);
22555
22590
  }
22556
22591
  /**
@@ -22816,15 +22851,16 @@ class Room extends EventEmitter {
22816
22851
  }
22817
22852
  /**
22818
22853
  * Returns the active audio output device used in this room.
22819
- *
22820
- * Note: to get the active `audioinput` or `videoinput` use [[LocalTrack.getDeviceId()]]
22821
- *
22822
22854
  * @return the previously successfully set audio output device ID or an empty string if the default device is used.
22855
+ * @deprecated use `getActiveDevice('audiooutput')` instead
22823
22856
  */
22824
22857
  getActiveAudioOutputDevice() {
22825
22858
  var _a, _b;
22826
22859
  return (_b = (_a = this.options.audioOutput) === null || _a === void 0 ? void 0 : _a.deviceId) !== null && _b !== void 0 ? _b : '';
22827
22860
  }
22861
+ getActiveDevice(kind) {
22862
+ return this.activeDeviceMap.get(kind);
22863
+ }
22828
22864
  /**
22829
22865
  * Switches all active devices used in this room to the given device.
22830
22866
  *
@@ -22837,21 +22873,24 @@ class Room extends EventEmitter {
22837
22873
  */
22838
22874
  switchActiveDevice(kind, deviceId) {
22839
22875
  let exact = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
22840
- var _a;
22841
- var _b;
22876
+ var _a, _b;
22877
+ var _c;
22842
22878
  return __awaiter(this, void 0, void 0, function* () {
22879
+ let deviceHasChanged = false;
22880
+ let success = true;
22843
22881
  const deviceConstraint = exact ? {
22844
22882
  exact: deviceId
22845
22883
  } : deviceId;
22846
22884
  if (kind === 'audioinput') {
22847
22885
  const prevDeviceId = this.options.audioCaptureDefaults.deviceId;
22848
22886
  this.options.audioCaptureDefaults.deviceId = deviceConstraint;
22887
+ deviceHasChanged = prevDeviceId !== deviceConstraint;
22849
22888
  const tracks = Array.from(this.localParticipant.audioTracks.values()).filter(track => track.source === Track.Source.Microphone);
22850
22889
  try {
22851
- yield Promise.all(tracks.map(t => {
22890
+ success = (yield Promise.all(tracks.map(t => {
22852
22891
  var _a;
22853
22892
  return (_a = t.audioTrack) === null || _a === void 0 ? void 0 : _a.setDeviceId(deviceConstraint);
22854
- }));
22893
+ }))).every(val => val === true);
22855
22894
  } catch (e) {
22856
22895
  this.options.audioCaptureDefaults.deviceId = prevDeviceId;
22857
22896
  throw e;
@@ -22859,33 +22898,44 @@ class Room extends EventEmitter {
22859
22898
  } else if (kind === 'videoinput') {
22860
22899
  const prevDeviceId = this.options.videoCaptureDefaults.deviceId;
22861
22900
  this.options.videoCaptureDefaults.deviceId = deviceConstraint;
22901
+ deviceHasChanged = prevDeviceId !== deviceConstraint;
22862
22902
  const tracks = Array.from(this.localParticipant.videoTracks.values()).filter(track => track.source === Track.Source.Camera);
22863
22903
  try {
22864
- yield Promise.all(tracks.map(t => {
22904
+ success = (yield Promise.all(tracks.map(t => {
22865
22905
  var _a;
22866
22906
  return (_a = t.videoTrack) === null || _a === void 0 ? void 0 : _a.setDeviceId(deviceConstraint);
22867
- }));
22907
+ }))).every(val => val === true);
22868
22908
  } catch (e) {
22869
22909
  this.options.videoCaptureDefaults.deviceId = prevDeviceId;
22870
22910
  throw e;
22871
22911
  }
22872
22912
  } else if (kind === 'audiooutput') {
22873
- // TODO add support for webaudio mix once the API becomes available https://github.com/WebAudio/web-audio-api/pull/2498
22874
- if (!supportsSetSinkId()) {
22913
+ if (!supportsSetSinkId() && !this.options.expWebAudioMix || this.audioContext && !('setSinkId' in this.audioContext)) {
22875
22914
  throw new Error('cannot switch audio output, setSinkId not supported');
22876
22915
  }
22877
- (_a = (_b = this.options).audioOutput) !== null && _a !== void 0 ? _a : _b.audioOutput = {};
22916
+ (_a = (_c = this.options).audioOutput) !== null && _a !== void 0 ? _a : _c.audioOutput = {};
22878
22917
  const prevDeviceId = this.options.audioOutput.deviceId;
22879
22918
  this.options.audioOutput.deviceId = deviceId;
22919
+ deviceHasChanged = prevDeviceId !== deviceConstraint;
22880
22920
  try {
22881
- yield Promise.all(Array.from(this.participants.values()).map(p => p.setAudioOutput({
22882
- deviceId
22883
- })));
22921
+ if (this.options.expWebAudioMix) {
22922
+ // @ts-expect-error setSinkId is not yet in the typescript type of AudioContext
22923
+ (_b = this.audioContext) === null || _b === void 0 ? void 0 : _b.setSinkId(deviceId);
22924
+ } else {
22925
+ yield Promise.all(Array.from(this.participants.values()).map(p => p.setAudioOutput({
22926
+ deviceId
22927
+ })));
22928
+ }
22884
22929
  } catch (e) {
22885
22930
  this.options.audioOutput.deviceId = prevDeviceId;
22886
22931
  throw e;
22887
22932
  }
22888
22933
  }
22934
+ if (deviceHasChanged && success) {
22935
+ this.activeDeviceMap.set(kind, deviceId);
22936
+ this.emit(RoomEvent.ActiveDeviceChanged, kind, deviceId);
22937
+ }
22938
+ return success;
22889
22939
  });
22890
22940
  }
22891
22941
  setupLocalParticipantEvents() {