livekit-client 2.13.1 → 2.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/livekit-client.esm.mjs +319 -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 +13 -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 +13 -0
  33. package/package.json +6 -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 +55 -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 +75 -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.3";
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,66 @@ function computeBitrate(currentStats, prevStats) {
15528
15533
  return (bytesNow - bytesPrev) * 8 * 1000 / (currentStats.timestamp - prevStats.timestamp);
15529
15534
  }
15530
15535
 
15531
- const defaultDimensionsTimeout = 1000;
15536
+ // Check if MediaRecorder is available
15537
+ const isMediaRecorderAvailable = typeof MediaRecorder !== 'undefined';
15538
+ // Fallback class for environments without MediaRecorder
15539
+ class FallbackRecorder {
15540
+ constructor() {
15541
+ throw new Error('MediaRecorder is not available in this environment');
15542
+ }
15543
+ }
15544
+ // Use conditional inheritance to avoid parse-time errors
15545
+ const RecorderBase = isMediaRecorderAvailable ? MediaRecorder : FallbackRecorder;
15546
+ class LocalTrackRecorder extends RecorderBase {
15547
+ constructor(track, options) {
15548
+ if (!isMediaRecorderAvailable) {
15549
+ throw new Error('MediaRecorder is not available in this environment');
15550
+ }
15551
+ super(new MediaStream([track.mediaStreamTrack]), options);
15552
+ let dataListener;
15553
+ let streamController;
15554
+ const isClosed = () => streamController === undefined;
15555
+ const onStop = () => {
15556
+ this.removeEventListener('dataavailable', dataListener);
15557
+ this.removeEventListener('stop', onStop);
15558
+ this.removeEventListener('error', onError);
15559
+ streamController === null || streamController === void 0 ? void 0 : streamController.close();
15560
+ streamController = undefined;
15561
+ };
15562
+ const onError = event => {
15563
+ streamController === null || streamController === void 0 ? void 0 : streamController.error(event);
15564
+ this.removeEventListener('dataavailable', dataListener);
15565
+ this.removeEventListener('stop', onStop);
15566
+ this.removeEventListener('error', onError);
15567
+ streamController = undefined;
15568
+ };
15569
+ this.byteStream = new ReadableStream({
15570
+ start: controller => {
15571
+ streamController = controller;
15572
+ dataListener = event => __awaiter(this, void 0, void 0, function* () {
15573
+ const arrayBuffer = yield event.data.arrayBuffer();
15574
+ if (isClosed()) {
15575
+ return;
15576
+ }
15577
+ controller.enqueue(new Uint8Array(arrayBuffer));
15578
+ });
15579
+ this.addEventListener('dataavailable', dataListener);
15580
+ },
15581
+ cancel: () => {
15582
+ onStop();
15583
+ }
15584
+ });
15585
+ this.addEventListener('stop', onStop);
15586
+ this.addEventListener('error', onError);
15587
+ }
15588
+ }
15589
+ // Helper function to check if recording is supported
15590
+ function isRecordingSupported() {
15591
+ return isMediaRecorderAvailable;
15592
+ }
15593
+
15594
+ const DEFAULT_DIMENSIONS_TIMEOUT = 1000;
15595
+ const PRE_CONNECT_BUFFER_TIMEOUT = 10000;
15532
15596
  class LocalTrack extends Track {
15533
15597
  /** @internal */
15534
15598
  get sender() {
@@ -15541,6 +15605,9 @@ class LocalTrack extends Track {
15541
15605
  get constraints() {
15542
15606
  return this._constraints;
15543
15607
  }
15608
+ get hasPreConnectBuffer() {
15609
+ return !!this.localTrackRecorder;
15610
+ }
15544
15611
  /**
15545
15612
  *
15546
15613
  * @param mediaTrack
@@ -15696,7 +15763,7 @@ class LocalTrack extends Track {
15696
15763
  waitForDimensions() {
15697
15764
  return __awaiter(this, arguments, void 0, function () {
15698
15765
  var _this = this;
15699
- let timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultDimensionsTimeout;
15766
+ let timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_DIMENSIONS_TIMEOUT;
15700
15767
  return function* () {
15701
15768
  var _a;
15702
15769
  if (_this.kind === Track.Kind.Audio) {
@@ -16051,6 +16118,40 @@ class LocalTrack extends Track {
16051
16118
  }();
16052
16119
  });
16053
16120
  }
16121
+ /** @internal */
16122
+ startPreConnectBuffer() {
16123
+ let timeslice = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 100;
16124
+ if (!isRecordingSupported()) {
16125
+ this.log.warn('MediaRecorder is not available, cannot start preconnect buffer', this.logContext);
16126
+ return;
16127
+ }
16128
+ if (!this.localTrackRecorder) {
16129
+ this.localTrackRecorder = new LocalTrackRecorder(this, {
16130
+ mimeType: 'audio/webm;codecs=opus'
16131
+ });
16132
+ } else {
16133
+ this.log.warn('preconnect buffer already started');
16134
+ return;
16135
+ }
16136
+ this.localTrackRecorder.start(timeslice);
16137
+ this.autoStopPreConnectBuffer = setTimeout(() => {
16138
+ this.log.warn('preconnect buffer timed out, stopping recording automatically', this.logContext);
16139
+ this.stopPreConnectBuffer();
16140
+ }, PRE_CONNECT_BUFFER_TIMEOUT);
16141
+ }
16142
+ /** @internal */
16143
+ stopPreConnectBuffer() {
16144
+ clearTimeout(this.autoStopPreConnectBuffer);
16145
+ if (this.localTrackRecorder) {
16146
+ this.localTrackRecorder.stop();
16147
+ this.localTrackRecorder = undefined;
16148
+ }
16149
+ }
16150
+ /** @internal */
16151
+ getPreConnectBuffer() {
16152
+ var _a;
16153
+ return (_a = this.localTrackRecorder) === null || _a === void 0 ? void 0 : _a.byteStream;
16154
+ }
16054
16155
  }
16055
16156
 
16056
16157
  class LocalAudioTrack extends LocalTrack {
@@ -17497,10 +17598,7 @@ class RTCEngine extends eventsExports.EventEmitter {
17497
17598
  this.negotiate();
17498
17599
  }
17499
17600
  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);
17601
+ this.emit(EngineEvent.SignalConnected, joinResponse);
17504
17602
  return joinResponse;
17505
17603
  } catch (e) {
17506
17604
  if (e instanceof ConnectionError) {
@@ -20053,12 +20151,29 @@ class LocalParticipant extends Participant {
20053
20151
  this.updateTrackSubscriptionPermissions();
20054
20152
  };
20055
20153
  this.handleDisconnected = () => {
20056
- var _a, _b;
20154
+ var _a, _b, _c, _d, _e, _f;
20057
20155
  if (this.reconnectFuture) {
20058
20156
  this.reconnectFuture.promise.catch(e => this.log.warn(e.message, this.logContext));
20059
20157
  (_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
20158
  this.reconnectFuture = undefined;
20061
20159
  }
20160
+ if (this.signalConnectedFuture) {
20161
+ (_d = (_c = this.signalConnectedFuture).reject) === null || _d === void 0 ? void 0 : _d.call(_c, 'Got disconnected without signal connected');
20162
+ this.signalConnectedFuture = undefined;
20163
+ }
20164
+ (_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');
20165
+ this.activeAgentFuture = undefined;
20166
+ this.firstActiveAgent = undefined;
20167
+ };
20168
+ this.handleSignalConnected = joinResponse => {
20169
+ var _a, _b;
20170
+ if (joinResponse.participant) {
20171
+ this.updateInfo(joinResponse.participant);
20172
+ }
20173
+ if (!this.signalConnectedFuture) {
20174
+ this.signalConnectedFuture = new Future();
20175
+ }
20176
+ (_b = (_a = this.signalConnectedFuture).resolve) === null || _b === void 0 ? void 0 : _b.call(_a);
20062
20177
  };
20063
20178
  this.handleSignalRequestResponse = response => {
20064
20179
  const {
@@ -20277,7 +20392,7 @@ class LocalParticipant extends Participant {
20277
20392
  pub.unmute();
20278
20393
  }
20279
20394
  });
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);
20395
+ 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
20396
  }
20282
20397
  /**
20283
20398
  * Sets and updates the metadata of the local participant.
@@ -20460,6 +20575,12 @@ class LocalParticipant extends Participant {
20460
20575
  this.pendingPublishing.delete(source);
20461
20576
  throw e;
20462
20577
  }
20578
+ for (const localTrack of localTracks) {
20579
+ if (source === Track.Source.Microphone && isAudioTrack(localTrack) && (publishOptions === null || publishOptions === void 0 ? void 0 : publishOptions.preConnectBuffer)) {
20580
+ this.log.info('starting preconnect buffer for microphone', Object.assign({}, this.logContext));
20581
+ localTrack.startPreConnectBuffer();
20582
+ }
20583
+ }
20463
20584
  try {
20464
20585
  const publishPromises = [];
20465
20586
  for (const localTrack of localTracks) {
@@ -20734,23 +20855,13 @@ class LocalParticipant extends Participant {
20734
20855
  this.log.debug('deferring track publication until signal is connected', Object.assign(Object.assign({}, this.logContext), {
20735
20856
  track: getLogContextFromTrack(track)
20736
20857
  }));
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);
20858
+ const timeout = setTimeout(() => {
20747
20859
  reject(new PublishTrackError('publishing rejected as engine not connected within timeout', 408));
20748
20860
  }, 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
- });
20861
+ yield this.waitUntilEngineConnected();
20862
+ clearTimeout(timeout);
20863
+ const publication = yield this.publish(track, opts, isStereo);
20864
+ resolve(publication);
20754
20865
  } else {
20755
20866
  try {
20756
20867
  const publication = yield this.publish(track, opts, isStereo);
@@ -20775,6 +20886,12 @@ class LocalParticipant extends Participant {
20775
20886
  }();
20776
20887
  });
20777
20888
  }
20889
+ waitUntilEngineConnected() {
20890
+ if (!this.signalConnectedFuture) {
20891
+ this.signalConnectedFuture = new Future();
20892
+ }
20893
+ return this.signalConnectedFuture.promise;
20894
+ }
20778
20895
  hasPermissionsToPublish(track) {
20779
20896
  if (!this.permissions) {
20780
20897
  this.log.warn('no permissions present for publishing track', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
@@ -20832,6 +20949,27 @@ class LocalParticipant extends Participant {
20832
20949
  track.on(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
20833
20950
  track.on(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
20834
20951
  track.on(TrackEvent.AudioTrackFeatureUpdate, this.onTrackFeatureUpdate);
20952
+ const audioFeatures = [];
20953
+ const disableDtx = !((_a = opts.dtx) !== null && _a !== void 0 ? _a : true);
20954
+ const settings = track.getSourceTrackSettings();
20955
+ if (settings.autoGainControl) {
20956
+ audioFeatures.push(AudioTrackFeature.TF_AUTO_GAIN_CONTROL);
20957
+ }
20958
+ if (settings.echoCancellation) {
20959
+ audioFeatures.push(AudioTrackFeature.TF_ECHO_CANCELLATION);
20960
+ }
20961
+ if (settings.noiseSuppression) {
20962
+ audioFeatures.push(AudioTrackFeature.TF_NOISE_SUPPRESSION);
20963
+ }
20964
+ if (settings.channelCount && settings.channelCount > 1) {
20965
+ audioFeatures.push(AudioTrackFeature.TF_STEREO);
20966
+ }
20967
+ if (disableDtx) {
20968
+ audioFeatures.push(AudioTrackFeature.TF_NO_DTX);
20969
+ }
20970
+ if (isLocalAudioTrack(track) && track.hasPreConnectBuffer) {
20971
+ audioFeatures.push(AudioTrackFeature.TF_PRECONNECT_BUFFER);
20972
+ }
20835
20973
  // create track publication from track
20836
20974
  const req = new AddTrackRequest({
20837
20975
  // get local track id for use during publishing
@@ -20840,12 +20978,13 @@ class LocalParticipant extends Participant {
20840
20978
  type: Track.kindToProto(track.kind),
20841
20979
  muted: track.isMuted,
20842
20980
  source: Track.sourceToProto(track.source),
20843
- disableDtx: !((_a = opts.dtx) !== null && _a !== void 0 ? _a : true),
20981
+ disableDtx,
20844
20982
  encryption: this.encryptionType,
20845
20983
  stereo: isStereo,
20846
20984
  disableRed: this.isE2EEEnabled || !((_b = opts.red) !== null && _b !== void 0 ? _b : true),
20847
20985
  stream: opts === null || opts === void 0 ? void 0 : opts.stream,
20848
- backupCodecPolicy: opts === null || opts === void 0 ? void 0 : opts.backupCodecPolicy
20986
+ backupCodecPolicy: opts === null || opts === void 0 ? void 0 : opts.backupCodecPolicy,
20987
+ audioFeatures
20849
20988
  });
20850
20989
  // compute encodings and layers for video
20851
20990
  let encodings;
@@ -21033,6 +21172,75 @@ class LocalParticipant extends Participant {
21033
21172
  this.addTrackPublication(publication);
21034
21173
  // send event for publication
21035
21174
  this.emit(ParticipantEvent.LocalTrackPublished, publication);
21175
+ if (isLocalAudioTrack(track) && ti.audioFeatures.includes(AudioTrackFeature.TF_PRECONNECT_BUFFER)) {
21176
+ const stream = track.getPreConnectBuffer();
21177
+ // TODO: we're registering the listener after negotiation, so there might be a race
21178
+ this.on(ParticipantEvent.LocalTrackSubscribed, pub => {
21179
+ if (pub.trackSid === ti.sid) {
21180
+ if (!track.hasPreConnectBuffer) {
21181
+ this.log.warn('subscribe event came to late, buffer already closed', this.logContext);
21182
+ return;
21183
+ }
21184
+ this.log.debug('finished recording preconnect buffer', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21185
+ track.stopPreConnectBuffer();
21186
+ }
21187
+ });
21188
+ if (stream) {
21189
+ const bufferStreamPromise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
21190
+ var _a, e_2, _b, _c;
21191
+ var _d, _e;
21192
+ try {
21193
+ this.log.debug('waiting for agent', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21194
+ const agentActiveTimeout = setTimeout(() => {
21195
+ reject(new Error('agent not active within 10 seconds'));
21196
+ }, 10000);
21197
+ const agent = yield this.waitUntilActiveAgentPresent();
21198
+ clearTimeout(agentActiveTimeout);
21199
+ this.log.debug('sending preconnect buffer', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21200
+ const writer = yield this.streamBytes({
21201
+ name: 'preconnect-buffer',
21202
+ mimeType: 'audio/opus',
21203
+ topic: 'lk.agent.pre-connect-audio-buffer',
21204
+ destinationIdentities: [agent.identity],
21205
+ attributes: {
21206
+ trackId: publication.trackSid,
21207
+ sampleRate: String((_d = settings.sampleRate) !== null && _d !== void 0 ? _d : '48000'),
21208
+ channels: String((_e = settings.channelCount) !== null && _e !== void 0 ? _e : '1')
21209
+ }
21210
+ });
21211
+ try {
21212
+ 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) {
21213
+ _c = stream_1_1.value;
21214
+ _f = false;
21215
+ const chunk = _c;
21216
+ yield writer.write(chunk);
21217
+ }
21218
+ } catch (e_2_1) {
21219
+ e_2 = {
21220
+ error: e_2_1
21221
+ };
21222
+ } finally {
21223
+ try {
21224
+ if (!_f && !_a && (_b = stream_1.return)) yield _b.call(stream_1);
21225
+ } finally {
21226
+ if (e_2) throw e_2.error;
21227
+ }
21228
+ }
21229
+ yield writer.close();
21230
+ resolve();
21231
+ } catch (e) {
21232
+ reject(e);
21233
+ }
21234
+ }));
21235
+ bufferStreamPromise.then(() => {
21236
+ this.log.debug('preconnect buffer sent successfully', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21237
+ }).catch(e => {
21238
+ this.log.error('error sending preconnect buffer', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
21239
+ error: e
21240
+ }));
21241
+ });
21242
+ }
21243
+ }
21036
21244
  return publication;
21037
21245
  });
21038
21246
  }
@@ -21781,6 +21989,29 @@ class LocalParticipant extends Participant {
21781
21989
  });
21782
21990
  return true;
21783
21991
  }
21992
+ /** @internal */
21993
+ setActiveAgent(agent) {
21994
+ var _a, _b, _c, _d;
21995
+ this.firstActiveAgent = agent;
21996
+ if (agent && !this.firstActiveAgent) {
21997
+ this.firstActiveAgent = agent;
21998
+ }
21999
+ if (agent) {
22000
+ (_b = (_a = this.activeAgentFuture) === null || _a === void 0 ? void 0 : _a.resolve) === null || _b === void 0 ? void 0 : _b.call(_a, agent);
22001
+ } else {
22002
+ (_d = (_c = this.activeAgentFuture) === null || _c === void 0 ? void 0 : _c.reject) === null || _d === void 0 ? void 0 : _d.call(_c, 'Agent disconnected');
22003
+ }
22004
+ this.activeAgentFuture = undefined;
22005
+ }
22006
+ waitUntilActiveAgentPresent() {
22007
+ if (this.firstActiveAgent) {
22008
+ return Promise.resolve(this.firstActiveAgent);
22009
+ }
22010
+ if (!this.activeAgentFuture) {
22011
+ this.activeAgentFuture = new Future();
22012
+ }
22013
+ return this.activeAgentFuture.promise;
22014
+ }
21784
22015
  getPublicationForTrack(track) {
21785
22016
  let publication;
21786
22017
  this.trackPublications.forEach(pub => {
@@ -22974,51 +23205,10 @@ class Room extends eventsExports.EventEmitter {
22974
23205
  }
22975
23206
  };
22976
23207
  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
- }
23208
+ var _a;
23209
+ if (((_a = getBrowser()) === null || _a === void 0 ? void 0 : _a.os) !== 'iOS') {
23210
+ // default devices are non deterministic on iOS, so we don't attempt to select them here
23211
+ yield this.selectDefaultDevices();
23022
23212
  }
23023
23213
  this.emit(RoomEvent.MediaDevicesChanged);
23024
23214
  });
@@ -23923,6 +24113,59 @@ class Room extends eventsExports.EventEmitter {
23923
24113
  }
23924
24114
  }
23925
24115
  }
24116
+ /**
24117
+ * attempt to select the default devices if the previously selected devices are no longer available after a device change event
24118
+ */
24119
+ selectDefaultDevices() {
24120
+ return __awaiter(this, void 0, void 0, function* () {
24121
+ var _a, _b, _c;
24122
+ const previousDevices = DeviceManager.getInstance().previousDevices;
24123
+ // check for available devices, but don't request permissions in order to avoid prompts for kinds that haven't been used before
24124
+ const availableDevices = yield DeviceManager.getInstance().getDevices(undefined, false);
24125
+ const browser = getBrowser();
24126
+ if ((browser === null || browser === void 0 ? void 0 : browser.name) === 'Chrome' && browser.os !== 'iOS') {
24127
+ for (let availableDevice of availableDevices) {
24128
+ const previousDevice = previousDevices.find(info => info.deviceId === availableDevice.deviceId);
24129
+ if (previousDevice && previousDevice.label !== '' && previousDevice.kind === availableDevice.kind && previousDevice.label !== availableDevice.label) {
24130
+ // label has changed on device the same deviceId, indicating that the default device has changed on the OS level
24131
+ if (this.getActiveDevice(availableDevice.kind) === 'default') {
24132
+ // emit an active device change event only if the selected output device is actually on `default`
24133
+ this.emit(RoomEvent.ActiveDeviceChanged, availableDevice.kind, availableDevice.deviceId);
24134
+ }
24135
+ }
24136
+ }
24137
+ }
24138
+ const kinds = ['audiooutput', 'audioinput', 'videoinput'];
24139
+ for (let kind of kinds) {
24140
+ const targetSource = kindToSource(kind);
24141
+ const targetPublication = this.localParticipant.getTrackPublication(targetSource);
24142
+ if (targetPublication && ((_a = targetPublication.track) === null || _a === void 0 ? void 0 : _a.isUserProvided)) {
24143
+ // if the track is user provided, we don't want to switch devices on behalf of the user
24144
+ continue;
24145
+ }
24146
+ const devicesOfKind = availableDevices.filter(d => d.kind === kind);
24147
+ const activeDevice = this.getActiveDevice(kind);
24148
+ if (activeDevice === ((_b = previousDevices.filter(info => info.kind === kind)[0]) === null || _b === void 0 ? void 0 : _b.deviceId)) {
24149
+ // 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
24150
+ // 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
24151
+ if (devicesOfKind.length > 0 && ((_c = devicesOfKind[0]) === null || _c === void 0 ? void 0 : _c.deviceId) !== activeDevice) {
24152
+ yield this.switchActiveDevice(kind, devicesOfKind[0].deviceId);
24153
+ continue;
24154
+ }
24155
+ }
24156
+ if (kind === 'audioinput' && !isSafariBased() || kind === 'videoinput') {
24157
+ // airpods on Safari need special handling for audioinput as the track doesn't end as soon as you take them out
24158
+ continue;
24159
+ }
24160
+ // switch to first available device if previously active device is not available any more
24161
+ if (devicesOfKind.length > 0 && !devicesOfKind.find(deviceInfo => deviceInfo.deviceId === this.getActiveDevice(kind)) && (
24162
+ // avoid switching audio output on safari without explicit user action as it leads to slowed down audio playback
24163
+ kind !== 'audiooutput' || !isSafariBased())) {
24164
+ yield this.switchActiveDevice(kind, devicesOfKind[0].deviceId);
24165
+ }
24166
+ }
24167
+ });
24168
+ }
23926
24169
  acquireAudioContext() {
23927
24170
  return __awaiter(this, void 0, void 0, function* () {
23928
24171
  var _a, _b;
@@ -24036,6 +24279,9 @@ class Room extends eventsExports.EventEmitter {
24036
24279
  this.emitWhenConnected(RoomEvent.TrackSubscriptionPermissionChanged, pub, status, participant);
24037
24280
  }).on(ParticipantEvent.Active, () => {
24038
24281
  this.emitWhenConnected(RoomEvent.ParticipantActive, participant);
24282
+ if (participant.kind === ParticipantInfo_Kind.AGENT) {
24283
+ this.localParticipant.setActiveAgent(participant);
24284
+ }
24039
24285
  });
24040
24286
  // update info at the end after callbacks have been set up
24041
24287
  if (info) {
@@ -25091,5 +25337,5 @@ function isFacingModeValue(item) {
25091
25337
  return item === undefined || allowedValues.includes(item);
25092
25338
  }
25093
25339
 
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 };
25340
+ 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
25341
  //# sourceMappingURL=livekit-client.esm.mjs.map