livekit-client 2.13.1 → 2.13.2

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 (44) hide show
  1. package/dist/livekit-client.esm.mjs +298 -73
  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/index.d.ts +1 -0
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/room/RTCEngine.d.ts +1 -1
  8. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  9. package/dist/src/room/Room.d.ts +5 -1
  10. package/dist/src/room/Room.d.ts.map +1 -1
  11. package/dist/src/room/defaults.d.ts.map +1 -1
  12. package/dist/src/room/events.d.ts +5 -1
  13. package/dist/src/room/events.d.ts.map +1 -1
  14. package/dist/src/room/participant/LocalParticipant.d.ts +9 -0
  15. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  16. package/dist/src/room/track/LocalTrack.d.ts +10 -0
  17. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  18. package/dist/src/room/track/Track.d.ts +1 -0
  19. package/dist/src/room/track/Track.d.ts.map +1 -1
  20. package/dist/src/room/track/options.d.ts +8 -0
  21. package/dist/src/room/track/options.d.ts.map +1 -1
  22. package/dist/src/room/track/record.d.ts +6 -0
  23. package/dist/src/room/track/record.d.ts.map +1 -0
  24. package/dist/ts4.2/src/index.d.ts +1 -0
  25. package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -1
  26. package/dist/ts4.2/src/room/Room.d.ts +4 -0
  27. package/dist/ts4.2/src/room/events.d.ts +5 -1
  28. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +9 -0
  29. package/dist/ts4.2/src/room/track/LocalTrack.d.ts +10 -0
  30. package/dist/ts4.2/src/room/track/Track.d.ts +1 -0
  31. package/dist/ts4.2/src/room/track/options.d.ts +8 -0
  32. package/dist/ts4.2/src/room/track/record.d.ts +6 -0
  33. package/package.json +3 -2
  34. package/src/e2ee/worker/tsconfig.json +9 -1
  35. package/src/index.ts +2 -0
  36. package/src/room/RTCEngine.ts +2 -5
  37. package/src/room/Room.ts +14 -2
  38. package/src/room/defaults.ts +1 -0
  39. package/src/room/events.ts +5 -0
  40. package/src/room/participant/LocalParticipant.ts +179 -16
  41. package/src/room/track/LocalTrack.ts +47 -2
  42. package/src/room/track/Track.ts +1 -0
  43. package/src/room/track/options.ts +9 -0
  44. package/src/room/track/record.ts +51 -0
@@ -11181,6 +11181,10 @@ var TrackEvent;
11181
11181
  * @experimental
11182
11182
  */
11183
11183
  TrackEvent["TimeSyncUpdate"] = "timeSyncUpdate";
11184
+ /**
11185
+ * @internal
11186
+ */
11187
+ TrackEvent["PreConnectBufferFlushed"] = "preConnectBufferFlushed";
11184
11188
  })(TrackEvent || (TrackEvent = {}));
11185
11189
 
11186
11190
  function cloneDeep(value) {
@@ -11262,7 +11266,7 @@ function getOSVersion(ua) {
11262
11266
  return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
11263
11267
  }
11264
11268
 
11265
- var version$1 = "2.13.1";
11269
+ var version$1 = "2.13.2";
11266
11270
 
11267
11271
  const version = version$1;
11268
11272
  const protocolVersion = 16;
@@ -15080,7 +15084,8 @@ const publishDefaults = {
15080
15084
  screenShareEncoding: ScreenSharePresets.h1080fps15.encoding,
15081
15085
  stopMicTrackOnMute: false,
15082
15086
  videoCodec: defaultVideoCodec,
15083
- backupCodec: true
15087
+ backupCodec: true,
15088
+ preConnectBuffer: false
15084
15089
  };
15085
15090
  const audioDefaults = {
15086
15091
  deviceId: {
@@ -15528,7 +15533,49 @@ function computeBitrate(currentStats, prevStats) {
15528
15533
  return (bytesNow - bytesPrev) * 8 * 1000 / (currentStats.timestamp - prevStats.timestamp);
15529
15534
  }
15530
15535
 
15531
- const defaultDimensionsTimeout = 1000;
15536
+ class LocalTrackRecorder extends MediaRecorder {
15537
+ constructor(track, options) {
15538
+ super(new MediaStream([track.mediaStreamTrack]), options);
15539
+ let dataListener;
15540
+ let streamController;
15541
+ const isClosed = () => streamController === undefined;
15542
+ const onStop = () => {
15543
+ this.removeEventListener('dataavailable', dataListener);
15544
+ this.removeEventListener('stop', onStop);
15545
+ this.removeEventListener('error', onError);
15546
+ streamController === null || streamController === void 0 ? void 0 : streamController.close();
15547
+ streamController = undefined;
15548
+ };
15549
+ const onError = event => {
15550
+ streamController === null || streamController === void 0 ? void 0 : streamController.error(event);
15551
+ this.removeEventListener('dataavailable', dataListener);
15552
+ this.removeEventListener('stop', onStop);
15553
+ this.removeEventListener('error', onError);
15554
+ streamController = undefined;
15555
+ };
15556
+ this.byteStream = new ReadableStream({
15557
+ start: controller => {
15558
+ streamController = controller;
15559
+ dataListener = event => __awaiter(this, void 0, void 0, function* () {
15560
+ const arrayBuffer = yield event.data.arrayBuffer();
15561
+ if (isClosed()) {
15562
+ return;
15563
+ }
15564
+ controller.enqueue(new Uint8Array(arrayBuffer));
15565
+ });
15566
+ this.addEventListener('dataavailable', dataListener);
15567
+ },
15568
+ cancel: () => {
15569
+ onStop();
15570
+ }
15571
+ });
15572
+ this.addEventListener('stop', onStop);
15573
+ this.addEventListener('error', onError);
15574
+ }
15575
+ }
15576
+
15577
+ const DEFAULT_DIMENSIONS_TIMEOUT = 1000;
15578
+ const PRE_CONNECT_BUFFER_TIMEOUT = 10000;
15532
15579
  class LocalTrack extends Track {
15533
15580
  /** @internal */
15534
15581
  get sender() {
@@ -15541,6 +15588,9 @@ class LocalTrack extends Track {
15541
15588
  get constraints() {
15542
15589
  return this._constraints;
15543
15590
  }
15591
+ get hasPreConnectBuffer() {
15592
+ return !!this.localTrackRecorder;
15593
+ }
15544
15594
  /**
15545
15595
  *
15546
15596
  * @param mediaTrack
@@ -15696,7 +15746,7 @@ class LocalTrack extends Track {
15696
15746
  waitForDimensions() {
15697
15747
  return __awaiter(this, arguments, void 0, function () {
15698
15748
  var _this = this;
15699
- let timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultDimensionsTimeout;
15749
+ let timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_DIMENSIONS_TIMEOUT;
15700
15750
  return function* () {
15701
15751
  var _a;
15702
15752
  if (_this.kind === Track.Kind.Audio) {
@@ -16051,6 +16101,36 @@ class LocalTrack extends Track {
16051
16101
  }();
16052
16102
  });
16053
16103
  }
16104
+ /** @internal */
16105
+ startPreConnectBuffer() {
16106
+ let timeslice = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 100;
16107
+ if (!this.localTrackRecorder) {
16108
+ this.localTrackRecorder = new LocalTrackRecorder(this, {
16109
+ mimeType: 'audio/webm;codecs=opus'
16110
+ });
16111
+ } else {
16112
+ this.log.warn('preconnect buffer already started');
16113
+ return;
16114
+ }
16115
+ this.localTrackRecorder.start(timeslice);
16116
+ this.autoStopPreConnectBuffer = setTimeout(() => {
16117
+ this.log.warn('preconnect buffer timed out, stopping recording automatically', this.logContext);
16118
+ this.stopPreConnectBuffer();
16119
+ }, PRE_CONNECT_BUFFER_TIMEOUT);
16120
+ }
16121
+ /** @internal */
16122
+ stopPreConnectBuffer() {
16123
+ clearTimeout(this.autoStopPreConnectBuffer);
16124
+ if (this.localTrackRecorder) {
16125
+ this.localTrackRecorder.stop();
16126
+ this.localTrackRecorder = undefined;
16127
+ }
16128
+ }
16129
+ /** @internal */
16130
+ getPreConnectBuffer() {
16131
+ var _a;
16132
+ return (_a = this.localTrackRecorder) === null || _a === void 0 ? void 0 : _a.byteStream;
16133
+ }
16054
16134
  }
16055
16135
 
16056
16136
  class LocalAudioTrack extends LocalTrack {
@@ -17497,10 +17577,7 @@ class RTCEngine extends eventsExports.EventEmitter {
17497
17577
  this.negotiate();
17498
17578
  }
17499
17579
  this.clientConfiguration = joinResponse.clientConfiguration;
17500
- // emit signal connected event after a short delay to allow for join response to be processed on room
17501
- setTimeout(() => {
17502
- this.emit(EngineEvent.SignalConnected);
17503
- }, 10);
17580
+ this.emit(EngineEvent.SignalConnected, joinResponse);
17504
17581
  return joinResponse;
17505
17582
  } catch (e) {
17506
17583
  if (e instanceof ConnectionError) {
@@ -20053,12 +20130,29 @@ class LocalParticipant extends Participant {
20053
20130
  this.updateTrackSubscriptionPermissions();
20054
20131
  };
20055
20132
  this.handleDisconnected = () => {
20056
- var _a, _b;
20133
+ var _a, _b, _c, _d, _e, _f;
20057
20134
  if (this.reconnectFuture) {
20058
20135
  this.reconnectFuture.promise.catch(e => this.log.warn(e.message, this.logContext));
20059
20136
  (_b = (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.reject) === null || _b === void 0 ? void 0 : _b.call(_a, 'Got disconnected during reconnection attempt');
20060
20137
  this.reconnectFuture = undefined;
20061
20138
  }
20139
+ if (this.signalConnectedFuture) {
20140
+ (_d = (_c = this.signalConnectedFuture).reject) === null || _d === void 0 ? void 0 : _d.call(_c, 'Got disconnected without signal connected');
20141
+ this.signalConnectedFuture = undefined;
20142
+ }
20143
+ (_f = (_e = this.activeAgentFuture) === null || _e === void 0 ? void 0 : _e.reject) === null || _f === void 0 ? void 0 : _f.call(_e, 'Got disconnected without active agent present');
20144
+ this.activeAgentFuture = undefined;
20145
+ this.firstActiveAgent = undefined;
20146
+ };
20147
+ this.handleSignalConnected = joinResponse => {
20148
+ var _a, _b;
20149
+ if (joinResponse.participant) {
20150
+ this.updateInfo(joinResponse.participant);
20151
+ }
20152
+ if (!this.signalConnectedFuture) {
20153
+ this.signalConnectedFuture = new Future();
20154
+ }
20155
+ (_b = (_a = this.signalConnectedFuture).resolve) === null || _b === void 0 ? void 0 : _b.call(_a);
20062
20156
  };
20063
20157
  this.handleSignalRequestResponse = response => {
20064
20158
  const {
@@ -20277,7 +20371,7 @@ class LocalParticipant extends Participant {
20277
20371
  pub.unmute();
20278
20372
  }
20279
20373
  });
20280
- this.engine.on(EngineEvent.Connected, this.handleReconnected).on(EngineEvent.SignalRestarted, this.handleReconnected).on(EngineEvent.SignalResumed, this.handleReconnected).on(EngineEvent.Restarting, this.handleReconnecting).on(EngineEvent.Resuming, this.handleReconnecting).on(EngineEvent.LocalTrackUnpublished, this.handleLocalTrackUnpublished).on(EngineEvent.SubscribedQualityUpdate, this.handleSubscribedQualityUpdate).on(EngineEvent.Disconnected, this.handleDisconnected).on(EngineEvent.SignalRequestResponse, this.handleSignalRequestResponse).on(EngineEvent.DataPacketReceived, this.handleDataPacket);
20374
+ this.engine.on(EngineEvent.Connected, this.handleReconnected).on(EngineEvent.SignalConnected, this.handleSignalConnected).on(EngineEvent.SignalRestarted, this.handleReconnected).on(EngineEvent.SignalResumed, this.handleReconnected).on(EngineEvent.Restarting, this.handleReconnecting).on(EngineEvent.Resuming, this.handleReconnecting).on(EngineEvent.LocalTrackUnpublished, this.handleLocalTrackUnpublished).on(EngineEvent.SubscribedQualityUpdate, this.handleSubscribedQualityUpdate).on(EngineEvent.Disconnected, this.handleDisconnected).on(EngineEvent.SignalRequestResponse, this.handleSignalRequestResponse).on(EngineEvent.DataPacketReceived, this.handleDataPacket);
20281
20375
  }
20282
20376
  /**
20283
20377
  * Sets and updates the metadata of the local participant.
@@ -20460,6 +20554,12 @@ class LocalParticipant extends Participant {
20460
20554
  this.pendingPublishing.delete(source);
20461
20555
  throw e;
20462
20556
  }
20557
+ for (const localTrack of localTracks) {
20558
+ if (source === Track.Source.Microphone && isAudioTrack(localTrack) && (publishOptions === null || publishOptions === void 0 ? void 0 : publishOptions.preConnectBuffer)) {
20559
+ this.log.info('starting preconnect buffer for microphone', Object.assign({}, this.logContext));
20560
+ localTrack.startPreConnectBuffer();
20561
+ }
20562
+ }
20463
20563
  try {
20464
20564
  const publishPromises = [];
20465
20565
  for (const localTrack of localTracks) {
@@ -20734,23 +20834,13 @@ class LocalParticipant extends Participant {
20734
20834
  this.log.debug('deferring track publication until signal is connected', Object.assign(Object.assign({}, this.logContext), {
20735
20835
  track: getLogContextFromTrack(track)
20736
20836
  }));
20737
- const onSignalConnected = () => __awaiter(this, void 0, void 0, function* () {
20738
- try {
20739
- const publication = yield this.publish(track, opts, isStereo);
20740
- resolve(publication);
20741
- } catch (e) {
20742
- reject(e);
20743
- }
20744
- });
20745
- setTimeout(() => {
20746
- this.engine.off(EngineEvent.SignalConnected, onSignalConnected);
20837
+ const timeout = setTimeout(() => {
20747
20838
  reject(new PublishTrackError('publishing rejected as engine not connected within timeout', 408));
20748
20839
  }, 15000);
20749
- this.engine.once(EngineEvent.SignalConnected, onSignalConnected);
20750
- this.engine.on(EngineEvent.Closing, () => {
20751
- this.engine.off(EngineEvent.SignalConnected, onSignalConnected);
20752
- reject(new PublishTrackError('publishing rejected as engine closed', 499));
20753
- });
20840
+ yield this.waitUntilEngineConnected();
20841
+ clearTimeout(timeout);
20842
+ const publication = yield this.publish(track, opts, isStereo);
20843
+ resolve(publication);
20754
20844
  } else {
20755
20845
  try {
20756
20846
  const publication = yield this.publish(track, opts, isStereo);
@@ -20775,6 +20865,12 @@ class LocalParticipant extends Participant {
20775
20865
  }();
20776
20866
  });
20777
20867
  }
20868
+ waitUntilEngineConnected() {
20869
+ if (!this.signalConnectedFuture) {
20870
+ this.signalConnectedFuture = new Future();
20871
+ }
20872
+ return this.signalConnectedFuture.promise;
20873
+ }
20778
20874
  hasPermissionsToPublish(track) {
20779
20875
  if (!this.permissions) {
20780
20876
  this.log.warn('no permissions present for publishing track', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
@@ -20832,6 +20928,27 @@ class LocalParticipant extends Participant {
20832
20928
  track.on(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
20833
20929
  track.on(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
20834
20930
  track.on(TrackEvent.AudioTrackFeatureUpdate, this.onTrackFeatureUpdate);
20931
+ const audioFeatures = [];
20932
+ const disableDtx = !((_a = opts.dtx) !== null && _a !== void 0 ? _a : true);
20933
+ const settings = track.getSourceTrackSettings();
20934
+ if (settings.autoGainControl) {
20935
+ audioFeatures.push(AudioTrackFeature.TF_AUTO_GAIN_CONTROL);
20936
+ }
20937
+ if (settings.echoCancellation) {
20938
+ audioFeatures.push(AudioTrackFeature.TF_ECHO_CANCELLATION);
20939
+ }
20940
+ if (settings.noiseSuppression) {
20941
+ audioFeatures.push(AudioTrackFeature.TF_NOISE_SUPPRESSION);
20942
+ }
20943
+ if (settings.channelCount && settings.channelCount > 1) {
20944
+ audioFeatures.push(AudioTrackFeature.TF_STEREO);
20945
+ }
20946
+ if (disableDtx) {
20947
+ audioFeatures.push(AudioTrackFeature.TF_NO_DTX);
20948
+ }
20949
+ if (isLocalAudioTrack(track) && track.hasPreConnectBuffer) {
20950
+ audioFeatures.push(AudioTrackFeature.TF_PRECONNECT_BUFFER);
20951
+ }
20835
20952
  // create track publication from track
20836
20953
  const req = new AddTrackRequest({
20837
20954
  // get local track id for use during publishing
@@ -20840,12 +20957,13 @@ class LocalParticipant extends Participant {
20840
20957
  type: Track.kindToProto(track.kind),
20841
20958
  muted: track.isMuted,
20842
20959
  source: Track.sourceToProto(track.source),
20843
- disableDtx: !((_a = opts.dtx) !== null && _a !== void 0 ? _a : true),
20960
+ disableDtx,
20844
20961
  encryption: this.encryptionType,
20845
20962
  stereo: isStereo,
20846
20963
  disableRed: this.isE2EEEnabled || !((_b = opts.red) !== null && _b !== void 0 ? _b : true),
20847
20964
  stream: opts === null || opts === void 0 ? void 0 : opts.stream,
20848
- backupCodecPolicy: opts === null || opts === void 0 ? void 0 : opts.backupCodecPolicy
20965
+ backupCodecPolicy: opts === null || opts === void 0 ? void 0 : opts.backupCodecPolicy,
20966
+ audioFeatures
20849
20967
  });
20850
20968
  // compute encodings and layers for video
20851
20969
  let encodings;
@@ -21033,6 +21151,75 @@ class LocalParticipant extends Participant {
21033
21151
  this.addTrackPublication(publication);
21034
21152
  // send event for publication
21035
21153
  this.emit(ParticipantEvent.LocalTrackPublished, publication);
21154
+ if (isLocalAudioTrack(track) && ti.audioFeatures.includes(AudioTrackFeature.TF_PRECONNECT_BUFFER)) {
21155
+ const stream = track.getPreConnectBuffer();
21156
+ // TODO: we're registering the listener after negotiation, so there might be a race
21157
+ this.on(ParticipantEvent.LocalTrackSubscribed, pub => {
21158
+ if (pub.trackSid === ti.sid) {
21159
+ if (!track.hasPreConnectBuffer) {
21160
+ this.log.warn('subscribe event came to late, buffer already closed', this.logContext);
21161
+ return;
21162
+ }
21163
+ this.log.debug('finished recording preconnect buffer', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21164
+ track.stopPreConnectBuffer();
21165
+ }
21166
+ });
21167
+ if (stream) {
21168
+ const bufferStreamPromise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
21169
+ var _a, e_2, _b, _c;
21170
+ var _d, _e;
21171
+ try {
21172
+ this.log.debug('waiting for agent', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21173
+ const agentActiveTimeout = setTimeout(() => {
21174
+ reject(new Error('agent not active within 10 seconds'));
21175
+ }, 10000);
21176
+ const agent = yield this.waitUntilActiveAgentPresent();
21177
+ clearTimeout(agentActiveTimeout);
21178
+ this.log.debug('sending preconnect buffer', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21179
+ const writer = yield this.streamBytes({
21180
+ name: 'preconnect-buffer',
21181
+ mimeType: 'audio/opus',
21182
+ topic: 'lk.agent.pre-connect-audio-buffer',
21183
+ destinationIdentities: [agent.identity],
21184
+ attributes: {
21185
+ trackId: publication.trackSid,
21186
+ sampleRate: String((_d = settings.sampleRate) !== null && _d !== void 0 ? _d : '48000'),
21187
+ channels: String((_e = settings.channelCount) !== null && _e !== void 0 ? _e : '1')
21188
+ }
21189
+ });
21190
+ try {
21191
+ for (var _f = true, stream_1 = __asyncValues(stream), stream_1_1; stream_1_1 = yield stream_1.next(), _a = stream_1_1.done, !_a; _f = true) {
21192
+ _c = stream_1_1.value;
21193
+ _f = false;
21194
+ const chunk = _c;
21195
+ yield writer.write(chunk);
21196
+ }
21197
+ } catch (e_2_1) {
21198
+ e_2 = {
21199
+ error: e_2_1
21200
+ };
21201
+ } finally {
21202
+ try {
21203
+ if (!_f && !_a && (_b = stream_1.return)) yield _b.call(stream_1);
21204
+ } finally {
21205
+ if (e_2) throw e_2.error;
21206
+ }
21207
+ }
21208
+ yield writer.close();
21209
+ resolve();
21210
+ } catch (e) {
21211
+ reject(e);
21212
+ }
21213
+ }));
21214
+ bufferStreamPromise.then(() => {
21215
+ this.log.debug('preconnect buffer sent successfully', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21216
+ }).catch(e => {
21217
+ this.log.error('error sending preconnect buffer', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
21218
+ error: e
21219
+ }));
21220
+ });
21221
+ }
21222
+ }
21036
21223
  return publication;
21037
21224
  });
21038
21225
  }
@@ -21781,6 +21968,29 @@ class LocalParticipant extends Participant {
21781
21968
  });
21782
21969
  return true;
21783
21970
  }
21971
+ /** @internal */
21972
+ setActiveAgent(agent) {
21973
+ var _a, _b, _c, _d;
21974
+ this.firstActiveAgent = agent;
21975
+ if (agent && !this.firstActiveAgent) {
21976
+ this.firstActiveAgent = agent;
21977
+ }
21978
+ if (agent) {
21979
+ (_b = (_a = this.activeAgentFuture) === null || _a === void 0 ? void 0 : _a.resolve) === null || _b === void 0 ? void 0 : _b.call(_a, agent);
21980
+ } else {
21981
+ (_d = (_c = this.activeAgentFuture) === null || _c === void 0 ? void 0 : _c.reject) === null || _d === void 0 ? void 0 : _d.call(_c, 'Agent disconnected');
21982
+ }
21983
+ this.activeAgentFuture = undefined;
21984
+ }
21985
+ waitUntilActiveAgentPresent() {
21986
+ if (this.firstActiveAgent) {
21987
+ return Promise.resolve(this.firstActiveAgent);
21988
+ }
21989
+ if (!this.activeAgentFuture) {
21990
+ this.activeAgentFuture = new Future();
21991
+ }
21992
+ return this.activeAgentFuture.promise;
21993
+ }
21784
21994
  getPublicationForTrack(track) {
21785
21995
  let publication;
21786
21996
  this.trackPublications.forEach(pub => {
@@ -22974,51 +23184,10 @@ class Room extends eventsExports.EventEmitter {
22974
23184
  }
22975
23185
  };
22976
23186
  this.handleDeviceChange = () => __awaiter(this, void 0, void 0, function* () {
22977
- var _a, _b, _c;
22978
- const previousDevices = DeviceManager.getInstance().previousDevices;
22979
- // check for available devices, but don't request permissions in order to avoid prompts for kinds that haven't been used before
22980
- const availableDevices = yield DeviceManager.getInstance().getDevices(undefined, false);
22981
- const browser = getBrowser();
22982
- if ((browser === null || browser === void 0 ? void 0 : browser.name) === 'Chrome' && browser.os !== 'iOS') {
22983
- for (let availableDevice of availableDevices) {
22984
- const previousDevice = previousDevices.find(info => info.deviceId === availableDevice.deviceId);
22985
- if (previousDevice && previousDevice.label !== '' && previousDevice.kind === availableDevice.kind && previousDevice.label !== availableDevice.label) {
22986
- // label has changed on device the same deviceId, indicating that the default device has changed on the OS level
22987
- if (this.getActiveDevice(availableDevice.kind) === 'default') {
22988
- // emit an active device change event only if the selected output device is actually on `default`
22989
- this.emit(RoomEvent.ActiveDeviceChanged, availableDevice.kind, availableDevice.deviceId);
22990
- }
22991
- }
22992
- }
22993
- }
22994
- const kinds = ['audiooutput', 'audioinput', 'videoinput'];
22995
- for (let kind of kinds) {
22996
- const targetSource = kindToSource(kind);
22997
- const targetPublication = this.localParticipant.getTrackPublication(targetSource);
22998
- if (targetPublication && ((_a = targetPublication.track) === null || _a === void 0 ? void 0 : _a.isUserProvided)) {
22999
- // if the track is user provided, we don't want to switch devices on behalf of the user
23000
- continue;
23001
- }
23002
- const devicesOfKind = availableDevices.filter(d => d.kind === kind);
23003
- const activeDevice = this.getActiveDevice(kind);
23004
- if (activeDevice === ((_b = previousDevices.filter(info => info.kind === kind)[0]) === null || _b === void 0 ? void 0 : _b.deviceId)) {
23005
- // in Safari the first device is always the default, so we assume a user on the default device would like to switch to the default once it changes
23006
- // FF doesn't emit an event when the default device changes, so we perform the same best effort and switch to the new device once connected and if it's the first in the array
23007
- if (devicesOfKind.length > 0 && ((_c = devicesOfKind[0]) === null || _c === void 0 ? void 0 : _c.deviceId) !== activeDevice) {
23008
- yield this.switchActiveDevice(kind, devicesOfKind[0].deviceId);
23009
- continue;
23010
- }
23011
- }
23012
- if (kind === 'audioinput' && !isSafariBased() || kind === 'videoinput') {
23013
- // airpods on Safari need special handling for audioinput as the track doesn't end as soon as you take them out
23014
- continue;
23015
- }
23016
- // switch to first available device if previously active device is not available any more
23017
- if (devicesOfKind.length > 0 && !devicesOfKind.find(deviceInfo => deviceInfo.deviceId === this.getActiveDevice(kind)) && (
23018
- // avoid switching audio output on safari without explicit user action as it leads to slowed down audio playback
23019
- kind !== 'audiooutput' || !isSafariBased())) {
23020
- yield this.switchActiveDevice(kind, devicesOfKind[0].deviceId);
23021
- }
23187
+ var _a;
23188
+ if (((_a = getBrowser()) === null || _a === void 0 ? void 0 : _a.os) !== 'iOS') {
23189
+ // default devices are non deterministic on iOS, so we don't attempt to select them here
23190
+ yield this.selectDefaultDevices();
23022
23191
  }
23023
23192
  this.emit(RoomEvent.MediaDevicesChanged);
23024
23193
  });
@@ -23923,6 +24092,59 @@ class Room extends eventsExports.EventEmitter {
23923
24092
  }
23924
24093
  }
23925
24094
  }
24095
+ /**
24096
+ * attempt to select the default devices if the previously selected devices are no longer available after a device change event
24097
+ */
24098
+ selectDefaultDevices() {
24099
+ return __awaiter(this, void 0, void 0, function* () {
24100
+ var _a, _b, _c;
24101
+ const previousDevices = DeviceManager.getInstance().previousDevices;
24102
+ // check for available devices, but don't request permissions in order to avoid prompts for kinds that haven't been used before
24103
+ const availableDevices = yield DeviceManager.getInstance().getDevices(undefined, false);
24104
+ const browser = getBrowser();
24105
+ if ((browser === null || browser === void 0 ? void 0 : browser.name) === 'Chrome' && browser.os !== 'iOS') {
24106
+ for (let availableDevice of availableDevices) {
24107
+ const previousDevice = previousDevices.find(info => info.deviceId === availableDevice.deviceId);
24108
+ if (previousDevice && previousDevice.label !== '' && previousDevice.kind === availableDevice.kind && previousDevice.label !== availableDevice.label) {
24109
+ // label has changed on device the same deviceId, indicating that the default device has changed on the OS level
24110
+ if (this.getActiveDevice(availableDevice.kind) === 'default') {
24111
+ // emit an active device change event only if the selected output device is actually on `default`
24112
+ this.emit(RoomEvent.ActiveDeviceChanged, availableDevice.kind, availableDevice.deviceId);
24113
+ }
24114
+ }
24115
+ }
24116
+ }
24117
+ const kinds = ['audiooutput', 'audioinput', 'videoinput'];
24118
+ for (let kind of kinds) {
24119
+ const targetSource = kindToSource(kind);
24120
+ const targetPublication = this.localParticipant.getTrackPublication(targetSource);
24121
+ if (targetPublication && ((_a = targetPublication.track) === null || _a === void 0 ? void 0 : _a.isUserProvided)) {
24122
+ // if the track is user provided, we don't want to switch devices on behalf of the user
24123
+ continue;
24124
+ }
24125
+ const devicesOfKind = availableDevices.filter(d => d.kind === kind);
24126
+ const activeDevice = this.getActiveDevice(kind);
24127
+ if (activeDevice === ((_b = previousDevices.filter(info => info.kind === kind)[0]) === null || _b === void 0 ? void 0 : _b.deviceId)) {
24128
+ // in Safari the first device is always the default, so we assume a user on the default device would like to switch to the default once it changes
24129
+ // FF doesn't emit an event when the default device changes, so we perform the same best effort and switch to the new device once connected and if it's the first in the array
24130
+ if (devicesOfKind.length > 0 && ((_c = devicesOfKind[0]) === null || _c === void 0 ? void 0 : _c.deviceId) !== activeDevice) {
24131
+ yield this.switchActiveDevice(kind, devicesOfKind[0].deviceId);
24132
+ continue;
24133
+ }
24134
+ }
24135
+ if (kind === 'audioinput' && !isSafariBased() || kind === 'videoinput') {
24136
+ // airpods on Safari need special handling for audioinput as the track doesn't end as soon as you take them out
24137
+ continue;
24138
+ }
24139
+ // switch to first available device if previously active device is not available any more
24140
+ if (devicesOfKind.length > 0 && !devicesOfKind.find(deviceInfo => deviceInfo.deviceId === this.getActiveDevice(kind)) && (
24141
+ // avoid switching audio output on safari without explicit user action as it leads to slowed down audio playback
24142
+ kind !== 'audiooutput' || !isSafariBased())) {
24143
+ yield this.switchActiveDevice(kind, devicesOfKind[0].deviceId);
24144
+ }
24145
+ }
24146
+ });
24147
+ }
23926
24148
  acquireAudioContext() {
23927
24149
  return __awaiter(this, void 0, void 0, function* () {
23928
24150
  var _a, _b;
@@ -24036,6 +24258,9 @@ class Room extends eventsExports.EventEmitter {
24036
24258
  this.emitWhenConnected(RoomEvent.TrackSubscriptionPermissionChanged, pub, status, participant);
24037
24259
  }).on(ParticipantEvent.Active, () => {
24038
24260
  this.emitWhenConnected(RoomEvent.ParticipantActive, participant);
24261
+ if (participant.kind === ParticipantInfo_Kind.AGENT) {
24262
+ this.localParticipant.setActiveAgent(participant);
24263
+ }
24039
24264
  });
24040
24265
  // update info at the end after callbacks have been set up
24041
24266
  if (info) {
@@ -25091,5 +25316,5 @@ function isFacingModeValue(item) {
25091
25316
  return item === undefined || allowedValues.includes(item);
25092
25317
  }
25093
25318
 
25094
- export { AudioPresets, BackupCodecPolicy, BaseKeyProvider, CheckStatus, Checker, ConnectionCheck, ConnectionError, ConnectionErrorReason, ConnectionQuality, ConnectionState, CriticalTimers, CryptorError, CryptorErrorReason, CryptorEvent, DataPacket_Kind, DefaultReconnectPolicy, DeviceUnsupportedError, DisconnectReason, EncryptionEvent, EngineEvent, ExternalE2EEKeyProvider, KeyHandlerEvent, KeyProviderEvent, LivekitError, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, LoggerNames, MediaDeviceFailure, _ as Mutex, NegotiationError, Participant, ParticipantEvent, ParticipantInfo_Kind as ParticipantKind, PublishDataError, PublishTrackError, RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, RoomEvent, RpcError, ScreenSharePresets, SignalRequestError, SubscriptionError, Track, TrackEvent, TrackInvalidError, TrackPublication, TrackType, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, attachToElement, compareVersions, createAudioAnalyser, createE2EEKey, createKeyMaterialFromBuffer, createKeyMaterialFromString, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, deriveKeys, detachTrack, facingModeFromDeviceLabel, facingModeFromLocalTrack, getBrowser, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, getLogger, importKey, isAudioTrack, isBackupCodec, isBrowserSupported, isE2EESupported, isInsertableStreamSupported, isLocalParticipant, isLocalTrack, isRemoteParticipant, isRemoteTrack, isScriptTransformSupported, isVideoFrame, isVideoTrack, needsRbspUnescaping, parseRbsp, protocolVersion, ratchet, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, version, videoCodecs, writeRbsp };
25319
+ export { AudioPresets, BackupCodecPolicy, BaseKeyProvider, CheckStatus, Checker, ConnectionCheck, ConnectionError, ConnectionErrorReason, ConnectionQuality, ConnectionState, CriticalTimers, CryptorError, CryptorErrorReason, CryptorEvent, DataPacket_Kind, DefaultReconnectPolicy, DeviceUnsupportedError, DisconnectReason, EncryptionEvent, EngineEvent, ExternalE2EEKeyProvider, KeyHandlerEvent, KeyProviderEvent, LivekitError, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalTrackRecorder, LocalVideoTrack, LogLevel, LoggerNames, MediaDeviceFailure, _ as Mutex, NegotiationError, Participant, ParticipantEvent, ParticipantInfo_Kind as ParticipantKind, PublishDataError, PublishTrackError, RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, RoomEvent, RpcError, ScreenSharePresets, SignalRequestError, SubscriptionError, Track, TrackEvent, TrackInvalidError, TrackPublication, TrackType, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, attachToElement, compareVersions, createAudioAnalyser, createE2EEKey, createKeyMaterialFromBuffer, createKeyMaterialFromString, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, deriveKeys, detachTrack, facingModeFromDeviceLabel, facingModeFromLocalTrack, getBrowser, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, getLogger, importKey, isAudioTrack, isBackupCodec, isBrowserSupported, isE2EESupported, isInsertableStreamSupported, isLocalParticipant, isLocalTrack, isRemoteParticipant, isRemoteTrack, isScriptTransformSupported, isVideoFrame, isVideoTrack, needsRbspUnescaping, parseRbsp, protocolVersion, ratchet, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, version, videoCodecs, writeRbsp };
25095
25320
  //# sourceMappingURL=livekit-client.esm.mjs.map