livekit-client 1.15.11 → 2.0.0

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 (76) hide show
  1. package/README.md +21 -17
  2. package/dist/livekit-client.esm.mjs +1573 -1479
  3. package/dist/livekit-client.esm.mjs.map +1 -1
  4. package/dist/livekit-client.umd.js +1 -1
  5. package/dist/livekit-client.umd.js.map +1 -1
  6. package/dist/src/api/SignalClient.d.ts +0 -2
  7. package/dist/src/api/SignalClient.d.ts.map +1 -1
  8. package/dist/src/index.d.ts +3 -3
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/options.d.ts +3 -9
  11. package/dist/src/options.d.ts.map +1 -1
  12. package/dist/src/proto/livekit_models_pb.d.ts +47 -0
  13. package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
  14. package/dist/src/room/RTCEngine.d.ts +1 -0
  15. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  16. package/dist/src/room/Room.d.ts +12 -15
  17. package/dist/src/room/Room.d.ts.map +1 -1
  18. package/dist/src/room/defaults.d.ts.map +1 -1
  19. package/dist/src/room/events.d.ts +0 -4
  20. package/dist/src/room/events.d.ts.map +1 -1
  21. package/dist/src/room/participant/LocalParticipant.d.ts +8 -25
  22. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  23. package/dist/src/room/participant/Participant.d.ts +6 -10
  24. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  25. package/dist/src/room/participant/RemoteParticipant.d.ts +9 -6
  26. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  27. package/dist/src/room/timers.d.ts +4 -5
  28. package/dist/src/room/timers.d.ts.map +1 -1
  29. package/dist/src/room/track/LocalVideoTrack.d.ts +3 -3
  30. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  31. package/dist/src/room/track/RemoteTrackPublication.d.ts +2 -2
  32. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
  33. package/dist/src/room/track/Track.d.ts +5 -0
  34. package/dist/src/room/track/Track.d.ts.map +1 -1
  35. package/dist/src/room/track/options.d.ts +0 -5
  36. package/dist/src/room/track/options.d.ts.map +1 -1
  37. package/dist/src/room/types.d.ts +11 -3
  38. package/dist/src/room/types.d.ts.map +1 -1
  39. package/dist/src/version.d.ts +1 -1
  40. package/dist/ts4.2/src/api/SignalClient.d.ts +0 -2
  41. package/dist/ts4.2/src/index.d.ts +3 -3
  42. package/dist/ts4.2/src/options.d.ts +3 -9
  43. package/dist/ts4.2/src/proto/livekit_models_pb.d.ts +47 -0
  44. package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -0
  45. package/dist/ts4.2/src/room/Room.d.ts +12 -15
  46. package/dist/ts4.2/src/room/events.d.ts +0 -4
  47. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +8 -25
  48. package/dist/ts4.2/src/room/participant/Participant.d.ts +6 -10
  49. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +9 -6
  50. package/dist/ts4.2/src/room/timers.d.ts +4 -5
  51. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +3 -3
  52. package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +2 -2
  53. package/dist/ts4.2/src/room/track/Track.d.ts +5 -0
  54. package/dist/ts4.2/src/room/track/options.d.ts +0 -5
  55. package/dist/ts4.2/src/room/types.d.ts +11 -3
  56. package/dist/ts4.2/src/version.d.ts +1 -1
  57. package/package.json +8 -7
  58. package/src/api/SignalClient.ts +2 -6
  59. package/src/e2ee/E2eeManager.ts +2 -2
  60. package/src/index.ts +2 -4
  61. package/src/options.ts +3 -10
  62. package/src/proto/livekit_models_pb.ts +66 -0
  63. package/src/room/RTCEngine.ts +6 -1
  64. package/src/room/Room.ts +145 -114
  65. package/src/room/defaults.ts +1 -5
  66. package/src/room/events.ts +0 -5
  67. package/src/room/participant/LocalParticipant.ts +36 -77
  68. package/src/room/participant/Participant.ts +23 -24
  69. package/src/room/participant/RemoteParticipant.ts +27 -24
  70. package/src/room/track/LocalVideoTrack.test.ts +1 -1
  71. package/src/room/track/LocalVideoTrack.ts +11 -7
  72. package/src/room/track/RemoteTrackPublication.ts +2 -7
  73. package/src/room/track/Track.ts +10 -1
  74. package/src/room/track/options.ts +0 -6
  75. package/src/room/types.ts +11 -4
  76. package/src/version.ts +1 -1
@@ -3739,7 +3739,7 @@ proto3.util.setEnumType(TrackSource, "livekit.TrackSource", [{
3739
3739
  /**
3740
3740
  * @generated from enum livekit.VideoQuality
3741
3741
  */
3742
- var VideoQuality;
3742
+ var VideoQuality$1;
3743
3743
  (function (VideoQuality) {
3744
3744
  /**
3745
3745
  * @generated from enum value: LOW = 0;
@@ -3757,9 +3757,9 @@ var VideoQuality;
3757
3757
  * @generated from enum value: OFF = 3;
3758
3758
  */
3759
3759
  VideoQuality[VideoQuality["OFF"] = 3] = "OFF";
3760
- })(VideoQuality || (VideoQuality = {}));
3760
+ })(VideoQuality$1 || (VideoQuality$1 = {}));
3761
3761
  // Retrieve enum metadata with: proto3.getEnumType(VideoQuality)
3762
- proto3.util.setEnumType(VideoQuality, "livekit.VideoQuality", [{
3762
+ proto3.util.setEnumType(VideoQuality$1, "livekit.VideoQuality", [{
3763
3763
  no: 0,
3764
3764
  name: "LOW"
3765
3765
  }, {
@@ -4096,6 +4096,11 @@ Room$1.fields = proto3.util.newFieldList(() => [{
4096
4096
  name: "active_recording",
4097
4097
  kind: "scalar",
4098
4098
  T: 8 /* ScalarType.BOOL */
4099
+ }, {
4100
+ no: 13,
4101
+ name: "version",
4102
+ kind: "message",
4103
+ T: TimedVersion
4099
4104
  }]);
4100
4105
  /**
4101
4106
  * @generated from message livekit.Codec
@@ -4354,6 +4359,10 @@ class ParticipantInfo extends Message {
4354
4359
  * @generated from field: bool is_publisher = 13;
4355
4360
  */
4356
4361
  this.isPublisher = false;
4362
+ /**
4363
+ * @generated from field: livekit.ParticipantInfo.Kind kind = 14;
4364
+ */
4365
+ this.kind = ParticipantInfo_Kind.STANDARD;
4357
4366
  proto3.util.initPartial(data, this);
4358
4367
  }
4359
4368
  static fromBinary(bytes, options) {
@@ -4427,6 +4436,11 @@ ParticipantInfo.fields = proto3.util.newFieldList(() => [{
4427
4436
  name: "is_publisher",
4428
4437
  kind: "scalar",
4429
4438
  T: 8 /* ScalarType.BOOL */
4439
+ }, {
4440
+ no: 14,
4441
+ name: "kind",
4442
+ kind: "enum",
4443
+ T: proto3.getEnumType(ParticipantInfo_Kind)
4430
4444
  }]);
4431
4445
  /**
4432
4446
  * @generated from enum livekit.ParticipantInfo.State
@@ -4472,6 +4486,59 @@ proto3.util.setEnumType(ParticipantInfo_State, "livekit.ParticipantInfo.State",
4472
4486
  no: 3,
4473
4487
  name: "DISCONNECTED"
4474
4488
  }]);
4489
+ /**
4490
+ * @generated from enum livekit.ParticipantInfo.Kind
4491
+ */
4492
+ var ParticipantInfo_Kind;
4493
+ (function (ParticipantInfo_Kind) {
4494
+ /**
4495
+ * standard participants, e.g. web clients
4496
+ *
4497
+ * @generated from enum value: STANDARD = 0;
4498
+ */
4499
+ ParticipantInfo_Kind[ParticipantInfo_Kind["STANDARD"] = 0] = "STANDARD";
4500
+ /**
4501
+ * only ingests streams
4502
+ *
4503
+ * @generated from enum value: INGRESS = 1;
4504
+ */
4505
+ ParticipantInfo_Kind[ParticipantInfo_Kind["INGRESS"] = 1] = "INGRESS";
4506
+ /**
4507
+ * only consumes streams
4508
+ *
4509
+ * @generated from enum value: EGRESS = 2;
4510
+ */
4511
+ ParticipantInfo_Kind[ParticipantInfo_Kind["EGRESS"] = 2] = "EGRESS";
4512
+ /**
4513
+ * SIP participants
4514
+ *
4515
+ * @generated from enum value: SIP = 3;
4516
+ */
4517
+ ParticipantInfo_Kind[ParticipantInfo_Kind["SIP"] = 3] = "SIP";
4518
+ /**
4519
+ * LiveKit agents
4520
+ *
4521
+ * @generated from enum value: AGENT = 4;
4522
+ */
4523
+ ParticipantInfo_Kind[ParticipantInfo_Kind["AGENT"] = 4] = "AGENT";
4524
+ })(ParticipantInfo_Kind || (ParticipantInfo_Kind = {}));
4525
+ // Retrieve enum metadata with: proto3.getEnumType(ParticipantInfo_Kind)
4526
+ proto3.util.setEnumType(ParticipantInfo_Kind, "livekit.ParticipantInfo.Kind", [{
4527
+ no: 0,
4528
+ name: "STANDARD"
4529
+ }, {
4530
+ no: 1,
4531
+ name: "INGRESS"
4532
+ }, {
4533
+ no: 2,
4534
+ name: "EGRESS"
4535
+ }, {
4536
+ no: 3,
4537
+ name: "SIP"
4538
+ }, {
4539
+ no: 4,
4540
+ name: "AGENT"
4541
+ }]);
4475
4542
  /**
4476
4543
  * @generated from message livekit.Encryption
4477
4544
  */
@@ -4779,6 +4846,11 @@ TrackInfo.fields = proto3.util.newFieldList(() => [{
4779
4846
  name: "stream",
4780
4847
  kind: "scalar",
4781
4848
  T: 9 /* ScalarType.STRING */
4849
+ }, {
4850
+ no: 18,
4851
+ name: "version",
4852
+ kind: "message",
4853
+ T: TimedVersion
4782
4854
  }]);
4783
4855
  /**
4784
4856
  * provide information about available spatial layers
@@ -4793,7 +4865,7 @@ class VideoLayer extends Message {
4793
4865
  *
4794
4866
  * @generated from field: livekit.VideoQuality quality = 1;
4795
4867
  */
4796
- this.quality = VideoQuality.LOW;
4868
+ this.quality = VideoQuality$1.LOW;
4797
4869
  /**
4798
4870
  * @generated from field: uint32 width = 2;
4799
4871
  */
@@ -4833,7 +4905,7 @@ VideoLayer.fields = proto3.util.newFieldList(() => [{
4833
4905
  no: 1,
4834
4906
  name: "quality",
4835
4907
  kind: "enum",
4836
- T: proto3.getEnumType(VideoQuality)
4908
+ T: proto3.getEnumType(VideoQuality$1)
4837
4909
  }, {
4838
4910
  no: 2,
4839
4911
  name: "width",
@@ -10045,10 +10117,6 @@ var RoomEvent;
10045
10117
  * args: ([[ConnectionState]])
10046
10118
  */
10047
10119
  RoomEvent["ConnectionStateChanged"] = "connectionStateChanged";
10048
- /**
10049
- * @deprecated StateChanged has been renamed to ConnectionStateChanged
10050
- */
10051
- RoomEvent["StateChanged"] = "connectionStateChanged";
10052
10120
  /**
10053
10121
  * When input or output devices on the machine have changed.
10054
10122
  */
@@ -10614,10 +10682,10 @@ function getMatch(exp, ua) {
10614
10682
  return match && match.length >= id && match[id] || '';
10615
10683
  }
10616
10684
 
10617
- var version$1 = "1.15.11";
10685
+ var version$1 = "2.0.0";
10618
10686
 
10619
10687
  const version = version$1;
10620
- const protocolVersion = 11;
10688
+ const protocolVersion = 12;
10621
10689
 
10622
10690
  /**
10623
10691
  * Timers that can be overridden with platform specific implementations
@@ -11783,7 +11851,7 @@ class UpdateTrackSettings extends Message {
11783
11851
  *
11784
11852
  * @generated from field: livekit.VideoQuality quality = 4;
11785
11853
  */
11786
- this.quality = VideoQuality.LOW;
11854
+ this.quality = VideoQuality$1.LOW;
11787
11855
  /**
11788
11856
  * for video, width to receive
11789
11857
  *
@@ -11844,7 +11912,7 @@ UpdateTrackSettings.fields = proto3.util.newFieldList(() => [{
11844
11912
  no: 4,
11845
11913
  name: "quality",
11846
11914
  kind: "enum",
11847
- T: proto3.getEnumType(VideoQuality)
11915
+ T: proto3.getEnumType(VideoQuality$1)
11848
11916
  }, {
11849
11917
  no: 5,
11850
11918
  name: "width",
@@ -12292,7 +12360,7 @@ class SubscribedQuality extends Message {
12292
12360
  /**
12293
12361
  * @generated from field: livekit.VideoQuality quality = 1;
12294
12362
  */
12295
- this.quality = VideoQuality.LOW;
12363
+ this.quality = VideoQuality$1.LOW;
12296
12364
  /**
12297
12365
  * @generated from field: bool enabled = 2;
12298
12366
  */
@@ -12318,7 +12386,7 @@ SubscribedQuality.fields = proto3.util.newFieldList(() => [{
12318
12386
  no: 1,
12319
12387
  name: "quality",
12320
12388
  kind: "enum",
12321
- T: proto3.getEnumType(VideoQuality)
12389
+ T: proto3.getEnumType(VideoQuality$1)
12322
12390
  }, {
12323
12391
  no: 2,
12324
12392
  name: "enabled",
@@ -12997,6 +13065,12 @@ const BACKGROUND_REACTION_DELAY = 5000;
12997
13065
  // keep old audio elements when detached, we would re-use them since on iOS
12998
13066
  // Safari tracks which audio elements have been "blessed" by the user.
12999
13067
  const recycledElements = [];
13068
+ var VideoQuality;
13069
+ (function (VideoQuality) {
13070
+ VideoQuality[VideoQuality["LOW"] = 0] = "LOW";
13071
+ VideoQuality[VideoQuality["MEDIUM"] = 1] = "MEDIUM";
13072
+ VideoQuality[VideoQuality["HIGH"] = 2] = "HIGH";
13073
+ })(VideoQuality || (VideoQuality = {}));
13000
13074
  class Track extends eventsExports.EventEmitter {
13001
13075
  constructor(mediaTrack, kind) {
13002
13076
  let loggerOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
@@ -14548,8 +14622,8 @@ class E2EEManager extends eventsExports.EventEmitter {
14548
14622
  room.on(RoomEvent.TrackPublished, (pub, participant) => this.setParticipantCryptorEnabled(pub.trackInfo.encryption !== Encryption_Type.NONE, participant.identity));
14549
14623
  room.on(RoomEvent.ConnectionStateChanged, state => {
14550
14624
  if (state === ConnectionState.Connected) {
14551
- room.participants.forEach(participant => {
14552
- participant.tracks.forEach(pub => {
14625
+ room.remoteParticipants.forEach(participant => {
14626
+ participant.trackPublications.forEach(pub => {
14553
14627
  this.setParticipantCryptorEnabled(pub.trackInfo.encryption !== Encryption_Type.NONE, participant.identity);
14554
14628
  });
14555
14629
  });
@@ -15025,6 +15099,8 @@ class SignalClient {
15025
15099
  }
15026
15100
  this.log.warn("websocket closed", Object.assign(Object.assign({}, this.logContext), {
15027
15101
  reason: ev.reason,
15102
+ code: ev.code,
15103
+ wasClean: ev.wasClean,
15028
15104
  state: this.state
15029
15105
  }));
15030
15106
  this.handleOnClose(ev.reason);
@@ -15441,9 +15517,6 @@ function createConnectionParams(token, info, opts) {
15441
15517
  if (info.browserVersion) {
15442
15518
  params.set('browser_version', info.browserVersion);
15443
15519
  }
15444
- if (opts.publishOnly !== undefined) {
15445
- params.set('publish', opts.publishOnly);
15446
- }
15447
15520
  if (opts.adaptiveStream) {
15448
15521
  params.set('adaptive_stream', '1');
15449
15522
  }
@@ -16657,10 +16730,6 @@ function extractStereoAndNackAudioFromOffer(offer) {
16657
16730
 
16658
16731
  const defaultVideoCodec = 'vp8';
16659
16732
  const publishDefaults = {
16660
- /**
16661
- * @deprecated
16662
- */
16663
- audioBitrate: AudioPresets.music.maxBitrate,
16664
16733
  audioPreset: AudioPresets.music,
16665
16734
  dtx: true,
16666
16735
  red: true,
@@ -16685,7 +16754,7 @@ const roomOptionDefaults = {
16685
16754
  stopLocalTrackOnUnpublish: true,
16686
16755
  reconnectPolicy: new DefaultReconnectPolicy(),
16687
16756
  disconnectOnPageLeave: true,
16688
- expWebAudioMix: false
16757
+ webAudioMix: true
16689
16758
  };
16690
16759
  const roomConnectOptionDefaults = {
16691
16760
  autoSubscribe: true,
@@ -16989,6 +17058,9 @@ class RTCEngine extends eventsExports.EventEmitter {
16989
17058
  get isClosed() {
16990
17059
  return this._isClosed;
16991
17060
  }
17061
+ get pendingReconnect() {
17062
+ return !!this.reconnectTimeout;
17063
+ }
16992
17064
  constructor(options) {
16993
17065
  var _a;
16994
17066
  super();
@@ -17113,7 +17185,7 @@ class RTCEngine extends eventsExports.EventEmitter {
17113
17185
  // since the current engine may have inherited a regional url
17114
17186
  this.regionUrlProvider.updateToken(this.token);
17115
17187
  }
17116
- this.reconnectTimeout = CriticalTimers.setTimeout(() => this.attemptReconnect(disconnectReason), delay);
17188
+ this.reconnectTimeout = CriticalTimers.setTimeout(() => this.attemptReconnect(disconnectReason).finally(() => this.reconnectTimeout = undefined), delay);
17117
17189
  };
17118
17190
  this.waitForRestarted = () => {
17119
17191
  return new Promise((resolve, reject) => {
@@ -18958,7 +19030,8 @@ class LocalVideoTrack extends LocalTrack {
18958
19030
  }
18959
19031
  addSimulcastTrack(codec, encodings) {
18960
19032
  if (this.simulcastCodecs.has(codec)) {
18961
- throw new Error("".concat(codec, " already added"));
19033
+ this.log.error("".concat(codec, " already added, skipping adding simulcast codec"), this.logContext);
19034
+ return;
18962
19035
  }
18963
19036
  const simulcastCodecInfo = {
18964
19037
  codec,
@@ -19987,7 +20060,7 @@ class Participant extends eventsExports.EventEmitter {
19987
20060
  });
19988
20061
  }
19989
20062
  get isEncrypted() {
19990
- return this.tracks.size > 0 && Array.from(this.tracks.values()).every(tr => tr.isEncrypted);
20063
+ return this.trackPublications.size > 0 && Array.from(this.trackPublications.values()).every(tr => tr.isEncrypted);
19991
20064
  }
19992
20065
  get isAgent() {
19993
20066
  var _a, _b;
@@ -20010,21 +20083,19 @@ class Participant extends eventsExports.EventEmitter {
20010
20083
  this.identity = identity;
20011
20084
  this.name = name;
20012
20085
  this.metadata = metadata;
20013
- this.audioTracks = new Map();
20014
- this.videoTracks = new Map();
20015
- this.tracks = new Map();
20086
+ this.audioTrackPublications = new Map();
20087
+ this.videoTrackPublications = new Map();
20088
+ this.trackPublications = new Map();
20016
20089
  }
20017
- getTracks() {
20018
- return Array.from(this.tracks.values());
20090
+ getTrackPublications() {
20091
+ return Array.from(this.trackPublications.values());
20019
20092
  }
20020
20093
  /**
20021
20094
  * Finds the first track that matches the source filter, for example, getting
20022
20095
  * the user's camera track with getTrackBySource(Track.Source.Camera).
20023
- * @param source
20024
- * @returns
20025
20096
  */
20026
- getTrack(source) {
20027
- for (const [, pub] of this.tracks) {
20097
+ getTrackPublication(source) {
20098
+ for (const [, pub] of this.trackPublications) {
20028
20099
  if (pub.source === source) {
20029
20100
  return pub;
20030
20101
  }
@@ -20032,11 +20103,9 @@ class Participant extends eventsExports.EventEmitter {
20032
20103
  }
20033
20104
  /**
20034
20105
  * Finds the first track that matches the track's name.
20035
- * @param name
20036
- * @returns
20037
20106
  */
20038
- getTrackByName(name) {
20039
- for (const [, pub] of this.tracks) {
20107
+ getTrackPublicationByName(name) {
20108
+ for (const [, pub] of this.trackPublications) {
20040
20109
  if (pub.trackName === name) {
20041
20110
  return pub;
20042
20111
  }
@@ -20047,16 +20116,16 @@ class Participant extends eventsExports.EventEmitter {
20047
20116
  }
20048
20117
  get isCameraEnabled() {
20049
20118
  var _a;
20050
- const track = this.getTrack(Track.Source.Camera);
20119
+ const track = this.getTrackPublication(Track.Source.Camera);
20051
20120
  return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true);
20052
20121
  }
20053
20122
  get isMicrophoneEnabled() {
20054
20123
  var _a;
20055
- const track = this.getTrack(Track.Source.Microphone);
20124
+ const track = this.getTrackPublication(Track.Source.Microphone);
20056
20125
  return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true);
20057
20126
  }
20058
20127
  get isScreenShareEnabled() {
20059
- const track = this.getTrack(Track.Source.ScreenShare);
20128
+ const track = this.getTrackPublication(Track.Source.ScreenShare);
20060
20129
  return !!track;
20061
20130
  }
20062
20131
  get isLocal() {
@@ -20150,7 +20219,7 @@ class Participant extends eventsExports.EventEmitter {
20150
20219
  */
20151
20220
  setAudioContext(ctx) {
20152
20221
  this.audioContext = ctx;
20153
- this.audioTracks.forEach(track => (track.track instanceof RemoteAudioTrack || track.track instanceof LocalAudioTrack) && track.track.setAudioContext(ctx));
20222
+ this.audioTrackPublications.forEach(track => (track.track instanceof RemoteAudioTrack || track.track instanceof LocalAudioTrack) && track.track.setAudioContext(ctx));
20154
20223
  }
20155
20224
  addTrackPublication(publication) {
20156
20225
  // forward publication driven events
@@ -20164,13 +20233,13 @@ class Participant extends eventsExports.EventEmitter {
20164
20233
  if (pub.track) {
20165
20234
  pub.track.sid = publication.trackSid;
20166
20235
  }
20167
- this.tracks.set(publication.trackSid, publication);
20236
+ this.trackPublications.set(publication.trackSid, publication);
20168
20237
  switch (publication.kind) {
20169
20238
  case Track.Kind.Audio:
20170
- this.audioTracks.set(publication.trackSid, publication);
20239
+ this.audioTrackPublications.set(publication.trackSid, publication);
20171
20240
  break;
20172
20241
  case Track.Kind.Video:
20173
- this.videoTracks.set(publication.trackSid, publication);
20242
+ this.videoTrackPublications.set(publication.trackSid, publication);
20174
20243
  break;
20175
20244
  }
20176
20245
  }
@@ -20189,993 +20258,502 @@ function trackPermissionToProto(perms) {
20189
20258
  });
20190
20259
  }
20191
20260
 
20192
- class RemoteTrackPublication extends TrackPublication {
20193
- constructor(kind, ti, autoSubscribe, loggerOptions) {
20194
- super(kind, ti.sid, ti.name, loggerOptions);
20195
- this.track = undefined;
20261
+ class LocalParticipant extends Participant {
20262
+ /** @internal */
20263
+ constructor(sid, identity, engine, options) {
20264
+ super(sid, identity, undefined, undefined, {
20265
+ loggerName: options.loggerName,
20266
+ loggerContextCb: () => this.engine.logContext
20267
+ });
20268
+ this.pendingPublishing = new Set();
20269
+ this.pendingPublishPromises = new Map();
20270
+ this.participantTrackPermissions = [];
20271
+ this.allParticipantsAllowedToSubscribe = true;
20272
+ this.encryptionType = Encryption_Type.NONE;
20273
+ this.handleReconnecting = () => {
20274
+ if (!this.reconnectFuture) {
20275
+ this.reconnectFuture = new Future();
20276
+ }
20277
+ };
20278
+ this.handleReconnected = () => {
20279
+ var _a, _b;
20280
+ (_b = (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.resolve) === null || _b === void 0 ? void 0 : _b.call(_a);
20281
+ this.reconnectFuture = undefined;
20282
+ this.updateTrackSubscriptionPermissions();
20283
+ };
20284
+ this.handleDisconnected = () => {
20285
+ var _a, _b;
20286
+ if (this.reconnectFuture) {
20287
+ this.reconnectFuture.promise.catch(e => this.log.warn(e.message, this.logContext));
20288
+ (_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');
20289
+ this.reconnectFuture = undefined;
20290
+ }
20291
+ };
20292
+ this.updateTrackSubscriptionPermissions = () => {
20293
+ this.log.debug('updating track subscription permissions', Object.assign(Object.assign({}, this.logContext), {
20294
+ allParticipantsAllowed: this.allParticipantsAllowedToSubscribe,
20295
+ participantTrackPermissions: this.participantTrackPermissions
20296
+ }));
20297
+ this.engine.client.sendUpdateSubscriptionPermissions(this.allParticipantsAllowedToSubscribe, this.participantTrackPermissions.map(p => trackPermissionToProto(p)));
20298
+ };
20196
20299
  /** @internal */
20197
- this.allowed = true;
20198
- this.disabled = false;
20199
- this.currentVideoQuality = VideoQuality.HIGH;
20200
- this.handleEnded = track => {
20201
- this.setTrack(undefined);
20202
- this.emit(TrackEvent.Ended, track);
20300
+ this.onTrackUnmuted = track => {
20301
+ this.onTrackMuted(track, track.isUpstreamPaused);
20203
20302
  };
20204
- this.handleVisibilityChange = visible => {
20205
- this.log.debug("adaptivestream video visibility ".concat(this.trackSid, ", visible=").concat(visible), this.logContext);
20206
- this.disabled = !visible;
20207
- this.emitTrackUpdate();
20303
+ // when the local track changes in mute status, we'll notify server as such
20304
+ /** @internal */
20305
+ this.onTrackMuted = (track, muted) => {
20306
+ if (muted === undefined) {
20307
+ muted = true;
20308
+ }
20309
+ if (!track.sid) {
20310
+ this.log.error('could not update mute status for unpublished track', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20311
+ return;
20312
+ }
20313
+ this.engine.updateMuteStatus(track.sid, muted);
20208
20314
  };
20209
- this.handleVideoDimensionsChange = dimensions => {
20210
- this.log.debug("adaptivestream video dimensions ".concat(dimensions.width, "x").concat(dimensions.height), this.logContext);
20211
- this.videoDimensions = dimensions;
20212
- this.emitTrackUpdate();
20315
+ this.onTrackUpstreamPaused = track => {
20316
+ this.log.debug('upstream paused', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20317
+ this.onTrackMuted(track, true);
20213
20318
  };
20214
- this.subscribed = autoSubscribe;
20215
- this.updateInfo(ti);
20319
+ this.onTrackUpstreamResumed = track => {
20320
+ this.log.debug('upstream resumed', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20321
+ this.onTrackMuted(track, track.isMuted);
20322
+ };
20323
+ this.handleSubscribedQualityUpdate = update => __awaiter(this, void 0, void 0, function* () {
20324
+ var _a, e_1, _b, _c;
20325
+ var _d, _e;
20326
+ if (!((_d = this.roomOptions) === null || _d === void 0 ? void 0 : _d.dynacast)) {
20327
+ return;
20328
+ }
20329
+ const pub = this.videoTrackPublications.get(update.trackSid);
20330
+ if (!pub) {
20331
+ this.log.warn('received subscribed quality update for unknown track', Object.assign(Object.assign({}, this.logContext), {
20332
+ trackSid: update.trackSid
20333
+ }));
20334
+ return;
20335
+ }
20336
+ if (update.subscribedCodecs.length > 0) {
20337
+ if (!pub.videoTrack) {
20338
+ return;
20339
+ }
20340
+ const newCodecs = yield pub.videoTrack.setPublishingCodecs(update.subscribedCodecs);
20341
+ try {
20342
+ for (var _f = true, newCodecs_1 = __asyncValues(newCodecs), newCodecs_1_1; newCodecs_1_1 = yield newCodecs_1.next(), _a = newCodecs_1_1.done, !_a; _f = true) {
20343
+ _c = newCodecs_1_1.value;
20344
+ _f = false;
20345
+ const codec = _c;
20346
+ if (isBackupCodec(codec)) {
20347
+ this.log.debug("publish ".concat(codec, " for ").concat(pub.videoTrack.sid), Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(pub)));
20348
+ yield this.publishAdditionalCodecForTrack(pub.videoTrack, codec, pub.options);
20349
+ }
20350
+ }
20351
+ } catch (e_1_1) {
20352
+ e_1 = {
20353
+ error: e_1_1
20354
+ };
20355
+ } finally {
20356
+ try {
20357
+ if (!_f && !_a && (_b = newCodecs_1.return)) yield _b.call(newCodecs_1);
20358
+ } finally {
20359
+ if (e_1) throw e_1.error;
20360
+ }
20361
+ }
20362
+ } else if (update.subscribedQualities.length > 0) {
20363
+ yield (_e = pub.videoTrack) === null || _e === void 0 ? void 0 : _e.setPublishingLayers(update.subscribedQualities);
20364
+ }
20365
+ });
20366
+ this.handleLocalTrackUnpublished = unpublished => {
20367
+ const track = this.trackPublications.get(unpublished.trackSid);
20368
+ if (!track) {
20369
+ this.log.warn('received unpublished event for unknown track', Object.assign(Object.assign({}, this.logContext), {
20370
+ trackSid: unpublished.trackSid
20371
+ }));
20372
+ return;
20373
+ }
20374
+ this.unpublishTrack(track.track);
20375
+ };
20376
+ this.handleTrackEnded = track => __awaiter(this, void 0, void 0, function* () {
20377
+ if (track.source === Track.Source.ScreenShare || track.source === Track.Source.ScreenShareAudio) {
20378
+ this.log.debug('unpublishing local track due to TrackEnded', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20379
+ this.unpublishTrack(track);
20380
+ } else if (track.isUserProvided) {
20381
+ yield track.mute();
20382
+ } else if (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) {
20383
+ try {
20384
+ if (isWeb()) {
20385
+ try {
20386
+ const currentPermissions = yield navigator === null || navigator === void 0 ? void 0 : navigator.permissions.query({
20387
+ // the permission query for camera and microphone currently not supported in Safari and Firefox
20388
+ // @ts-ignore
20389
+ name: track.source === Track.Source.Camera ? 'camera' : 'microphone'
20390
+ });
20391
+ if (currentPermissions && currentPermissions.state === 'denied') {
20392
+ this.log.warn("user has revoked access to ".concat(track.source), Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20393
+ // detect granted change after permissions were denied to try and resume then
20394
+ currentPermissions.onchange = () => {
20395
+ if (currentPermissions.state !== 'denied') {
20396
+ if (!track.isMuted) {
20397
+ track.restartTrack();
20398
+ }
20399
+ currentPermissions.onchange = null;
20400
+ }
20401
+ };
20402
+ throw new Error('GetUserMedia Permission denied');
20403
+ }
20404
+ } catch (e) {
20405
+ // permissions query fails for firefox, we continue and try to restart the track
20406
+ }
20407
+ }
20408
+ if (!track.isMuted) {
20409
+ this.log.debug('track ended, attempting to use a different device', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20410
+ yield track.restartTrack();
20411
+ }
20412
+ } catch (e) {
20413
+ this.log.warn("could not restart track, muting instead", Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20414
+ yield track.mute();
20415
+ }
20416
+ }
20417
+ });
20418
+ this.audioTrackPublications = new Map();
20419
+ this.videoTrackPublications = new Map();
20420
+ this.trackPublications = new Map();
20421
+ this.engine = engine;
20422
+ this.roomOptions = options;
20423
+ this.setupEngine(engine);
20424
+ this.activeDeviceMap = new Map();
20425
+ }
20426
+ get lastCameraError() {
20427
+ return this.cameraError;
20428
+ }
20429
+ get lastMicrophoneError() {
20430
+ return this.microphoneError;
20431
+ }
20432
+ get isE2EEEnabled() {
20433
+ return this.encryptionType !== Encryption_Type.NONE;
20434
+ }
20435
+ getTrackPublication(source) {
20436
+ const track = super.getTrackPublication(source);
20437
+ if (track) {
20438
+ return track;
20439
+ }
20440
+ }
20441
+ getTrackPublicationByName(name) {
20442
+ const track = super.getTrackPublicationByName(name);
20443
+ if (track) {
20444
+ return track;
20445
+ }
20216
20446
  }
20217
20447
  /**
20218
- * Subscribe or unsubscribe to this remote track
20219
- * @param subscribed true to subscribe to a track, false to unsubscribe
20448
+ * @internal
20220
20449
  */
20221
- setSubscribed(subscribed) {
20222
- const prevStatus = this.subscriptionStatus;
20223
- const prevPermission = this.permissionStatus;
20224
- this.subscribed = subscribed;
20225
- // reset allowed status when desired subscription state changes
20226
- // server will notify client via signal message if it's not allowed
20227
- if (subscribed) {
20228
- this.allowed = true;
20229
- }
20230
- const sub = new UpdateSubscription({
20231
- trackSids: [this.trackSid],
20232
- subscribe: this.subscribed,
20233
- participantTracks: [new ParticipantTracks({
20234
- // sending an empty participant id since TrackPublication doesn't keep it
20235
- // this is filled in by the participant that receives this message
20236
- participantSid: '',
20237
- trackSids: [this.trackSid]
20238
- })]
20450
+ setupEngine(engine) {
20451
+ this.engine = engine;
20452
+ this.engine.on(EngineEvent.RemoteMute, (trackSid, muted) => {
20453
+ const pub = this.trackPublications.get(trackSid);
20454
+ if (!pub || !pub.track) {
20455
+ return;
20456
+ }
20457
+ if (muted) {
20458
+ pub.mute();
20459
+ } else {
20460
+ pub.unmute();
20461
+ }
20239
20462
  });
20240
- this.emit(TrackEvent.UpdateSubscription, sub);
20241
- this.emitSubscriptionUpdateIfChanged(prevStatus);
20242
- this.emitPermissionUpdateIfChanged(prevPermission);
20243
- }
20244
- get subscriptionStatus() {
20245
- if (this.subscribed === false) {
20246
- return TrackPublication.SubscriptionStatus.Unsubscribed;
20247
- }
20248
- if (!super.isSubscribed) {
20249
- return TrackPublication.SubscriptionStatus.Desired;
20250
- }
20251
- return TrackPublication.SubscriptionStatus.Subscribed;
20252
- }
20253
- get permissionStatus() {
20254
- return this.allowed ? TrackPublication.PermissionStatus.Allowed : TrackPublication.PermissionStatus.NotAllowed;
20463
+ 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);
20255
20464
  }
20256
20465
  /**
20257
- * Returns true if track is subscribed, and ready for playback
20466
+ * Sets and updates the metadata of the local participant.
20467
+ * The change does not take immediate effect.
20468
+ * If successful, a `ParticipantEvent.MetadataChanged` event will be emitted on the local participant.
20469
+ * Note: this requires `canUpdateOwnMetadata` permission.
20470
+ * @param metadata
20258
20471
  */
20259
- get isSubscribed() {
20260
- if (this.subscribed === false) {
20261
- return false;
20262
- }
20263
- return super.isSubscribed;
20264
- }
20265
- // returns client's desire to subscribe to a track, also true if autoSubscribe is enabled
20266
- get isDesired() {
20267
- return this.subscribed !== false;
20268
- }
20269
- get isEnabled() {
20270
- return !this.disabled;
20472
+ setMetadata(metadata) {
20473
+ var _a;
20474
+ this.engine.client.sendUpdateLocalMetadata(metadata, (_a = this.name) !== null && _a !== void 0 ? _a : '');
20271
20475
  }
20272
20476
  /**
20273
- * disable server from sending down data for this track. this is useful when
20274
- * the participant is off screen, you may disable streaming down their video
20275
- * to reduce bandwidth requirements
20276
- * @param enabled
20477
+ * Sets and updates the name of the local participant.
20478
+ * The change does not take immediate effect.
20479
+ * If successful, a `ParticipantEvent.ParticipantNameChanged` event will be emitted on the local participant.
20480
+ * Note: this requires `canUpdateOwnMetadata` permission.
20481
+ * @param metadata
20277
20482
  */
20278
- setEnabled(enabled) {
20279
- if (!this.isManualOperationAllowed() || this.disabled === !enabled) {
20280
- return;
20281
- }
20282
- this.disabled = !enabled;
20283
- this.emitTrackUpdate();
20483
+ setName(name) {
20484
+ var _a;
20485
+ this.engine.client.sendUpdateLocalMetadata((_a = this.metadata) !== null && _a !== void 0 ? _a : '', name);
20284
20486
  }
20285
20487
  /**
20286
- * for tracks that support simulcasting, adjust subscribed quality
20488
+ * Enable or disable a participant's camera track.
20287
20489
  *
20288
- * This indicates the highest quality the client can accept. if network
20289
- * bandwidth does not allow, server will automatically reduce quality to
20290
- * optimize for uninterrupted video
20490
+ * If a track has already published, it'll mute or unmute the track.
20491
+ * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20291
20492
  */
20292
- setVideoQuality(quality) {
20293
- if (!this.isManualOperationAllowed() || this.currentVideoQuality === quality) {
20294
- return;
20295
- }
20296
- this.currentVideoQuality = quality;
20297
- this.videoDimensions = undefined;
20298
- this.emitTrackUpdate();
20299
- }
20300
- setVideoDimensions(dimensions) {
20301
- var _a, _b;
20302
- if (!this.isManualOperationAllowed()) {
20303
- return;
20304
- }
20305
- if (((_a = this.videoDimensions) === null || _a === void 0 ? void 0 : _a.width) === dimensions.width && ((_b = this.videoDimensions) === null || _b === void 0 ? void 0 : _b.height) === dimensions.height) {
20306
- return;
20307
- }
20308
- if (this.track instanceof RemoteVideoTrack) {
20309
- this.videoDimensions = dimensions;
20310
- }
20311
- this.currentVideoQuality = undefined;
20312
- this.emitTrackUpdate();
20493
+ setCameraEnabled(enabled, options, publishOptions) {
20494
+ return this.setTrackEnabled(Track.Source.Camera, enabled, options, publishOptions);
20313
20495
  }
20314
- setVideoFPS(fps) {
20315
- if (!this.isManualOperationAllowed()) {
20316
- return;
20317
- }
20318
- if (!(this.track instanceof RemoteVideoTrack)) {
20319
- return;
20320
- }
20321
- if (this.fps === fps) {
20322
- return;
20323
- }
20324
- this.fps = fps;
20325
- this.emitTrackUpdate();
20496
+ /**
20497
+ * Enable or disable a participant's microphone track.
20498
+ *
20499
+ * If a track has already published, it'll mute or unmute the track.
20500
+ * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20501
+ */
20502
+ setMicrophoneEnabled(enabled, options, publishOptions) {
20503
+ return this.setTrackEnabled(Track.Source.Microphone, enabled, options, publishOptions);
20326
20504
  }
20327
- get videoQuality() {
20328
- return this.currentVideoQuality;
20505
+ /**
20506
+ * Start or stop sharing a participant's screen
20507
+ * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20508
+ */
20509
+ setScreenShareEnabled(enabled, options, publishOptions) {
20510
+ return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options, publishOptions);
20329
20511
  }
20330
20512
  /** @internal */
20331
- setTrack(track) {
20332
- const prevStatus = this.subscriptionStatus;
20333
- const prevPermission = this.permissionStatus;
20334
- const prevTrack = this.track;
20335
- if (prevTrack === track) {
20336
- return;
20337
- }
20338
- if (prevTrack) {
20339
- // unregister listener
20340
- prevTrack.off(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
20341
- prevTrack.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
20342
- prevTrack.off(TrackEvent.Ended, this.handleEnded);
20343
- prevTrack.detach();
20344
- prevTrack.stopMonitor();
20345
- this.emit(TrackEvent.Unsubscribed, prevTrack);
20346
- }
20347
- super.setTrack(track);
20348
- if (track) {
20349
- track.sid = this.trackSid;
20350
- track.on(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
20351
- track.on(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
20352
- track.on(TrackEvent.Ended, this.handleEnded);
20353
- this.emit(TrackEvent.Subscribed, track);
20513
+ setPermissions(permissions) {
20514
+ const prevPermissions = this.permissions;
20515
+ const changed = super.setPermissions(permissions);
20516
+ if (changed && prevPermissions) {
20517
+ this.emit(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions);
20354
20518
  }
20355
- this.emitPermissionUpdateIfChanged(prevPermission);
20356
- this.emitSubscriptionUpdateIfChanged(prevStatus);
20357
- }
20358
- /** @internal */
20359
- setAllowed(allowed) {
20360
- const prevStatus = this.subscriptionStatus;
20361
- const prevPermission = this.permissionStatus;
20362
- this.allowed = allowed;
20363
- this.emitPermissionUpdateIfChanged(prevPermission);
20364
- this.emitSubscriptionUpdateIfChanged(prevStatus);
20365
- }
20366
- /** @internal */
20367
- setSubscriptionError(error) {
20368
- this.emit(TrackEvent.SubscriptionFailed, error);
20519
+ return changed;
20369
20520
  }
20370
20521
  /** @internal */
20371
- updateInfo(info) {
20372
- super.updateInfo(info);
20373
- const prevMetadataMuted = this.metadataMuted;
20374
- this.metadataMuted = info.muted;
20375
- if (this.track) {
20376
- this.track.setMuted(info.muted);
20377
- } else if (prevMetadataMuted !== info.muted) {
20378
- this.emit(info.muted ? TrackEvent.Muted : TrackEvent.Unmuted);
20379
- }
20380
- }
20381
- emitSubscriptionUpdateIfChanged(previousStatus) {
20382
- const currentStatus = this.subscriptionStatus;
20383
- if (previousStatus === currentStatus) {
20384
- return;
20385
- }
20386
- this.emit(TrackEvent.SubscriptionStatusChanged, currentStatus, previousStatus);
20387
- }
20388
- emitPermissionUpdateIfChanged(previousPermissionStatus) {
20389
- const currentPermissionStatus = this.permissionStatus;
20390
- if (currentPermissionStatus !== previousPermissionStatus) {
20391
- this.emit(TrackEvent.SubscriptionPermissionChanged, this.permissionStatus, previousPermissionStatus);
20392
- }
20393
- }
20394
- isManualOperationAllowed() {
20395
- if (this.kind === Track.Kind.Video && this.isAdaptiveStream) {
20396
- this.log.warn('adaptive stream is enabled, cannot change video track settings', this.logContext);
20397
- return false;
20398
- }
20399
- if (!this.isDesired) {
20400
- this.log.warn('cannot update track settings when not subscribed', this.logContext);
20401
- return false;
20402
- }
20403
- return true;
20404
- }
20405
- get isAdaptiveStream() {
20406
- return this.track instanceof RemoteVideoTrack && this.track.isAdaptiveStream;
20407
- }
20408
- /* @internal */
20409
- emitTrackUpdate() {
20410
- const settings = new UpdateTrackSettings({
20411
- trackSids: [this.trackSid],
20412
- disabled: this.disabled,
20413
- fps: this.fps
20522
+ setE2EEEnabled(enabled) {
20523
+ return __awaiter(this, void 0, void 0, function* () {
20524
+ this.encryptionType = enabled ? Encryption_Type.GCM : Encryption_Type.NONE;
20525
+ yield this.republishAllTracks(undefined, false);
20414
20526
  });
20415
- if (this.videoDimensions) {
20416
- settings.width = Math.ceil(this.videoDimensions.width);
20417
- settings.height = Math.ceil(this.videoDimensions.height);
20418
- } else if (this.currentVideoQuality !== undefined) {
20419
- settings.quality = this.currentVideoQuality;
20420
- } else {
20421
- // defaults to high quality
20422
- settings.quality = VideoQuality.HIGH;
20423
- }
20424
- this.emit(TrackEvent.UpdateSettings, settings);
20425
20527
  }
20426
- }
20427
-
20428
- class RemoteParticipant extends Participant {
20429
- /** @internal */
20430
- static fromParticipantInfo(signalClient, pi) {
20431
- return new RemoteParticipant(signalClient, pi.sid, pi.identity, pi.name, pi.metadata);
20432
- }
20433
- /** @internal */
20434
- constructor(signalClient, sid, identity, name, metadata, loggerOptions) {
20435
- super(sid, identity || '', name, metadata, loggerOptions);
20436
- this.signalClient = signalClient;
20437
- this.tracks = new Map();
20438
- this.audioTracks = new Map();
20439
- this.videoTracks = new Map();
20440
- this.volumeMap = new Map();
20441
- }
20442
- addTrackPublication(publication) {
20443
- super.addTrackPublication(publication);
20444
- // register action events
20445
- publication.on(TrackEvent.UpdateSettings, settings => {
20446
- this.log.debug('send update settings', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
20447
- this.signalClient.sendUpdateTrackSettings(settings);
20448
- });
20449
- publication.on(TrackEvent.UpdateSubscription, sub => {
20450
- sub.participantTracks.forEach(pt => {
20451
- pt.participantSid = this.sid;
20452
- });
20453
- this.signalClient.sendUpdateSubscription(sub);
20454
- });
20455
- publication.on(TrackEvent.SubscriptionPermissionChanged, status => {
20456
- this.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, publication, status);
20457
- });
20458
- publication.on(TrackEvent.SubscriptionStatusChanged, status => {
20459
- this.emit(ParticipantEvent.TrackSubscriptionStatusChanged, publication, status);
20460
- });
20461
- publication.on(TrackEvent.Subscribed, track => {
20462
- this.emit(ParticipantEvent.TrackSubscribed, track, publication);
20463
- });
20464
- publication.on(TrackEvent.Unsubscribed, previousTrack => {
20465
- this.emit(ParticipantEvent.TrackUnsubscribed, previousTrack, publication);
20466
- });
20467
- publication.on(TrackEvent.SubscriptionFailed, error => {
20468
- this.emit(ParticipantEvent.TrackSubscriptionFailed, publication.trackSid, error);
20469
- });
20470
- }
20471
- getTrack(source) {
20472
- const track = super.getTrack(source);
20473
- if (track) {
20474
- return track;
20475
- }
20476
- }
20477
- getTrackByName(name) {
20478
- const track = super.getTrackByName(name);
20479
- if (track) {
20528
+ setTrackEnabled(source, enabled, options, publishOptions) {
20529
+ var _a, _b;
20530
+ return __awaiter(this, void 0, void 0, function* () {
20531
+ this.log.debug('setTrackEnabled', Object.assign(Object.assign({}, this.logContext), {
20532
+ source,
20533
+ enabled
20534
+ }));
20535
+ let track = this.getTrackPublication(source);
20536
+ if (enabled) {
20537
+ if (track) {
20538
+ yield track.unmute();
20539
+ } else {
20540
+ let localTracks;
20541
+ if (this.pendingPublishing.has(source)) {
20542
+ this.log.info('skipping duplicate published source', Object.assign(Object.assign({}, this.logContext), {
20543
+ source
20544
+ }));
20545
+ // no-op it's already been requested
20546
+ return;
20547
+ }
20548
+ this.pendingPublishing.add(source);
20549
+ try {
20550
+ switch (source) {
20551
+ case Track.Source.Camera:
20552
+ localTracks = yield this.createTracks({
20553
+ video: (_a = options) !== null && _a !== void 0 ? _a : true
20554
+ });
20555
+ break;
20556
+ case Track.Source.Microphone:
20557
+ localTracks = yield this.createTracks({
20558
+ audio: (_b = options) !== null && _b !== void 0 ? _b : true
20559
+ });
20560
+ break;
20561
+ case Track.Source.ScreenShare:
20562
+ localTracks = yield this.createScreenTracks(Object.assign({}, options));
20563
+ break;
20564
+ default:
20565
+ throw new TrackInvalidError(source);
20566
+ }
20567
+ const publishPromises = [];
20568
+ for (const localTrack of localTracks) {
20569
+ this.log.info('publishing track', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(localTrack)));
20570
+ publishPromises.push(this.publishTrack(localTrack, publishOptions));
20571
+ }
20572
+ const publishedTracks = yield Promise.all(publishPromises);
20573
+ // for screen share publications including audio, this will only return the screen share publication, not the screen share audio one
20574
+ // revisit if we want to return an array of tracks instead for v2
20575
+ [track] = publishedTracks;
20576
+ } catch (e) {
20577
+ localTracks === null || localTracks === void 0 ? void 0 : localTracks.forEach(tr => {
20578
+ tr.stop();
20579
+ });
20580
+ if (e instanceof Error && !(e instanceof TrackInvalidError)) {
20581
+ this.emit(ParticipantEvent.MediaDevicesError, e);
20582
+ }
20583
+ throw e;
20584
+ } finally {
20585
+ this.pendingPublishing.delete(source);
20586
+ }
20587
+ }
20588
+ } else if (track && track.track) {
20589
+ // screenshare cannot be muted, unpublish instead
20590
+ if (source === Track.Source.ScreenShare) {
20591
+ track = yield this.unpublishTrack(track.track);
20592
+ const screenAudioTrack = this.getTrackPublication(Track.Source.ScreenShareAudio);
20593
+ if (screenAudioTrack && screenAudioTrack.track) {
20594
+ this.unpublishTrack(screenAudioTrack.track);
20595
+ }
20596
+ } else {
20597
+ yield track.mute();
20598
+ }
20599
+ }
20480
20600
  return track;
20481
- }
20601
+ });
20482
20602
  }
20483
20603
  /**
20484
- * sets the volume on the participant's audio track
20485
- * by default, this affects the microphone publication
20486
- * a different source can be passed in as a second argument
20487
- * if no track exists the volume will be applied when the microphone track is added
20604
+ * Publish both camera and microphone at the same time. This is useful for
20605
+ * displaying a single Permission Dialog box to the end user.
20488
20606
  */
20489
- setVolume(volume) {
20490
- let source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Track.Source.Microphone;
20491
- this.volumeMap.set(source, volume);
20492
- const audioPublication = this.getTrack(source);
20493
- if (audioPublication && audioPublication.track) {
20494
- audioPublication.track.setVolume(volume);
20495
- }
20607
+ enableCameraAndMicrophone() {
20608
+ return __awaiter(this, void 0, void 0, function* () {
20609
+ if (this.pendingPublishing.has(Track.Source.Camera) || this.pendingPublishing.has(Track.Source.Microphone)) {
20610
+ // no-op it's already been requested
20611
+ return;
20612
+ }
20613
+ this.pendingPublishing.add(Track.Source.Camera);
20614
+ this.pendingPublishing.add(Track.Source.Microphone);
20615
+ try {
20616
+ const tracks = yield this.createTracks({
20617
+ audio: true,
20618
+ video: true
20619
+ });
20620
+ yield Promise.all(tracks.map(track => this.publishTrack(track)));
20621
+ } finally {
20622
+ this.pendingPublishing.delete(Track.Source.Camera);
20623
+ this.pendingPublishing.delete(Track.Source.Microphone);
20624
+ }
20625
+ });
20496
20626
  }
20497
20627
  /**
20498
- * gets the volume on the participant's microphone track
20628
+ * Create local camera and/or microphone tracks
20629
+ * @param options
20630
+ * @returns
20499
20631
  */
20500
- getVolume() {
20501
- let source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Track.Source.Microphone;
20502
- const audioPublication = this.getTrack(source);
20503
- if (audioPublication && audioPublication.track) {
20504
- return audioPublication.track.getVolume();
20505
- }
20506
- return this.volumeMap.get(source);
20507
- }
20508
- /** @internal */
20509
- addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft) {
20510
- // find the track publication
20511
- // it's possible for the media track to arrive before participant info
20512
- let publication = this.getTrackPublication(sid);
20513
- // it's also possible that the browser didn't honor our original track id
20514
- // FireFox would use its own local uuid instead of server track id
20515
- if (!publication) {
20516
- if (!sid.startsWith('TR')) {
20517
- // find the first track that matches type
20518
- this.tracks.forEach(p => {
20519
- if (!publication && mediaTrack.kind === p.kind.toString()) {
20520
- publication = p;
20632
+ createTracks(options) {
20633
+ var _a, _b;
20634
+ return __awaiter(this, void 0, void 0, function* () {
20635
+ const opts = mergeDefaultOptions(options, (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.audioCaptureDefaults, (_b = this.roomOptions) === null || _b === void 0 ? void 0 : _b.videoCaptureDefaults);
20636
+ const constraints = constraintsForOptions(opts);
20637
+ let stream;
20638
+ try {
20639
+ stream = yield navigator.mediaDevices.getUserMedia(constraints);
20640
+ } catch (err) {
20641
+ if (err instanceof Error) {
20642
+ if (constraints.audio) {
20643
+ this.microphoneError = err;
20521
20644
  }
20522
- });
20645
+ if (constraints.video) {
20646
+ this.cameraError = err;
20647
+ }
20648
+ }
20649
+ throw err;
20523
20650
  }
20524
- }
20525
- // when we couldn't locate the track, it's possible that the metadata hasn't
20526
- // yet arrived. Wait a bit longer for it to arrive, or fire an error
20527
- if (!publication) {
20528
- if (triesLeft === 0) {
20529
- this.log.error('could not find published track', Object.assign(Object.assign({}, this.logContext), {
20530
- trackSid: sid
20531
- }));
20532
- this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
20533
- return;
20651
+ if (constraints.audio) {
20652
+ this.microphoneError = undefined;
20653
+ this.emit(ParticipantEvent.AudioStreamAcquired);
20534
20654
  }
20535
- if (triesLeft === undefined) triesLeft = 20;
20536
- setTimeout(() => {
20537
- this.addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft - 1);
20538
- }, 150);
20539
- return;
20540
- }
20541
- if (mediaTrack.readyState === 'ended') {
20542
- this.log.error('unable to subscribe because MediaStreamTrack is ended. Do not call MediaStreamTrack.stop()', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
20543
- this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
20544
- return;
20545
- }
20546
- const isVideo = mediaTrack.kind === 'video';
20547
- let track;
20548
- if (isVideo) {
20549
- track = new RemoteVideoTrack(mediaTrack, sid, receiver, adaptiveStreamSettings);
20550
- } else {
20551
- track = new RemoteAudioTrack(mediaTrack, sid, receiver, this.audioContext, this.audioOutput);
20552
- }
20553
- // set track info
20554
- track.source = publication.source;
20555
- // keep publication's muted status
20556
- track.isMuted = publication.isMuted;
20557
- track.setMediaStream(mediaStream);
20558
- track.start();
20559
- publication.setTrack(track);
20560
- // set participant volumes on new audio tracks
20561
- if (this.volumeMap.has(publication.source) && track instanceof RemoteAudioTrack) {
20562
- track.setVolume(this.volumeMap.get(publication.source));
20563
- }
20564
- return publication;
20565
- }
20566
- /** @internal */
20567
- get hasMetadata() {
20568
- return !!this.participantInfo;
20569
- }
20570
- getTrackPublication(sid) {
20571
- return this.tracks.get(sid);
20572
- }
20573
- /** @internal */
20574
- updateInfo(info) {
20575
- if (!super.updateInfo(info)) {
20576
- return false;
20577
- }
20578
- // we are getting a list of all available tracks, reconcile in here
20579
- // and send out events for changes
20580
- // reconcile track publications, publish events only if metadata is already there
20581
- // i.e. changes since the local participant has joined
20582
- const validTracks = new Map();
20583
- const newTracks = new Map();
20584
- info.tracks.forEach(ti => {
20585
- var _a, _b;
20586
- let publication = this.getTrackPublication(ti.sid);
20587
- if (!publication) {
20588
- // new publication
20589
- const kind = Track.kindFromProto(ti.type);
20590
- if (!kind) {
20591
- return;
20655
+ if (constraints.video) {
20656
+ this.cameraError = undefined;
20657
+ }
20658
+ return stream.getTracks().map(mediaStreamTrack => {
20659
+ const isAudio = mediaStreamTrack.kind === 'audio';
20660
+ isAudio ? options.audio : options.video;
20661
+ let trackConstraints;
20662
+ const conOrBool = isAudio ? constraints.audio : constraints.video;
20663
+ if (typeof conOrBool !== 'boolean') {
20664
+ trackConstraints = conOrBool;
20592
20665
  }
20593
- publication = new RemoteTrackPublication(kind, ti, (_a = this.signalClient.connectOptions) === null || _a === void 0 ? void 0 : _a.autoSubscribe, {
20594
- loggerContextCb: () => this.logContext,
20595
- loggerName: (_b = this.loggerOptions) === null || _b === void 0 ? void 0 : _b.loggerName
20596
- });
20597
- publication.updateInfo(ti);
20598
- newTracks.set(ti.sid, publication);
20599
- const existingTrackOfSource = Array.from(this.tracks.values()).find(publishedTrack => publishedTrack.source === (publication === null || publication === void 0 ? void 0 : publication.source));
20600
- if (existingTrackOfSource && publication.source !== Track.Source.Unknown) {
20601
- this.log.debug("received a second track publication for ".concat(this.identity, " with the same source: ").concat(publication.source), Object.assign(Object.assign({}, this.logContext), {
20602
- oldTrack: getLogContextFromTrack(existingTrackOfSource),
20603
- newTrack: getLogContextFromTrack(publication)
20604
- }));
20666
+ const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints, {
20667
+ loggerName: this.roomOptions.loggerName,
20668
+ loggerContextCb: () => this.logContext
20669
+ });
20670
+ if (track.kind === Track.Kind.Video) {
20671
+ track.source = Track.Source.Camera;
20672
+ } else if (track.kind === Track.Kind.Audio) {
20673
+ track.source = Track.Source.Microphone;
20605
20674
  }
20606
- this.addTrackPublication(publication);
20607
- } else {
20608
- publication.updateInfo(ti);
20609
- }
20610
- validTracks.set(ti.sid, publication);
20611
- });
20612
- // detect removed tracks
20613
- this.tracks.forEach(publication => {
20614
- if (!validTracks.has(publication.trackSid)) {
20615
- this.log.trace('detected removed track on remote participant, unpublishing', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
20616
- this.unpublishTrack(publication.trackSid, true);
20617
- }
20618
- });
20619
- // always emit events for new publications, Room will not forward them unless it's ready
20620
- newTracks.forEach(publication => {
20621
- this.emit(ParticipantEvent.TrackPublished, publication);
20675
+ track.mediaStream = stream;
20676
+ return track;
20677
+ });
20622
20678
  });
20623
- return true;
20624
- }
20625
- /** @internal */
20626
- unpublishTrack(sid, sendUnpublish) {
20627
- const publication = this.tracks.get(sid);
20628
- if (!publication) {
20629
- return;
20630
- }
20631
- // also send unsubscribe, if track is actively subscribed
20632
- const {
20633
- track
20634
- } = publication;
20635
- if (track) {
20636
- track.stop();
20637
- publication.setTrack(undefined);
20638
- }
20639
- // remove track from maps only after unsubscribed has been fired
20640
- this.tracks.delete(sid);
20641
- // remove from the right type map
20642
- switch (publication.kind) {
20643
- case Track.Kind.Audio:
20644
- this.audioTracks.delete(sid);
20645
- break;
20646
- case Track.Kind.Video:
20647
- this.videoTracks.delete(sid);
20648
- break;
20649
- }
20650
- if (sendUnpublish) {
20651
- this.emit(ParticipantEvent.TrackUnpublished, publication);
20652
- }
20653
20679
  }
20654
20680
  /**
20655
- * @internal
20681
+ * Creates a screen capture tracks with getDisplayMedia().
20682
+ * A LocalVideoTrack is always created and returned.
20683
+ * If { audio: true }, and the browser supports audio capture, a LocalAudioTrack is also created.
20656
20684
  */
20657
- setAudioOutput(output) {
20685
+ createScreenTracks(options) {
20658
20686
  return __awaiter(this, void 0, void 0, function* () {
20659
- this.audioOutput = output;
20660
- const promises = [];
20661
- this.audioTracks.forEach(pub => {
20662
- var _a;
20663
- if (pub.track instanceof RemoteAudioTrack) {
20664
- promises.push(pub.track.setSinkId((_a = output.deviceId) !== null && _a !== void 0 ? _a : 'default'));
20665
- }
20666
- });
20667
- yield Promise.all(promises);
20668
- });
20669
- }
20670
- /** @internal */
20671
- emit(event) {
20672
- for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
20673
- args[_key - 1] = arguments[_key];
20674
- }
20675
- this.log.trace('participant event', Object.assign(Object.assign({}, this.logContext), {
20676
- event,
20677
- args
20678
- }));
20679
- return super.emit(event, ...args);
20680
- }
20681
- }
20682
-
20683
- class LocalParticipant extends Participant {
20684
- /** @internal */
20685
- constructor(sid, identity, engine, options) {
20686
- super(sid, identity, undefined, undefined, {
20687
- loggerName: options.loggerName,
20688
- loggerContextCb: () => this.engine.logContext
20689
- });
20690
- this.pendingPublishing = new Set();
20691
- this.pendingPublishPromises = new Map();
20692
- this.participantTrackPermissions = [];
20693
- this.allParticipantsAllowedToSubscribe = true;
20694
- this.encryptionType = Encryption_Type.NONE;
20695
- this.handleReconnecting = () => {
20696
- if (!this.reconnectFuture) {
20697
- this.reconnectFuture = new Future();
20687
+ if (options === undefined) {
20688
+ options = {};
20698
20689
  }
20699
- };
20700
- this.handleReconnected = () => {
20701
- var _a, _b;
20702
- (_b = (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.resolve) === null || _b === void 0 ? void 0 : _b.call(_a);
20703
- this.reconnectFuture = undefined;
20704
- this.updateTrackSubscriptionPermissions();
20705
- };
20706
- this.handleDisconnected = () => {
20707
- var _a, _b;
20708
- if (this.reconnectFuture) {
20709
- this.reconnectFuture.promise.catch(e => this.log.warn(e.message, this.logContext));
20710
- (_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');
20711
- this.reconnectFuture = undefined;
20690
+ if (navigator.mediaDevices.getDisplayMedia === undefined) {
20691
+ throw new DeviceUnsupportedError('getDisplayMedia not supported');
20712
20692
  }
20713
- };
20714
- this.updateTrackSubscriptionPermissions = () => {
20715
- this.log.debug('updating track subscription permissions', Object.assign(Object.assign({}, this.logContext), {
20716
- allParticipantsAllowed: this.allParticipantsAllowedToSubscribe,
20717
- participantTrackPermissions: this.participantTrackPermissions
20718
- }));
20719
- this.engine.client.sendUpdateSubscriptionPermissions(this.allParticipantsAllowedToSubscribe, this.participantTrackPermissions.map(p => trackPermissionToProto(p)));
20720
- };
20721
- /** @internal */
20722
- this.onTrackUnmuted = track => {
20723
- this.onTrackMuted(track, track.isUpstreamPaused);
20724
- };
20725
- // when the local track changes in mute status, we'll notify server as such
20726
- /** @internal */
20727
- this.onTrackMuted = (track, muted) => {
20728
- if (muted === undefined) {
20729
- muted = true;
20693
+ if (options.resolution === undefined && !isSafari17()) {
20694
+ // we need to constrain the dimensions, otherwise it could lead to low bitrate
20695
+ // due to encoding a huge video. Encoding such large surfaces is really expensive
20696
+ // unfortunately Safari 17 has a but and cannot be constrained by default
20697
+ options.resolution = ScreenSharePresets.h1080fps30.resolution;
20730
20698
  }
20731
- if (!track.sid) {
20732
- this.log.error('could not update mute status for unpublished track', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20733
- return;
20699
+ const constraints = screenCaptureToDisplayMediaStreamOptions(options);
20700
+ const stream = yield navigator.mediaDevices.getDisplayMedia(constraints);
20701
+ const tracks = stream.getVideoTracks();
20702
+ if (tracks.length === 0) {
20703
+ throw new TrackInvalidError('no video track found');
20734
20704
  }
20735
- this.engine.updateMuteStatus(track.sid, muted);
20736
- };
20737
- this.onTrackUpstreamPaused = track => {
20738
- this.log.debug('upstream paused', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20739
- this.onTrackMuted(track, true);
20740
- };
20741
- this.onTrackUpstreamResumed = track => {
20742
- this.log.debug('upstream resumed', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20743
- this.onTrackMuted(track, track.isMuted);
20744
- };
20745
- this.handleSubscribedQualityUpdate = update => __awaiter(this, void 0, void 0, function* () {
20746
- var _a, e_1, _b, _c;
20747
- var _d, _e;
20748
- if (!((_d = this.roomOptions) === null || _d === void 0 ? void 0 : _d.dynacast)) {
20749
- return;
20705
+ const screenVideo = new LocalVideoTrack(tracks[0], undefined, false, {
20706
+ loggerName: this.roomOptions.loggerName,
20707
+ loggerContextCb: () => this.logContext
20708
+ });
20709
+ screenVideo.source = Track.Source.ScreenShare;
20710
+ if (options.contentHint) {
20711
+ screenVideo.mediaStreamTrack.contentHint = options.contentHint;
20750
20712
  }
20751
- const pub = this.videoTracks.get(update.trackSid);
20752
- if (!pub) {
20753
- this.log.warn('received subscribed quality update for unknown track', Object.assign(Object.assign({}, this.logContext), {
20754
- trackSid: update.trackSid
20755
- }));
20756
- return;
20713
+ const localTracks = [screenVideo];
20714
+ if (stream.getAudioTracks().length > 0) {
20715
+ this.emit(ParticipantEvent.AudioStreamAcquired);
20716
+ const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0], undefined, false, this.audioContext, {
20717
+ loggerName: this.roomOptions.loggerName,
20718
+ loggerContextCb: () => this.logContext
20719
+ });
20720
+ screenAudio.source = Track.Source.ScreenShareAudio;
20721
+ localTracks.push(screenAudio);
20757
20722
  }
20758
- if (update.subscribedCodecs.length > 0) {
20759
- if (!pub.videoTrack) {
20760
- return;
20723
+ return localTracks;
20724
+ });
20725
+ }
20726
+ /**
20727
+ * Publish a new track to the room
20728
+ * @param track
20729
+ * @param options
20730
+ */
20731
+ publishTrack(track, options) {
20732
+ var _a, _b, _c, _d;
20733
+ return __awaiter(this, void 0, void 0, function* () {
20734
+ yield (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.promise;
20735
+ if (track instanceof LocalTrack && this.pendingPublishPromises.has(track)) {
20736
+ yield this.pendingPublishPromises.get(track);
20737
+ }
20738
+ let defaultConstraints;
20739
+ if (track instanceof MediaStreamTrack) {
20740
+ defaultConstraints = track.getConstraints();
20741
+ } else {
20742
+ // we want to access constraints directly as `track.mediaStreamTrack`
20743
+ // might be pointing to a non-device track (e.g. processed track) already
20744
+ defaultConstraints = track.constraints;
20745
+ let deviceKind = undefined;
20746
+ switch (track.source) {
20747
+ case Track.Source.Microphone:
20748
+ deviceKind = 'audioinput';
20749
+ break;
20750
+ case Track.Source.Camera:
20751
+ deviceKind = 'videoinput';
20761
20752
  }
20762
- const newCodecs = yield pub.videoTrack.setPublishingCodecs(update.subscribedCodecs);
20763
- try {
20764
- for (var _f = true, newCodecs_1 = __asyncValues(newCodecs), newCodecs_1_1; newCodecs_1_1 = yield newCodecs_1.next(), _a = newCodecs_1_1.done, !_a; _f = true) {
20765
- _c = newCodecs_1_1.value;
20766
- _f = false;
20767
- const codec = _c;
20768
- if (isBackupCodec(codec)) {
20769
- this.log.debug("publish ".concat(codec, " for ").concat(pub.videoTrack.sid), Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(pub)));
20770
- yield this.publishAdditionalCodecForTrack(pub.videoTrack, codec, pub.options);
20771
- }
20772
- }
20773
- } catch (e_1_1) {
20774
- e_1 = {
20775
- error: e_1_1
20776
- };
20777
- } finally {
20778
- try {
20779
- if (!_f && !_a && (_b = newCodecs_1.return)) yield _b.call(newCodecs_1);
20780
- } finally {
20781
- if (e_1) throw e_1.error;
20782
- }
20783
- }
20784
- } else if (update.subscribedQualities.length > 0) {
20785
- yield (_e = pub.videoTrack) === null || _e === void 0 ? void 0 : _e.setPublishingLayers(update.subscribedQualities);
20786
- }
20787
- });
20788
- this.handleLocalTrackUnpublished = unpublished => {
20789
- const track = this.tracks.get(unpublished.trackSid);
20790
- if (!track) {
20791
- this.log.warn('received unpublished event for unknown track', Object.assign(Object.assign({}, this.logContext), {
20792
- trackSid: unpublished.trackSid
20793
- }));
20794
- return;
20795
- }
20796
- this.unpublishTrack(track.track);
20797
- };
20798
- this.handleTrackEnded = track => __awaiter(this, void 0, void 0, function* () {
20799
- if (track.source === Track.Source.ScreenShare || track.source === Track.Source.ScreenShareAudio) {
20800
- this.log.debug('unpublishing local track due to TrackEnded', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20801
- this.unpublishTrack(track);
20802
- } else if (track.isUserProvided) {
20803
- yield track.mute();
20804
- } else if (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) {
20805
- try {
20806
- if (isWeb()) {
20807
- try {
20808
- const currentPermissions = yield navigator === null || navigator === void 0 ? void 0 : navigator.permissions.query({
20809
- // the permission query for camera and microphone currently not supported in Safari and Firefox
20810
- // @ts-ignore
20811
- name: track.source === Track.Source.Camera ? 'camera' : 'microphone'
20812
- });
20813
- if (currentPermissions && currentPermissions.state === 'denied') {
20814
- this.log.warn("user has revoked access to ".concat(track.source), Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20815
- // detect granted change after permissions were denied to try and resume then
20816
- currentPermissions.onchange = () => {
20817
- if (currentPermissions.state !== 'denied') {
20818
- if (!track.isMuted) {
20819
- track.restartTrack();
20820
- }
20821
- currentPermissions.onchange = null;
20822
- }
20823
- };
20824
- throw new Error('GetUserMedia Permission denied');
20825
- }
20826
- } catch (e) {
20827
- // permissions query fails for firefox, we continue and try to restart the track
20828
- }
20829
- }
20830
- if (!track.isMuted) {
20831
- this.log.debug('track ended, attempting to use a different device', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20832
- yield track.restartTrack();
20833
- }
20834
- } catch (e) {
20835
- this.log.warn("could not restart track, muting instead", Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20836
- yield track.mute();
20837
- }
20838
- }
20839
- });
20840
- this.audioTracks = new Map();
20841
- this.videoTracks = new Map();
20842
- this.tracks = new Map();
20843
- this.engine = engine;
20844
- this.roomOptions = options;
20845
- this.setupEngine(engine);
20846
- this.activeDeviceMap = new Map();
20847
- }
20848
- get lastCameraError() {
20849
- return this.cameraError;
20850
- }
20851
- get lastMicrophoneError() {
20852
- return this.microphoneError;
20853
- }
20854
- get isE2EEEnabled() {
20855
- return this.encryptionType !== Encryption_Type.NONE;
20856
- }
20857
- getTrack(source) {
20858
- const track = super.getTrack(source);
20859
- if (track) {
20860
- return track;
20861
- }
20862
- }
20863
- getTrackByName(name) {
20864
- const track = super.getTrackByName(name);
20865
- if (track) {
20866
- return track;
20867
- }
20868
- }
20869
- /**
20870
- * @internal
20871
- */
20872
- setupEngine(engine) {
20873
- this.engine = engine;
20874
- this.engine.on(EngineEvent.RemoteMute, (trackSid, muted) => {
20875
- const pub = this.tracks.get(trackSid);
20876
- if (!pub || !pub.track) {
20877
- return;
20878
- }
20879
- if (muted) {
20880
- pub.mute();
20881
- } else {
20882
- pub.unmute();
20883
- }
20884
- });
20885
- 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);
20886
- }
20887
- /**
20888
- * Sets and updates the metadata of the local participant.
20889
- * The change does not take immediate effect.
20890
- * If successful, a `ParticipantEvent.MetadataChanged` event will be emitted on the local participant.
20891
- * Note: this requires `canUpdateOwnMetadata` permission.
20892
- * @param metadata
20893
- */
20894
- setMetadata(metadata) {
20895
- var _a;
20896
- this.engine.client.sendUpdateLocalMetadata(metadata, (_a = this.name) !== null && _a !== void 0 ? _a : '');
20897
- }
20898
- /**
20899
- * Sets and updates the name of the local participant.
20900
- * The change does not take immediate effect.
20901
- * If successful, a `ParticipantEvent.ParticipantNameChanged` event will be emitted on the local participant.
20902
- * Note: this requires `canUpdateOwnMetadata` permission.
20903
- * @param metadata
20904
- */
20905
- setName(name) {
20906
- var _a;
20907
- this.engine.client.sendUpdateLocalMetadata((_a = this.metadata) !== null && _a !== void 0 ? _a : '', name);
20908
- }
20909
- /**
20910
- * Enable or disable a participant's camera track.
20911
- *
20912
- * If a track has already published, it'll mute or unmute the track.
20913
- * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20914
- */
20915
- setCameraEnabled(enabled, options, publishOptions) {
20916
- return this.setTrackEnabled(Track.Source.Camera, enabled, options, publishOptions);
20917
- }
20918
- /**
20919
- * Enable or disable a participant's microphone track.
20920
- *
20921
- * If a track has already published, it'll mute or unmute the track.
20922
- * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20923
- */
20924
- setMicrophoneEnabled(enabled, options, publishOptions) {
20925
- return this.setTrackEnabled(Track.Source.Microphone, enabled, options, publishOptions);
20926
- }
20927
- /**
20928
- * Start or stop sharing a participant's screen
20929
- * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20930
- */
20931
- setScreenShareEnabled(enabled, options, publishOptions) {
20932
- return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options, publishOptions);
20933
- }
20934
- /** @internal */
20935
- setPermissions(permissions) {
20936
- const prevPermissions = this.permissions;
20937
- const changed = super.setPermissions(permissions);
20938
- if (changed && prevPermissions) {
20939
- this.emit(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions);
20940
- }
20941
- return changed;
20942
- }
20943
- /** @internal */
20944
- setE2EEEnabled(enabled) {
20945
- return __awaiter(this, void 0, void 0, function* () {
20946
- this.encryptionType = enabled ? Encryption_Type.GCM : Encryption_Type.NONE;
20947
- yield this.republishAllTracks(undefined, false);
20948
- });
20949
- }
20950
- setTrackEnabled(source, enabled, options, publishOptions) {
20951
- var _a, _b;
20952
- return __awaiter(this, void 0, void 0, function* () {
20953
- this.log.debug('setTrackEnabled', Object.assign(Object.assign({}, this.logContext), {
20954
- source,
20955
- enabled
20956
- }));
20957
- let track = this.getTrack(source);
20958
- if (enabled) {
20959
- if (track) {
20960
- yield track.unmute();
20961
- } else {
20962
- let localTracks;
20963
- if (this.pendingPublishing.has(source)) {
20964
- this.log.info('skipping duplicate published source', Object.assign(Object.assign({}, this.logContext), {
20965
- source
20966
- }));
20967
- // no-op it's already been requested
20968
- return;
20969
- }
20970
- this.pendingPublishing.add(source);
20971
- try {
20972
- switch (source) {
20973
- case Track.Source.Camera:
20974
- localTracks = yield this.createTracks({
20975
- video: (_a = options) !== null && _a !== void 0 ? _a : true
20976
- });
20977
- break;
20978
- case Track.Source.Microphone:
20979
- localTracks = yield this.createTracks({
20980
- audio: (_b = options) !== null && _b !== void 0 ? _b : true
20981
- });
20982
- break;
20983
- case Track.Source.ScreenShare:
20984
- localTracks = yield this.createScreenTracks(Object.assign({}, options));
20985
- break;
20986
- default:
20987
- throw new TrackInvalidError(source);
20988
- }
20989
- const publishPromises = [];
20990
- for (const localTrack of localTracks) {
20991
- this.log.info('publishing track', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(localTrack)));
20992
- publishPromises.push(this.publishTrack(localTrack, publishOptions));
20993
- }
20994
- const publishedTracks = yield Promise.all(publishPromises);
20995
- // for screen share publications including audio, this will only return the screen share publication, not the screen share audio one
20996
- // revisit if we want to return an array of tracks instead for v2
20997
- [track] = publishedTracks;
20998
- } catch (e) {
20999
- localTracks === null || localTracks === void 0 ? void 0 : localTracks.forEach(tr => {
21000
- tr.stop();
21001
- });
21002
- if (e instanceof Error && !(e instanceof TrackInvalidError)) {
21003
- this.emit(ParticipantEvent.MediaDevicesError, e);
21004
- }
21005
- throw e;
21006
- } finally {
21007
- this.pendingPublishing.delete(source);
21008
- }
21009
- }
21010
- } else if (track && track.track) {
21011
- // screenshare cannot be muted, unpublish instead
21012
- if (source === Track.Source.ScreenShare) {
21013
- track = yield this.unpublishTrack(track.track);
21014
- const screenAudioTrack = this.getTrack(Track.Source.ScreenShareAudio);
21015
- if (screenAudioTrack && screenAudioTrack.track) {
21016
- this.unpublishTrack(screenAudioTrack.track);
21017
- }
21018
- } else {
21019
- yield track.mute();
21020
- }
21021
- }
21022
- return track;
21023
- });
21024
- }
21025
- /**
21026
- * Publish both camera and microphone at the same time. This is useful for
21027
- * displaying a single Permission Dialog box to the end user.
21028
- */
21029
- enableCameraAndMicrophone() {
21030
- return __awaiter(this, void 0, void 0, function* () {
21031
- if (this.pendingPublishing.has(Track.Source.Camera) || this.pendingPublishing.has(Track.Source.Microphone)) {
21032
- // no-op it's already been requested
21033
- return;
21034
- }
21035
- this.pendingPublishing.add(Track.Source.Camera);
21036
- this.pendingPublishing.add(Track.Source.Microphone);
21037
- try {
21038
- const tracks = yield this.createTracks({
21039
- audio: true,
21040
- video: true
21041
- });
21042
- yield Promise.all(tracks.map(track => this.publishTrack(track)));
21043
- } finally {
21044
- this.pendingPublishing.delete(Track.Source.Camera);
21045
- this.pendingPublishing.delete(Track.Source.Microphone);
21046
- }
21047
- });
21048
- }
21049
- /**
21050
- * Create local camera and/or microphone tracks
21051
- * @param options
21052
- * @returns
21053
- */
21054
- createTracks(options) {
21055
- var _a, _b;
21056
- return __awaiter(this, void 0, void 0, function* () {
21057
- const opts = mergeDefaultOptions(options, (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.audioCaptureDefaults, (_b = this.roomOptions) === null || _b === void 0 ? void 0 : _b.videoCaptureDefaults);
21058
- const constraints = constraintsForOptions(opts);
21059
- let stream;
21060
- try {
21061
- stream = yield navigator.mediaDevices.getUserMedia(constraints);
21062
- } catch (err) {
21063
- if (err instanceof Error) {
21064
- if (constraints.audio) {
21065
- this.microphoneError = err;
21066
- }
21067
- if (constraints.video) {
21068
- this.cameraError = err;
21069
- }
21070
- }
21071
- throw err;
21072
- }
21073
- if (constraints.audio) {
21074
- this.microphoneError = undefined;
21075
- this.emit(ParticipantEvent.AudioStreamAcquired);
21076
- }
21077
- if (constraints.video) {
21078
- this.cameraError = undefined;
21079
- }
21080
- return stream.getTracks().map(mediaStreamTrack => {
21081
- const isAudio = mediaStreamTrack.kind === 'audio';
21082
- isAudio ? options.audio : options.video;
21083
- let trackConstraints;
21084
- const conOrBool = isAudio ? constraints.audio : constraints.video;
21085
- if (typeof conOrBool !== 'boolean') {
21086
- trackConstraints = conOrBool;
21087
- }
21088
- const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints, {
21089
- loggerName: this.roomOptions.loggerName,
21090
- loggerContextCb: () => this.logContext
21091
- });
21092
- if (track.kind === Track.Kind.Video) {
21093
- track.source = Track.Source.Camera;
21094
- } else if (track.kind === Track.Kind.Audio) {
21095
- track.source = Track.Source.Microphone;
21096
- }
21097
- track.mediaStream = stream;
21098
- return track;
21099
- });
21100
- });
21101
- }
21102
- /**
21103
- * Creates a screen capture tracks with getDisplayMedia().
21104
- * A LocalVideoTrack is always created and returned.
21105
- * If { audio: true }, and the browser supports audio capture, a LocalAudioTrack is also created.
21106
- */
21107
- createScreenTracks(options) {
21108
- return __awaiter(this, void 0, void 0, function* () {
21109
- if (options === undefined) {
21110
- options = {};
21111
- }
21112
- if (navigator.mediaDevices.getDisplayMedia === undefined) {
21113
- throw new DeviceUnsupportedError('getDisplayMedia not supported');
21114
- }
21115
- if (options.resolution === undefined && !isSafari17()) {
21116
- // we need to constrain the dimensions, otherwise it could lead to low bitrate
21117
- // due to encoding a huge video. Encoding such large surfaces is really expensive
21118
- // unfortunately Safari 17 has a but and cannot be constrained by default
21119
- options.resolution = ScreenSharePresets.h1080fps30.resolution;
21120
- }
21121
- const constraints = screenCaptureToDisplayMediaStreamOptions(options);
21122
- const stream = yield navigator.mediaDevices.getDisplayMedia(constraints);
21123
- const tracks = stream.getVideoTracks();
21124
- if (tracks.length === 0) {
21125
- throw new TrackInvalidError('no video track found');
21126
- }
21127
- const screenVideo = new LocalVideoTrack(tracks[0], undefined, false, {
21128
- loggerName: this.roomOptions.loggerName,
21129
- loggerContextCb: () => this.logContext
21130
- });
21131
- screenVideo.source = Track.Source.ScreenShare;
21132
- if (options.contentHint) {
21133
- screenVideo.mediaStreamTrack.contentHint = options.contentHint;
21134
- }
21135
- const localTracks = [screenVideo];
21136
- if (stream.getAudioTracks().length > 0) {
21137
- this.emit(ParticipantEvent.AudioStreamAcquired);
21138
- const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0], undefined, false, this.audioContext, {
21139
- loggerName: this.roomOptions.loggerName,
21140
- loggerContextCb: () => this.logContext
21141
- });
21142
- screenAudio.source = Track.Source.ScreenShareAudio;
21143
- localTracks.push(screenAudio);
21144
- }
21145
- return localTracks;
21146
- });
21147
- }
21148
- /**
21149
- * Publish a new track to the room
21150
- * @param track
21151
- * @param options
21152
- */
21153
- publishTrack(track, options) {
21154
- var _a, _b, _c, _d;
21155
- return __awaiter(this, void 0, void 0, function* () {
21156
- yield (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.promise;
21157
- if (track instanceof LocalTrack && this.pendingPublishPromises.has(track)) {
21158
- yield this.pendingPublishPromises.get(track);
21159
- }
21160
- let defaultConstraints;
21161
- if (track instanceof MediaStreamTrack) {
21162
- defaultConstraints = track.getConstraints();
21163
- } else {
21164
- // we want to access constraints directly as `track.mediaStreamTrack`
21165
- // might be pointing to a non-device track (e.g. processed track) already
21166
- defaultConstraints = track.constraints;
21167
- let deviceKind = undefined;
21168
- switch (track.source) {
21169
- case Track.Source.Microphone:
21170
- deviceKind = 'audioinput';
21171
- break;
21172
- case Track.Source.Camera:
21173
- deviceKind = 'videoinput';
21174
- }
21175
- if (deviceKind && this.activeDeviceMap.has(deviceKind)) {
21176
- defaultConstraints = Object.assign(Object.assign({}, defaultConstraints), {
21177
- deviceId: this.activeDeviceMap.get(deviceKind)
21178
- });
20753
+ if (deviceKind && this.activeDeviceMap.has(deviceKind)) {
20754
+ defaultConstraints = Object.assign(Object.assign({}, defaultConstraints), {
20755
+ deviceId: this.activeDeviceMap.get(deviceKind)
20756
+ });
21179
20757
  }
21180
20758
  }
21181
20759
  // convert raw media track into audio or video track
@@ -21207,7 +20785,7 @@ class LocalParticipant extends Participant {
21207
20785
  }
21208
20786
  // is it already published? if so skip
21209
20787
  let existingPublication;
21210
- this.tracks.forEach(publication => {
20788
+ this.trackPublications.forEach(publication => {
21211
20789
  if (!publication.track) {
21212
20790
  return;
21213
20791
  }
@@ -21259,9 +20837,9 @@ class LocalParticipant extends Participant {
21259
20837
  });
21260
20838
  }
21261
20839
  publish(track, opts, isStereo) {
21262
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
20840
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
21263
20841
  return __awaiter(this, void 0, void 0, function* () {
21264
- const existingTrackOfSource = Array.from(this.tracks.values()).find(publishedTrack => track instanceof LocalTrack && publishedTrack.source === track.source);
20842
+ const existingTrackOfSource = Array.from(this.trackPublications.values()).find(publishedTrack => track instanceof LocalTrack && publishedTrack.source === track.source);
21265
20843
  if (existingTrackOfSource && track.source !== Track.Source.Unknown) {
21266
20844
  this.log.info("publishing a second track with the same source: ".concat(track.source), Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21267
20845
  }
@@ -21292,438 +20870,933 @@ class LocalParticipant extends Participant {
21292
20870
  track.on(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
21293
20871
  // create track publication from track
21294
20872
  const req = new AddTrackRequest({
21295
- // get local track id for use during publishing
21296
- cid: track.mediaStreamTrack.id,
21297
- name: opts.name,
20873
+ // get local track id for use during publishing
20874
+ cid: track.mediaStreamTrack.id,
20875
+ name: opts.name,
20876
+ type: Track.kindToProto(track.kind),
20877
+ muted: track.isMuted,
20878
+ source: Track.sourceToProto(track.source),
20879
+ disableDtx: !((_a = opts.dtx) !== null && _a !== void 0 ? _a : true),
20880
+ encryption: this.encryptionType,
20881
+ stereo: isStereo,
20882
+ disableRed: this.isE2EEEnabled || !((_b = opts.red) !== null && _b !== void 0 ? _b : true),
20883
+ stream: opts === null || opts === void 0 ? void 0 : opts.stream
20884
+ });
20885
+ // compute encodings and layers for video
20886
+ let encodings;
20887
+ if (track.kind === Track.Kind.Video) {
20888
+ let dims = {
20889
+ width: 0,
20890
+ height: 0
20891
+ };
20892
+ try {
20893
+ dims = yield track.waitForDimensions();
20894
+ } catch (e) {
20895
+ // use defaults, it's quite painful for congestion control without simulcast
20896
+ // so using default dims according to publish settings
20897
+ const defaultRes = (_d = (_c = this.roomOptions.videoCaptureDefaults) === null || _c === void 0 ? void 0 : _c.resolution) !== null && _d !== void 0 ? _d : VideoPresets.h720.resolution;
20898
+ dims = {
20899
+ width: defaultRes.width,
20900
+ height: defaultRes.height
20901
+ };
20902
+ // log failure
20903
+ this.log.error('could not determine track dimensions, using defaults', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
20904
+ dims
20905
+ }));
20906
+ }
20907
+ // width and height should be defined for video
20908
+ req.width = dims.width;
20909
+ req.height = dims.height;
20910
+ // for svc codecs, disable simulcast and use vp8 for backup codec
20911
+ if (track instanceof LocalVideoTrack) {
20912
+ if (isSVCCodec(videoCodec)) {
20913
+ // vp9 svc with screenshare has problem to encode, always use L1T3 here
20914
+ if (track.source === Track.Source.ScreenShare && videoCodec === 'vp9') {
20915
+ opts.scalabilityMode = 'L1T3';
20916
+ }
20917
+ // set scalabilityMode to 'L3T3_KEY' by default
20918
+ opts.scalabilityMode = (_e = opts.scalabilityMode) !== null && _e !== void 0 ? _e : 'L3T3_KEY';
20919
+ }
20920
+ req.simulcastCodecs = [new SimulcastCodec({
20921
+ codec: videoCodec,
20922
+ cid: track.mediaStreamTrack.id
20923
+ })];
20924
+ // set up backup
20925
+ if (opts.backupCodec === true) {
20926
+ opts.backupCodec = {
20927
+ codec: defaultVideoCodec
20928
+ };
20929
+ }
20930
+ if (opts.backupCodec && videoCodec !== opts.backupCodec.codec &&
20931
+ // TODO remove this once e2ee is supported for backup codecs
20932
+ req.encryption === Encryption_Type.NONE) {
20933
+ // multi-codec simulcast requires dynacast
20934
+ if (!this.roomOptions.dynacast) {
20935
+ this.roomOptions.dynacast = true;
20936
+ }
20937
+ req.simulcastCodecs.push(new SimulcastCodec({
20938
+ codec: opts.backupCodec.codec,
20939
+ cid: ''
20940
+ }));
20941
+ }
20942
+ }
20943
+ encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts);
20944
+ req.layers = videoLayersFromEncodings(req.width, req.height, encodings, isSVCCodec(opts.videoCodec));
20945
+ } else if (track.kind === Track.Kind.Audio) {
20946
+ encodings = [{
20947
+ maxBitrate: (_f = opts.audioPreset) === null || _f === void 0 ? void 0 : _f.maxBitrate,
20948
+ priority: (_h = (_g = opts.audioPreset) === null || _g === void 0 ? void 0 : _g.priority) !== null && _h !== void 0 ? _h : 'high',
20949
+ networkPriority: (_k = (_j = opts.audioPreset) === null || _j === void 0 ? void 0 : _j.priority) !== null && _k !== void 0 ? _k : 'high'
20950
+ }];
20951
+ }
20952
+ if (!this.engine || this.engine.isClosed) {
20953
+ throw new UnexpectedConnectionState('cannot publish track when not connected');
20954
+ }
20955
+ const ti = yield this.engine.addTrack(req);
20956
+ // server might not support the codec the client has requested, in that case, fallback
20957
+ // to a supported codec
20958
+ let primaryCodecMime;
20959
+ ti.codecs.forEach(codec => {
20960
+ if (primaryCodecMime === undefined) {
20961
+ primaryCodecMime = codec.mimeType;
20962
+ }
20963
+ });
20964
+ if (primaryCodecMime && track.kind === Track.Kind.Video) {
20965
+ const updatedCodec = mimeTypeToVideoCodecString(primaryCodecMime);
20966
+ if (updatedCodec !== videoCodec) {
20967
+ this.log.debug('falling back to server selected codec', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
20968
+ codec: updatedCodec
20969
+ }));
20970
+ /* @ts-ignore */
20971
+ opts.videoCodec = updatedCodec;
20972
+ // recompute encodings since bitrates/etc could have changed
20973
+ encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts);
20974
+ }
20975
+ }
20976
+ const publication = new LocalTrackPublication(track.kind, ti, track, {
20977
+ loggerName: this.roomOptions.loggerName,
20978
+ loggerContextCb: () => this.logContext
20979
+ });
20980
+ // save options for when it needs to be republished again
20981
+ publication.options = opts;
20982
+ track.sid = ti.sid;
20983
+ if (!this.engine.pcManager) {
20984
+ throw new UnexpectedConnectionState('pcManager is not ready');
20985
+ }
20986
+ this.log.debug("publishing ".concat(track.kind, " with encodings"), Object.assign(Object.assign({}, this.logContext), {
20987
+ encodings,
20988
+ trackInfo: ti
20989
+ }));
20990
+ track.sender = yield this.engine.createSender(track, opts, encodings);
20991
+ if (encodings) {
20992
+ if (isFireFox() && track.kind === Track.Kind.Audio) {
20993
+ /* Refer to RFC https://datatracker.ietf.org/doc/html/rfc7587#section-6.1,
20994
+ livekit-server uses maxaveragebitrate=510000 in the answer sdp to permit client to
20995
+ publish high quality audio track. But firefox always uses this value as the actual
20996
+ bitrates, causing the audio bitrates to rise to 510Kbps in any stereo case unexpectedly.
20997
+ So the client need to modify maxaverragebitrates in answer sdp to user provided value to
20998
+ fix the issue.
20999
+ */
21000
+ let trackTransceiver = undefined;
21001
+ for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
21002
+ if (transceiver.sender === track.sender) {
21003
+ trackTransceiver = transceiver;
21004
+ break;
21005
+ }
21006
+ }
21007
+ if (trackTransceiver) {
21008
+ this.engine.pcManager.publisher.setTrackCodecBitrate({
21009
+ transceiver: trackTransceiver,
21010
+ codec: 'opus',
21011
+ maxbr: ((_l = encodings[0]) === null || _l === void 0 ? void 0 : _l.maxBitrate) ? encodings[0].maxBitrate / 1000 : 0
21012
+ });
21013
+ }
21014
+ } else if (track.codec && isSVCCodec(track.codec) && ((_m = encodings[0]) === null || _m === void 0 ? void 0 : _m.maxBitrate)) {
21015
+ this.engine.pcManager.publisher.setTrackCodecBitrate({
21016
+ cid: req.cid,
21017
+ codec: track.codec,
21018
+ maxbr: encodings[0].maxBitrate / 1000
21019
+ });
21020
+ }
21021
+ }
21022
+ yield this.engine.negotiate();
21023
+ if (track instanceof LocalVideoTrack) {
21024
+ track.startMonitor(this.engine.client);
21025
+ } else if (track instanceof LocalAudioTrack) {
21026
+ track.startMonitor();
21027
+ }
21028
+ this.addTrackPublication(publication);
21029
+ // send event for publication
21030
+ this.emit(ParticipantEvent.LocalTrackPublished, publication);
21031
+ return publication;
21032
+ });
21033
+ }
21034
+ get isLocal() {
21035
+ return true;
21036
+ }
21037
+ /** @internal
21038
+ * publish additional codec to existing track
21039
+ */
21040
+ publishAdditionalCodecForTrack(track, videoCodec, options) {
21041
+ var _a;
21042
+ return __awaiter(this, void 0, void 0, function* () {
21043
+ // TODO remove once e2ee is supported for backup tracks
21044
+ if (this.encryptionType !== Encryption_Type.NONE) {
21045
+ return;
21046
+ }
21047
+ // is it not published? if so skip
21048
+ let existingPublication;
21049
+ this.trackPublications.forEach(publication => {
21050
+ if (!publication.track) {
21051
+ return;
21052
+ }
21053
+ if (publication.track === track) {
21054
+ existingPublication = publication;
21055
+ }
21056
+ });
21057
+ if (!existingPublication) {
21058
+ throw new TrackInvalidError('track is not published');
21059
+ }
21060
+ if (!(track instanceof LocalVideoTrack)) {
21061
+ throw new TrackInvalidError('track is not a video track');
21062
+ }
21063
+ const opts = Object.assign(Object.assign({}, (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.publishDefaults), options);
21064
+ const encodings = computeTrackBackupEncodings(track, videoCodec, opts);
21065
+ if (!encodings) {
21066
+ this.log.info("backup codec has been disabled, ignoring request to add additional codec for track", Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21067
+ return;
21068
+ }
21069
+ const simulcastTrack = track.addSimulcastTrack(videoCodec, encodings);
21070
+ if (!simulcastTrack) {
21071
+ return;
21072
+ }
21073
+ const req = new AddTrackRequest({
21074
+ cid: simulcastTrack.mediaStreamTrack.id,
21298
21075
  type: Track.kindToProto(track.kind),
21299
21076
  muted: track.isMuted,
21300
21077
  source: Track.sourceToProto(track.source),
21301
- disableDtx: !((_a = opts.dtx) !== null && _a !== void 0 ? _a : true),
21302
- encryption: this.encryptionType,
21303
- stereo: isStereo,
21304
- disableRed: this.isE2EEEnabled || !((_b = opts.red) !== null && _b !== void 0 ? _b : true),
21305
- stream: opts === null || opts === void 0 ? void 0 : opts.stream
21078
+ sid: track.sid,
21079
+ simulcastCodecs: [{
21080
+ codec: opts.videoCodec,
21081
+ cid: simulcastTrack.mediaStreamTrack.id
21082
+ }]
21306
21083
  });
21307
- // compute encodings and layers for video
21308
- let encodings;
21309
- if (track.kind === Track.Kind.Video) {
21310
- let dims = {
21311
- width: 0,
21312
- height: 0
21313
- };
21084
+ req.layers = videoLayersFromEncodings(req.width, req.height, encodings);
21085
+ if (!this.engine || this.engine.isClosed) {
21086
+ throw new UnexpectedConnectionState('cannot publish track when not connected');
21087
+ }
21088
+ const ti = yield this.engine.addTrack(req);
21089
+ yield this.engine.createSimulcastSender(track, simulcastTrack, opts, encodings);
21090
+ yield this.engine.negotiate();
21091
+ this.log.debug("published ".concat(videoCodec, " for track ").concat(track.sid), Object.assign(Object.assign({}, this.logContext), {
21092
+ encodings,
21093
+ trackInfo: ti
21094
+ }));
21095
+ });
21096
+ }
21097
+ unpublishTrack(track, stopOnUnpublish) {
21098
+ var _a, _b;
21099
+ return __awaiter(this, void 0, void 0, function* () {
21100
+ // look through all published tracks to find the right ones
21101
+ const publication = this.getPublicationForTrack(track);
21102
+ const pubLogContext = publication ? getLogContextFromTrack(publication) : undefined;
21103
+ this.log.debug('unpublishing track', Object.assign(Object.assign({}, this.logContext), pubLogContext));
21104
+ if (!publication || !publication.track) {
21105
+ this.log.warn('track was not unpublished because no publication was found', Object.assign(Object.assign({}, this.logContext), pubLogContext));
21106
+ return undefined;
21107
+ }
21108
+ track = publication.track;
21109
+ track.off(TrackEvent.Muted, this.onTrackMuted);
21110
+ track.off(TrackEvent.Unmuted, this.onTrackUnmuted);
21111
+ track.off(TrackEvent.Ended, this.handleTrackEnded);
21112
+ track.off(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
21113
+ track.off(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
21114
+ if (stopOnUnpublish === undefined) {
21115
+ stopOnUnpublish = (_b = (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.stopLocalTrackOnUnpublish) !== null && _b !== void 0 ? _b : true;
21116
+ }
21117
+ if (stopOnUnpublish) {
21118
+ track.stop();
21119
+ }
21120
+ let negotiationNeeded = false;
21121
+ const trackSender = track.sender;
21122
+ track.sender = undefined;
21123
+ if (this.engine.pcManager && this.engine.pcManager.currentState < PCTransportState.FAILED && trackSender) {
21314
21124
  try {
21315
- dims = yield track.waitForDimensions();
21316
- } catch (e) {
21317
- // use defaults, it's quite painful for congestion control without simulcast
21318
- // so using default dims according to publish settings
21319
- const defaultRes = (_d = (_c = this.roomOptions.videoCaptureDefaults) === null || _c === void 0 ? void 0 : _c.resolution) !== null && _d !== void 0 ? _d : VideoPresets.h720.resolution;
21320
- dims = {
21321
- width: defaultRes.width,
21322
- height: defaultRes.height
21323
- };
21324
- // log failure
21325
- this.log.error('could not determine track dimensions, using defaults', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
21326
- dims
21327
- }));
21328
- }
21329
- // width and height should be defined for video
21330
- req.width = dims.width;
21331
- req.height = dims.height;
21332
- // for svc codecs, disable simulcast and use vp8 for backup codec
21333
- if (track instanceof LocalVideoTrack) {
21334
- if (isSVCCodec(videoCodec)) {
21335
- // vp9 svc with screenshare has problem to encode, always use L1T3 here
21336
- if (track.source === Track.Source.ScreenShare && videoCodec === 'vp9') {
21337
- opts.scalabilityMode = 'L1T3';
21125
+ for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
21126
+ // if sender is not currently sending (after replaceTrack(null))
21127
+ // removeTrack would have no effect.
21128
+ // to ensure we end up successfully removing the track, manually set
21129
+ // the transceiver to inactive
21130
+ if (transceiver.sender === trackSender) {
21131
+ transceiver.direction = 'inactive';
21132
+ negotiationNeeded = true;
21338
21133
  }
21339
- // set scalabilityMode to 'L3T3_KEY' by default
21340
- opts.scalabilityMode = (_e = opts.scalabilityMode) !== null && _e !== void 0 ? _e : 'L3T3_KEY';
21341
21134
  }
21342
- req.simulcastCodecs = [new SimulcastCodec({
21343
- codec: videoCodec,
21344
- cid: track.mediaStreamTrack.id
21345
- })];
21346
- // set up backup
21347
- if (opts.backupCodec === true) {
21348
- opts.backupCodec = {
21349
- codec: defaultVideoCodec
21350
- };
21135
+ if (this.engine.removeTrack(trackSender)) {
21136
+ negotiationNeeded = true;
21351
21137
  }
21352
- if (opts.backupCodec && videoCodec !== opts.backupCodec.codec &&
21353
- // TODO remove this once e2ee is supported for backup codecs
21354
- req.encryption === Encryption_Type.NONE) {
21355
- // multi-codec simulcast requires dynacast
21356
- if (!this.roomOptions.dynacast) {
21357
- this.roomOptions.dynacast = true;
21138
+ if (track instanceof LocalVideoTrack) {
21139
+ for (const [, trackInfo] of track.simulcastCodecs) {
21140
+ if (trackInfo.sender) {
21141
+ if (this.engine.removeTrack(trackInfo.sender)) {
21142
+ negotiationNeeded = true;
21143
+ }
21144
+ trackInfo.sender = undefined;
21145
+ }
21358
21146
  }
21359
- req.simulcastCodecs.push(new SimulcastCodec({
21360
- codec: opts.backupCodec.codec,
21361
- cid: ''
21362
- }));
21147
+ track.simulcastCodecs.clear();
21363
21148
  }
21149
+ } catch (e) {
21150
+ this.log.warn('failed to unpublish track', Object.assign(Object.assign(Object.assign({}, this.logContext), pubLogContext), {
21151
+ error: e
21152
+ }));
21364
21153
  }
21365
- encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts);
21366
- req.layers = videoLayersFromEncodings(req.width, req.height, encodings, isSVCCodec(opts.videoCodec));
21367
- } else if (track.kind === Track.Kind.Audio) {
21368
- encodings = [{
21369
- maxBitrate: (_g = (_f = opts.audioPreset) === null || _f === void 0 ? void 0 : _f.maxBitrate) !== null && _g !== void 0 ? _g : opts.audioBitrate,
21370
- priority: (_j = (_h = opts.audioPreset) === null || _h === void 0 ? void 0 : _h.priority) !== null && _j !== void 0 ? _j : 'high',
21371
- networkPriority: (_l = (_k = opts.audioPreset) === null || _k === void 0 ? void 0 : _k.priority) !== null && _l !== void 0 ? _l : 'high'
21372
- }];
21373
21154
  }
21374
- if (!this.engine || this.engine.isClosed) {
21375
- throw new UnexpectedConnectionState('cannot publish track when not connected');
21155
+ // remove from our maps
21156
+ this.trackPublications.delete(publication.trackSid);
21157
+ switch (publication.kind) {
21158
+ case Track.Kind.Audio:
21159
+ this.audioTrackPublications.delete(publication.trackSid);
21160
+ break;
21161
+ case Track.Kind.Video:
21162
+ this.videoTrackPublications.delete(publication.trackSid);
21163
+ break;
21376
21164
  }
21377
- const ti = yield this.engine.addTrack(req);
21378
- // server might not support the codec the client has requested, in that case, fallback
21379
- // to a supported codec
21380
- let primaryCodecMime;
21381
- ti.codecs.forEach(codec => {
21382
- if (primaryCodecMime === undefined) {
21383
- primaryCodecMime = codec.mimeType;
21165
+ this.emit(ParticipantEvent.LocalTrackUnpublished, publication);
21166
+ publication.setTrack(undefined);
21167
+ if (negotiationNeeded) {
21168
+ yield this.engine.negotiate();
21169
+ }
21170
+ return publication;
21171
+ });
21172
+ }
21173
+ unpublishTracks(tracks) {
21174
+ return __awaiter(this, void 0, void 0, function* () {
21175
+ const results = yield Promise.all(tracks.map(track => this.unpublishTrack(track)));
21176
+ return results.filter(track => track instanceof LocalTrackPublication);
21177
+ });
21178
+ }
21179
+ republishAllTracks(options) {
21180
+ let restartTracks = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
21181
+ return __awaiter(this, void 0, void 0, function* () {
21182
+ const localPubs = [];
21183
+ this.trackPublications.forEach(pub => {
21184
+ if (pub.track) {
21185
+ if (options) {
21186
+ pub.options = Object.assign(Object.assign({}, pub.options), options);
21187
+ }
21188
+ localPubs.push(pub);
21384
21189
  }
21385
21190
  });
21386
- if (primaryCodecMime && track.kind === Track.Kind.Video) {
21387
- const updatedCodec = mimeTypeToVideoCodecString(primaryCodecMime);
21388
- if (updatedCodec !== videoCodec) {
21389
- this.log.debug('falling back to server selected codec', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
21390
- codec: updatedCodec
21191
+ yield Promise.all(localPubs.map(pub => __awaiter(this, void 0, void 0, function* () {
21192
+ const track = pub.track;
21193
+ yield this.unpublishTrack(track, false);
21194
+ if (restartTracks && !track.isMuted && track.source !== Track.Source.ScreenShare && track.source !== Track.Source.ScreenShareAudio && (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) && !track.isUserProvided) {
21195
+ // generally we need to restart the track before publishing, often a full reconnect
21196
+ // is necessary because computer had gone to sleep.
21197
+ this.log.debug('restarting existing track', Object.assign(Object.assign({}, this.logContext), {
21198
+ track: pub.trackSid
21199
+ }));
21200
+ yield track.restartTrack();
21201
+ }
21202
+ yield this.publishTrack(track, pub.options);
21203
+ })));
21204
+ });
21205
+ }
21206
+ /**
21207
+ * Publish a new data payload to the room. Data will be forwarded to each
21208
+ * participant in the room if the destination field in publishOptions is empty
21209
+ *
21210
+ * @param data Uint8Array of the payload. To send string data, use TextEncoder.encode
21211
+ * @param options optionally specify a `reliable`, `topic` and `destination`
21212
+ */
21213
+ publishData(data) {
21214
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
21215
+ return __awaiter(this, void 0, void 0, function* () {
21216
+ const kind = options.reliable ? DataPacket_Kind.RELIABLE : DataPacket_Kind.LOSSY;
21217
+ const destinationIdentities = options.destinationIdentities;
21218
+ const topic = options.topic;
21219
+ const packet = new DataPacket({
21220
+ kind: kind,
21221
+ value: {
21222
+ case: 'user',
21223
+ value: new UserPacket({
21224
+ participantIdentity: this.identity,
21225
+ payload: data,
21226
+ destinationIdentities,
21227
+ topic
21228
+ })
21229
+ }
21230
+ });
21231
+ yield this.engine.sendDataPacket(packet, kind);
21232
+ });
21233
+ }
21234
+ /**
21235
+ * Control who can subscribe to LocalParticipant's published tracks.
21236
+ *
21237
+ * By default, all participants can subscribe. This allows fine-grained control over
21238
+ * who is able to subscribe at a participant and track level.
21239
+ *
21240
+ * Note: if access is given at a track-level (i.e. both [allParticipantsAllowed] and
21241
+ * [ParticipantTrackPermission.allTracksAllowed] are false), any newer published tracks
21242
+ * will not grant permissions to any participants and will require a subsequent
21243
+ * permissions update to allow subscription.
21244
+ *
21245
+ * @param allParticipantsAllowed Allows all participants to subscribe all tracks.
21246
+ * Takes precedence over [[participantTrackPermissions]] if set to true.
21247
+ * By default this is set to true.
21248
+ * @param participantTrackPermissions Full list of individual permissions per
21249
+ * participant/track. Any omitted participants will not receive any permissions.
21250
+ */
21251
+ setTrackSubscriptionPermissions(allParticipantsAllowed) {
21252
+ let participantTrackPermissions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
21253
+ this.participantTrackPermissions = participantTrackPermissions;
21254
+ this.allParticipantsAllowedToSubscribe = allParticipantsAllowed;
21255
+ if (!this.engine.client.isDisconnected) {
21256
+ this.updateTrackSubscriptionPermissions();
21257
+ }
21258
+ }
21259
+ /** @internal */
21260
+ updateInfo(info) {
21261
+ if (info.sid !== this.sid) {
21262
+ // drop updates that specify a wrong sid.
21263
+ // the sid for local participant is only explicitly set on join and full reconnect
21264
+ return false;
21265
+ }
21266
+ if (!super.updateInfo(info)) {
21267
+ return false;
21268
+ }
21269
+ // reconcile track mute status.
21270
+ // if server's track mute status doesn't match actual, we'll have to update
21271
+ // the server's copy
21272
+ info.tracks.forEach(ti => {
21273
+ var _a, _b;
21274
+ const pub = this.trackPublications.get(ti.sid);
21275
+ if (pub) {
21276
+ const mutedOnServer = pub.isMuted || ((_b = (_a = pub.track) === null || _a === void 0 ? void 0 : _a.isUpstreamPaused) !== null && _b !== void 0 ? _b : false);
21277
+ if (mutedOnServer !== ti.muted) {
21278
+ this.log.debug('updating server mute state after reconcile', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(pub)), {
21279
+ mutedOnServer
21391
21280
  }));
21392
- /* @ts-ignore */
21393
- opts.videoCodec = updatedCodec;
21394
- // recompute encodings since bitrates/etc could have changed
21395
- encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts);
21281
+ this.engine.client.sendMuteTrack(ti.sid, mutedOnServer);
21396
21282
  }
21397
21283
  }
21398
- const publication = new LocalTrackPublication(track.kind, ti, track, {
21399
- loggerName: this.roomOptions.loggerName,
21400
- loggerContextCb: () => this.logContext
21401
- });
21402
- // save options for when it needs to be republished again
21403
- publication.options = opts;
21404
- track.sid = ti.sid;
21405
- if (!this.engine.pcManager) {
21406
- throw new UnexpectedConnectionState('pcManager is not ready');
21284
+ });
21285
+ return true;
21286
+ }
21287
+ getPublicationForTrack(track) {
21288
+ let publication;
21289
+ this.trackPublications.forEach(pub => {
21290
+ const localTrack = pub.track;
21291
+ if (!localTrack) {
21292
+ return;
21407
21293
  }
21408
- this.log.debug("publishing ".concat(track.kind, " with encodings"), Object.assign(Object.assign({}, this.logContext), {
21409
- encodings,
21410
- trackInfo: ti
21411
- }));
21412
- track.sender = yield this.engine.createSender(track, opts, encodings);
21413
- if (encodings) {
21414
- if (isFireFox() && track.kind === Track.Kind.Audio) {
21415
- /* Refer to RFC https://datatracker.ietf.org/doc/html/rfc7587#section-6.1,
21416
- livekit-server uses maxaveragebitrate=510000 in the answer sdp to permit client to
21417
- publish high quality audio track. But firefox always uses this value as the actual
21418
- bitrates, causing the audio bitrates to rise to 510Kbps in any stereo case unexpectedly.
21419
- So the client need to modify maxaverragebitrates in answer sdp to user provided value to
21420
- fix the issue.
21421
- */
21422
- let trackTransceiver = undefined;
21423
- for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
21424
- if (transceiver.sender === track.sender) {
21425
- trackTransceiver = transceiver;
21426
- break;
21427
- }
21428
- }
21429
- if (trackTransceiver) {
21430
- this.engine.pcManager.publisher.setTrackCodecBitrate({
21431
- transceiver: trackTransceiver,
21432
- codec: 'opus',
21433
- maxbr: ((_m = encodings[0]) === null || _m === void 0 ? void 0 : _m.maxBitrate) ? encodings[0].maxBitrate / 1000 : 0
21434
- });
21294
+ // this looks overly complicated due to this object tree
21295
+ if (track instanceof MediaStreamTrack) {
21296
+ if (localTrack instanceof LocalAudioTrack || localTrack instanceof LocalVideoTrack) {
21297
+ if (localTrack.mediaStreamTrack === track) {
21298
+ publication = pub;
21435
21299
  }
21436
- } else if (track.codec && isSVCCodec(track.codec) && ((_o = encodings[0]) === null || _o === void 0 ? void 0 : _o.maxBitrate)) {
21437
- this.engine.pcManager.publisher.setTrackCodecBitrate({
21438
- cid: req.cid,
21439
- codec: track.codec,
21440
- maxbr: encodings[0].maxBitrate / 1000
21441
- });
21442
21300
  }
21301
+ } else if (track === localTrack) {
21302
+ publication = pub;
21443
21303
  }
21444
- yield this.engine.negotiate();
21445
- if (track instanceof LocalVideoTrack) {
21446
- track.startMonitor(this.engine.client);
21447
- } else if (track instanceof LocalAudioTrack) {
21448
- track.startMonitor();
21449
- }
21450
- this.addTrackPublication(publication);
21451
- // send event for publication
21452
- this.emit(ParticipantEvent.LocalTrackPublished, publication);
21453
- return publication;
21454
21304
  });
21305
+ return publication;
21306
+ }
21307
+ }
21308
+
21309
+ class RemoteTrackPublication extends TrackPublication {
21310
+ constructor(kind, ti, autoSubscribe, loggerOptions) {
21311
+ super(kind, ti.sid, ti.name, loggerOptions);
21312
+ this.track = undefined;
21313
+ /** @internal */
21314
+ this.allowed = true;
21315
+ this.disabled = false;
21316
+ this.currentVideoQuality = VideoQuality.HIGH;
21317
+ this.handleEnded = track => {
21318
+ this.setTrack(undefined);
21319
+ this.emit(TrackEvent.Ended, track);
21320
+ };
21321
+ this.handleVisibilityChange = visible => {
21322
+ this.log.debug("adaptivestream video visibility ".concat(this.trackSid, ", visible=").concat(visible), this.logContext);
21323
+ this.disabled = !visible;
21324
+ this.emitTrackUpdate();
21325
+ };
21326
+ this.handleVideoDimensionsChange = dimensions => {
21327
+ this.log.debug("adaptivestream video dimensions ".concat(dimensions.width, "x").concat(dimensions.height), this.logContext);
21328
+ this.videoDimensions = dimensions;
21329
+ this.emitTrackUpdate();
21330
+ };
21331
+ this.subscribed = autoSubscribe;
21332
+ this.updateInfo(ti);
21333
+ }
21334
+ /**
21335
+ * Subscribe or unsubscribe to this remote track
21336
+ * @param subscribed true to subscribe to a track, false to unsubscribe
21337
+ */
21338
+ setSubscribed(subscribed) {
21339
+ const prevStatus = this.subscriptionStatus;
21340
+ const prevPermission = this.permissionStatus;
21341
+ this.subscribed = subscribed;
21342
+ // reset allowed status when desired subscription state changes
21343
+ // server will notify client via signal message if it's not allowed
21344
+ if (subscribed) {
21345
+ this.allowed = true;
21346
+ }
21347
+ const sub = new UpdateSubscription({
21348
+ trackSids: [this.trackSid],
21349
+ subscribe: this.subscribed,
21350
+ participantTracks: [new ParticipantTracks({
21351
+ // sending an empty participant id since TrackPublication doesn't keep it
21352
+ // this is filled in by the participant that receives this message
21353
+ participantSid: '',
21354
+ trackSids: [this.trackSid]
21355
+ })]
21356
+ });
21357
+ this.emit(TrackEvent.UpdateSubscription, sub);
21358
+ this.emitSubscriptionUpdateIfChanged(prevStatus);
21359
+ this.emitPermissionUpdateIfChanged(prevPermission);
21360
+ }
21361
+ get subscriptionStatus() {
21362
+ if (this.subscribed === false) {
21363
+ return TrackPublication.SubscriptionStatus.Unsubscribed;
21364
+ }
21365
+ if (!super.isSubscribed) {
21366
+ return TrackPublication.SubscriptionStatus.Desired;
21367
+ }
21368
+ return TrackPublication.SubscriptionStatus.Subscribed;
21369
+ }
21370
+ get permissionStatus() {
21371
+ return this.allowed ? TrackPublication.PermissionStatus.Allowed : TrackPublication.PermissionStatus.NotAllowed;
21372
+ }
21373
+ /**
21374
+ * Returns true if track is subscribed, and ready for playback
21375
+ */
21376
+ get isSubscribed() {
21377
+ if (this.subscribed === false) {
21378
+ return false;
21379
+ }
21380
+ return super.isSubscribed;
21381
+ }
21382
+ // returns client's desire to subscribe to a track, also true if autoSubscribe is enabled
21383
+ get isDesired() {
21384
+ return this.subscribed !== false;
21385
+ }
21386
+ get isEnabled() {
21387
+ return !this.disabled;
21388
+ }
21389
+ /**
21390
+ * disable server from sending down data for this track. this is useful when
21391
+ * the participant is off screen, you may disable streaming down their video
21392
+ * to reduce bandwidth requirements
21393
+ * @param enabled
21394
+ */
21395
+ setEnabled(enabled) {
21396
+ if (!this.isManualOperationAllowed() || this.disabled === !enabled) {
21397
+ return;
21398
+ }
21399
+ this.disabled = !enabled;
21400
+ this.emitTrackUpdate();
21401
+ }
21402
+ /**
21403
+ * for tracks that support simulcasting, adjust subscribed quality
21404
+ *
21405
+ * This indicates the highest quality the client can accept. if network
21406
+ * bandwidth does not allow, server will automatically reduce quality to
21407
+ * optimize for uninterrupted video
21408
+ */
21409
+ setVideoQuality(quality) {
21410
+ if (!this.isManualOperationAllowed() || this.currentVideoQuality === quality) {
21411
+ return;
21412
+ }
21413
+ this.currentVideoQuality = quality;
21414
+ this.videoDimensions = undefined;
21415
+ this.emitTrackUpdate();
21416
+ }
21417
+ setVideoDimensions(dimensions) {
21418
+ var _a, _b;
21419
+ if (!this.isManualOperationAllowed()) {
21420
+ return;
21421
+ }
21422
+ if (((_a = this.videoDimensions) === null || _a === void 0 ? void 0 : _a.width) === dimensions.width && ((_b = this.videoDimensions) === null || _b === void 0 ? void 0 : _b.height) === dimensions.height) {
21423
+ return;
21424
+ }
21425
+ if (this.track instanceof RemoteVideoTrack) {
21426
+ this.videoDimensions = dimensions;
21427
+ }
21428
+ this.currentVideoQuality = undefined;
21429
+ this.emitTrackUpdate();
21430
+ }
21431
+ setVideoFPS(fps) {
21432
+ if (!this.isManualOperationAllowed()) {
21433
+ return;
21434
+ }
21435
+ if (!(this.track instanceof RemoteVideoTrack)) {
21436
+ return;
21437
+ }
21438
+ if (this.fps === fps) {
21439
+ return;
21440
+ }
21441
+ this.fps = fps;
21442
+ this.emitTrackUpdate();
21443
+ }
21444
+ get videoQuality() {
21445
+ return this.currentVideoQuality;
21446
+ }
21447
+ /** @internal */
21448
+ setTrack(track) {
21449
+ const prevStatus = this.subscriptionStatus;
21450
+ const prevPermission = this.permissionStatus;
21451
+ const prevTrack = this.track;
21452
+ if (prevTrack === track) {
21453
+ return;
21454
+ }
21455
+ if (prevTrack) {
21456
+ // unregister listener
21457
+ prevTrack.off(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
21458
+ prevTrack.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
21459
+ prevTrack.off(TrackEvent.Ended, this.handleEnded);
21460
+ prevTrack.detach();
21461
+ prevTrack.stopMonitor();
21462
+ this.emit(TrackEvent.Unsubscribed, prevTrack);
21463
+ }
21464
+ super.setTrack(track);
21465
+ if (track) {
21466
+ track.sid = this.trackSid;
21467
+ track.on(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
21468
+ track.on(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
21469
+ track.on(TrackEvent.Ended, this.handleEnded);
21470
+ this.emit(TrackEvent.Subscribed, track);
21471
+ }
21472
+ this.emitPermissionUpdateIfChanged(prevPermission);
21473
+ this.emitSubscriptionUpdateIfChanged(prevStatus);
21474
+ }
21475
+ /** @internal */
21476
+ setAllowed(allowed) {
21477
+ const prevStatus = this.subscriptionStatus;
21478
+ const prevPermission = this.permissionStatus;
21479
+ this.allowed = allowed;
21480
+ this.emitPermissionUpdateIfChanged(prevPermission);
21481
+ this.emitSubscriptionUpdateIfChanged(prevStatus);
21482
+ }
21483
+ /** @internal */
21484
+ setSubscriptionError(error) {
21485
+ this.emit(TrackEvent.SubscriptionFailed, error);
21486
+ }
21487
+ /** @internal */
21488
+ updateInfo(info) {
21489
+ super.updateInfo(info);
21490
+ const prevMetadataMuted = this.metadataMuted;
21491
+ this.metadataMuted = info.muted;
21492
+ if (this.track) {
21493
+ this.track.setMuted(info.muted);
21494
+ } else if (prevMetadataMuted !== info.muted) {
21495
+ this.emit(info.muted ? TrackEvent.Muted : TrackEvent.Unmuted);
21496
+ }
21455
21497
  }
21456
- get isLocal() {
21498
+ emitSubscriptionUpdateIfChanged(previousStatus) {
21499
+ const currentStatus = this.subscriptionStatus;
21500
+ if (previousStatus === currentStatus) {
21501
+ return;
21502
+ }
21503
+ this.emit(TrackEvent.SubscriptionStatusChanged, currentStatus, previousStatus);
21504
+ }
21505
+ emitPermissionUpdateIfChanged(previousPermissionStatus) {
21506
+ const currentPermissionStatus = this.permissionStatus;
21507
+ if (currentPermissionStatus !== previousPermissionStatus) {
21508
+ this.emit(TrackEvent.SubscriptionPermissionChanged, this.permissionStatus, previousPermissionStatus);
21509
+ }
21510
+ }
21511
+ isManualOperationAllowed() {
21512
+ if (this.kind === Track.Kind.Video && this.isAdaptiveStream) {
21513
+ this.log.warn('adaptive stream is enabled, cannot change video track settings', this.logContext);
21514
+ return false;
21515
+ }
21516
+ if (!this.isDesired) {
21517
+ this.log.warn('cannot update track settings when not subscribed', this.logContext);
21518
+ return false;
21519
+ }
21457
21520
  return true;
21458
21521
  }
21459
- /** @internal
21460
- * publish additional codec to existing track
21461
- */
21462
- publishAdditionalCodecForTrack(track, videoCodec, options) {
21463
- var _a;
21464
- return __awaiter(this, void 0, void 0, function* () {
21465
- // TODO remove once e2ee is supported for backup tracks
21466
- if (this.encryptionType !== Encryption_Type.NONE) {
21467
- return;
21468
- }
21469
- // is it not published? if so skip
21470
- let existingPublication;
21471
- this.tracks.forEach(publication => {
21472
- if (!publication.track) {
21473
- return;
21474
- }
21475
- if (publication.track === track) {
21476
- existingPublication = publication;
21477
- }
21478
- });
21479
- if (!existingPublication) {
21480
- throw new TrackInvalidError('track is not published');
21481
- }
21482
- if (!(track instanceof LocalVideoTrack)) {
21483
- throw new TrackInvalidError('track is not a video track');
21484
- }
21485
- const opts = Object.assign(Object.assign({}, (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.publishDefaults), options);
21486
- const encodings = computeTrackBackupEncodings(track, videoCodec, opts);
21487
- if (!encodings) {
21488
- this.log.info("backup codec has been disabled, ignoring request to add additional codec for track", Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21489
- return;
21490
- }
21491
- const simulcastTrack = track.addSimulcastTrack(videoCodec, encodings);
21492
- const req = new AddTrackRequest({
21493
- cid: simulcastTrack.mediaStreamTrack.id,
21494
- type: Track.kindToProto(track.kind),
21495
- muted: track.isMuted,
21496
- source: Track.sourceToProto(track.source),
21497
- sid: track.sid,
21498
- simulcastCodecs: [{
21499
- codec: opts.videoCodec,
21500
- cid: simulcastTrack.mediaStreamTrack.id
21501
- }]
21502
- });
21503
- req.layers = videoLayersFromEncodings(req.width, req.height, encodings);
21504
- if (!this.engine || this.engine.isClosed) {
21505
- throw new UnexpectedConnectionState('cannot publish track when not connected');
21506
- }
21507
- const ti = yield this.engine.addTrack(req);
21508
- yield this.engine.createSimulcastSender(track, simulcastTrack, opts, encodings);
21509
- yield this.engine.negotiate();
21510
- this.log.debug("published ".concat(videoCodec, " for track ").concat(track.sid), Object.assign(Object.assign({}, this.logContext), {
21511
- encodings,
21512
- trackInfo: ti
21513
- }));
21514
- });
21522
+ get isAdaptiveStream() {
21523
+ return this.track instanceof RemoteVideoTrack && this.track.isAdaptiveStream;
21515
21524
  }
21516
- unpublishTrack(track, stopOnUnpublish) {
21517
- var _a, _b;
21518
- return __awaiter(this, void 0, void 0, function* () {
21519
- // look through all published tracks to find the right ones
21520
- const publication = this.getPublicationForTrack(track);
21521
- const pubLogContext = publication ? getLogContextFromTrack(publication) : undefined;
21522
- this.log.debug('unpublishing track', Object.assign(Object.assign({}, this.logContext), pubLogContext));
21523
- if (!publication || !publication.track) {
21524
- this.log.warn('track was not unpublished because no publication was found', Object.assign(Object.assign({}, this.logContext), pubLogContext));
21525
- return undefined;
21526
- }
21527
- track = publication.track;
21528
- track.off(TrackEvent.Muted, this.onTrackMuted);
21529
- track.off(TrackEvent.Unmuted, this.onTrackUnmuted);
21530
- track.off(TrackEvent.Ended, this.handleTrackEnded);
21531
- track.off(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
21532
- track.off(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
21533
- if (stopOnUnpublish === undefined) {
21534
- stopOnUnpublish = (_b = (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.stopLocalTrackOnUnpublish) !== null && _b !== void 0 ? _b : true;
21535
- }
21536
- if (stopOnUnpublish) {
21537
- track.stop();
21538
- }
21539
- let negotiationNeeded = false;
21540
- const trackSender = track.sender;
21541
- track.sender = undefined;
21542
- if (this.engine.pcManager && this.engine.pcManager.currentState < PCTransportState.FAILED && trackSender) {
21543
- try {
21544
- for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
21545
- // if sender is not currently sending (after replaceTrack(null))
21546
- // removeTrack would have no effect.
21547
- // to ensure we end up successfully removing the track, manually set
21548
- // the transceiver to inactive
21549
- if (transceiver.sender === trackSender) {
21550
- transceiver.direction = 'inactive';
21551
- negotiationNeeded = true;
21552
- }
21553
- }
21554
- if (this.engine.removeTrack(trackSender)) {
21555
- negotiationNeeded = true;
21556
- }
21557
- if (track instanceof LocalVideoTrack) {
21558
- for (const [, trackInfo] of track.simulcastCodecs) {
21559
- if (trackInfo.sender) {
21560
- if (this.engine.removeTrack(trackInfo.sender)) {
21561
- negotiationNeeded = true;
21562
- }
21563
- trackInfo.sender = undefined;
21564
- }
21565
- }
21566
- track.simulcastCodecs.clear();
21567
- }
21568
- } catch (e) {
21569
- this.log.warn('failed to unpublish track', Object.assign(Object.assign(Object.assign({}, this.logContext), pubLogContext), {
21570
- error: e
21571
- }));
21572
- }
21573
- }
21574
- // remove from our maps
21575
- this.tracks.delete(publication.trackSid);
21576
- switch (publication.kind) {
21577
- case Track.Kind.Audio:
21578
- this.audioTracks.delete(publication.trackSid);
21579
- break;
21580
- case Track.Kind.Video:
21581
- this.videoTracks.delete(publication.trackSid);
21582
- break;
21583
- }
21584
- this.emit(ParticipantEvent.LocalTrackUnpublished, publication);
21585
- publication.setTrack(undefined);
21586
- if (negotiationNeeded) {
21587
- yield this.engine.negotiate();
21588
- }
21589
- return publication;
21525
+ /* @internal */
21526
+ emitTrackUpdate() {
21527
+ const settings = new UpdateTrackSettings({
21528
+ trackSids: [this.trackSid],
21529
+ disabled: this.disabled,
21530
+ fps: this.fps
21590
21531
  });
21532
+ if (this.videoDimensions) {
21533
+ settings.width = Math.ceil(this.videoDimensions.width);
21534
+ settings.height = Math.ceil(this.videoDimensions.height);
21535
+ } else if (this.currentVideoQuality !== undefined) {
21536
+ settings.quality = this.currentVideoQuality;
21537
+ } else {
21538
+ // defaults to high quality
21539
+ settings.quality = VideoQuality.HIGH;
21540
+ }
21541
+ this.emit(TrackEvent.UpdateSettings, settings);
21591
21542
  }
21592
- unpublishTracks(tracks) {
21593
- return __awaiter(this, void 0, void 0, function* () {
21594
- const results = yield Promise.all(tracks.map(track => this.unpublishTrack(track)));
21595
- return results.filter(track => track instanceof LocalTrackPublication);
21596
- });
21543
+ }
21544
+
21545
+ class RemoteParticipant extends Participant {
21546
+ /** @internal */
21547
+ static fromParticipantInfo(signalClient, pi) {
21548
+ return new RemoteParticipant(signalClient, pi.sid, pi.identity, pi.name, pi.metadata);
21597
21549
  }
21598
- republishAllTracks(options) {
21599
- let restartTracks = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
21600
- return __awaiter(this, void 0, void 0, function* () {
21601
- const localPubs = [];
21602
- this.tracks.forEach(pub => {
21603
- if (pub.track) {
21604
- if (options) {
21605
- pub.options = Object.assign(Object.assign({}, pub.options), options);
21606
- }
21607
- localPubs.push(pub);
21608
- }
21550
+ /** @internal */
21551
+ constructor(signalClient, sid, identity, name, metadata, loggerOptions) {
21552
+ super(sid, identity || '', name, metadata, loggerOptions);
21553
+ this.signalClient = signalClient;
21554
+ this.trackPublications = new Map();
21555
+ this.audioTrackPublications = new Map();
21556
+ this.videoTrackPublications = new Map();
21557
+ this.volumeMap = new Map();
21558
+ }
21559
+ addTrackPublication(publication) {
21560
+ super.addTrackPublication(publication);
21561
+ // register action events
21562
+ publication.on(TrackEvent.UpdateSettings, settings => {
21563
+ this.log.debug('send update settings', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
21564
+ this.signalClient.sendUpdateTrackSettings(settings);
21565
+ });
21566
+ publication.on(TrackEvent.UpdateSubscription, sub => {
21567
+ sub.participantTracks.forEach(pt => {
21568
+ pt.participantSid = this.sid;
21609
21569
  });
21610
- yield Promise.all(localPubs.map(pub => __awaiter(this, void 0, void 0, function* () {
21611
- const track = pub.track;
21612
- yield this.unpublishTrack(track, false);
21613
- if (restartTracks && !track.isMuted && track.source !== Track.Source.ScreenShare && track.source !== Track.Source.ScreenShareAudio && (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) && !track.isUserProvided) {
21614
- // generally we need to restart the track before publishing, often a full reconnect
21615
- // is necessary because computer had gone to sleep.
21616
- this.log.debug('restarting existing track', Object.assign(Object.assign({}, this.logContext), {
21617
- track: pub.trackSid
21618
- }));
21619
- yield track.restartTrack();
21620
- }
21621
- yield this.publishTrack(track, pub.options);
21622
- })));
21570
+ this.signalClient.sendUpdateSubscription(sub);
21571
+ });
21572
+ publication.on(TrackEvent.SubscriptionPermissionChanged, status => {
21573
+ this.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, publication, status);
21574
+ });
21575
+ publication.on(TrackEvent.SubscriptionStatusChanged, status => {
21576
+ this.emit(ParticipantEvent.TrackSubscriptionStatusChanged, publication, status);
21577
+ });
21578
+ publication.on(TrackEvent.Subscribed, track => {
21579
+ this.emit(ParticipantEvent.TrackSubscribed, track, publication);
21580
+ });
21581
+ publication.on(TrackEvent.Unsubscribed, previousTrack => {
21582
+ this.emit(ParticipantEvent.TrackUnsubscribed, previousTrack, publication);
21583
+ });
21584
+ publication.on(TrackEvent.SubscriptionFailed, error => {
21585
+ this.emit(ParticipantEvent.TrackSubscriptionFailed, publication.trackSid, error);
21623
21586
  });
21624
21587
  }
21625
- publishData(data, kind) {
21626
- let publishOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
21627
- return __awaiter(this, void 0, void 0, function* () {
21628
- const destination = Array.isArray(publishOptions) ? publishOptions : publishOptions === null || publishOptions === void 0 ? void 0 : publishOptions.destination;
21629
- const destinationSids = [];
21630
- const topic = !Array.isArray(publishOptions) ? publishOptions.topic : undefined;
21631
- if (destination !== undefined) {
21632
- destination.forEach(val => {
21633
- if (val instanceof RemoteParticipant) {
21634
- destinationSids.push(val.sid);
21635
- } else {
21636
- destinationSids.push(val);
21588
+ getTrackPublication(source) {
21589
+ const track = super.getTrackPublication(source);
21590
+ if (track) {
21591
+ return track;
21592
+ }
21593
+ }
21594
+ getTrackPublicationByName(name) {
21595
+ const track = super.getTrackPublicationByName(name);
21596
+ if (track) {
21597
+ return track;
21598
+ }
21599
+ }
21600
+ /**
21601
+ * sets the volume on the participant's audio track
21602
+ * by default, this affects the microphone publication
21603
+ * a different source can be passed in as a second argument
21604
+ * if no track exists the volume will be applied when the microphone track is added
21605
+ */
21606
+ setVolume(volume) {
21607
+ let source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Track.Source.Microphone;
21608
+ this.volumeMap.set(source, volume);
21609
+ const audioPublication = this.getTrackPublication(source);
21610
+ if (audioPublication && audioPublication.track) {
21611
+ audioPublication.track.setVolume(volume);
21612
+ }
21613
+ }
21614
+ /**
21615
+ * gets the volume on the participant's microphone track
21616
+ */
21617
+ getVolume() {
21618
+ let source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Track.Source.Microphone;
21619
+ const audioPublication = this.getTrackPublication(source);
21620
+ if (audioPublication && audioPublication.track) {
21621
+ return audioPublication.track.getVolume();
21622
+ }
21623
+ return this.volumeMap.get(source);
21624
+ }
21625
+ /** @internal */
21626
+ addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft) {
21627
+ // find the track publication
21628
+ // it's possible for the media track to arrive before participant info
21629
+ let publication = this.getTrackPublicationBySid(sid);
21630
+ // it's also possible that the browser didn't honor our original track id
21631
+ // FireFox would use its own local uuid instead of server track id
21632
+ if (!publication) {
21633
+ if (!sid.startsWith('TR')) {
21634
+ // find the first track that matches type
21635
+ this.trackPublications.forEach(p => {
21636
+ if (!publication && mediaTrack.kind === p.kind.toString()) {
21637
+ publication = p;
21637
21638
  }
21638
21639
  });
21639
21640
  }
21640
- const packet = new DataPacket({
21641
- kind,
21642
- value: {
21643
- case: 'user',
21644
- value: new UserPacket({
21645
- participantSid: this.sid,
21646
- payload: data,
21647
- destinationSids: destinationSids,
21648
- topic
21649
- })
21650
- }
21651
- });
21652
- yield this.engine.sendDataPacket(packet, kind);
21653
- });
21641
+ }
21642
+ // when we couldn't locate the track, it's possible that the metadata hasn't
21643
+ // yet arrived. Wait a bit longer for it to arrive, or fire an error
21644
+ if (!publication) {
21645
+ if (triesLeft === 0) {
21646
+ this.log.error('could not find published track', Object.assign(Object.assign({}, this.logContext), {
21647
+ trackSid: sid
21648
+ }));
21649
+ this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
21650
+ return;
21651
+ }
21652
+ if (triesLeft === undefined) triesLeft = 20;
21653
+ setTimeout(() => {
21654
+ this.addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft - 1);
21655
+ }, 150);
21656
+ return;
21657
+ }
21658
+ if (mediaTrack.readyState === 'ended') {
21659
+ this.log.error('unable to subscribe because MediaStreamTrack is ended. Do not call MediaStreamTrack.stop()', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
21660
+ this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
21661
+ return;
21662
+ }
21663
+ const isVideo = mediaTrack.kind === 'video';
21664
+ let track;
21665
+ if (isVideo) {
21666
+ track = new RemoteVideoTrack(mediaTrack, sid, receiver, adaptiveStreamSettings);
21667
+ } else {
21668
+ track = new RemoteAudioTrack(mediaTrack, sid, receiver, this.audioContext, this.audioOutput);
21669
+ }
21670
+ // set track info
21671
+ track.source = publication.source;
21672
+ // keep publication's muted status
21673
+ track.isMuted = publication.isMuted;
21674
+ track.setMediaStream(mediaStream);
21675
+ track.start();
21676
+ publication.setTrack(track);
21677
+ // set participant volumes on new audio tracks
21678
+ if (this.volumeMap.has(publication.source) && track instanceof RemoteAudioTrack) {
21679
+ track.setVolume(this.volumeMap.get(publication.source));
21680
+ }
21681
+ return publication;
21682
+ }
21683
+ /** @internal */
21684
+ get hasMetadata() {
21685
+ return !!this.participantInfo;
21654
21686
  }
21655
21687
  /**
21656
- * Control who can subscribe to LocalParticipant's published tracks.
21657
- *
21658
- * By default, all participants can subscribe. This allows fine-grained control over
21659
- * who is able to subscribe at a participant and track level.
21660
- *
21661
- * Note: if access is given at a track-level (i.e. both [allParticipantsAllowed] and
21662
- * [ParticipantTrackPermission.allTracksAllowed] are false), any newer published tracks
21663
- * will not grant permissions to any participants and will require a subsequent
21664
- * permissions update to allow subscription.
21665
- *
21666
- * @param allParticipantsAllowed Allows all participants to subscribe all tracks.
21667
- * Takes precedence over [[participantTrackPermissions]] if set to true.
21668
- * By default this is set to true.
21669
- * @param participantTrackPermissions Full list of individual permissions per
21670
- * participant/track. Any omitted participants will not receive any permissions.
21688
+ * @internal
21671
21689
  */
21672
- setTrackSubscriptionPermissions(allParticipantsAllowed) {
21673
- let participantTrackPermissions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
21674
- this.participantTrackPermissions = participantTrackPermissions;
21675
- this.allParticipantsAllowedToSubscribe = allParticipantsAllowed;
21676
- if (!this.engine.client.isDisconnected) {
21677
- this.updateTrackSubscriptionPermissions();
21678
- }
21690
+ getTrackPublicationBySid(sid) {
21691
+ return this.trackPublications.get(sid);
21679
21692
  }
21680
21693
  /** @internal */
21681
21694
  updateInfo(info) {
21682
- if (info.sid !== this.sid) {
21683
- // drop updates that specify a wrong sid.
21684
- // the sid for local participant is only explicitly set on join and full reconnect
21685
- return false;
21686
- }
21687
21695
  if (!super.updateInfo(info)) {
21688
21696
  return false;
21689
21697
  }
21690
- // reconcile track mute status.
21691
- // if server's track mute status doesn't match actual, we'll have to update
21692
- // the server's copy
21698
+ // we are getting a list of all available tracks, reconcile in here
21699
+ // and send out events for changes
21700
+ // reconcile track publications, publish events only if metadata is already there
21701
+ // i.e. changes since the local participant has joined
21702
+ const validTracks = new Map();
21703
+ const newTracks = new Map();
21693
21704
  info.tracks.forEach(ti => {
21694
21705
  var _a, _b;
21695
- const pub = this.tracks.get(ti.sid);
21696
- if (pub) {
21697
- const mutedOnServer = pub.isMuted || ((_b = (_a = pub.track) === null || _a === void 0 ? void 0 : _a.isUpstreamPaused) !== null && _b !== void 0 ? _b : false);
21698
- if (mutedOnServer !== ti.muted) {
21699
- this.log.debug('updating server mute state after reconcile', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(pub)), {
21700
- mutedOnServer
21706
+ let publication = this.getTrackPublicationBySid(ti.sid);
21707
+ if (!publication) {
21708
+ // new publication
21709
+ const kind = Track.kindFromProto(ti.type);
21710
+ if (!kind) {
21711
+ return;
21712
+ }
21713
+ publication = new RemoteTrackPublication(kind, ti, (_a = this.signalClient.connectOptions) === null || _a === void 0 ? void 0 : _a.autoSubscribe, {
21714
+ loggerContextCb: () => this.logContext,
21715
+ loggerName: (_b = this.loggerOptions) === null || _b === void 0 ? void 0 : _b.loggerName
21716
+ });
21717
+ publication.updateInfo(ti);
21718
+ newTracks.set(ti.sid, publication);
21719
+ const existingTrackOfSource = Array.from(this.trackPublications.values()).find(publishedTrack => publishedTrack.source === (publication === null || publication === void 0 ? void 0 : publication.source));
21720
+ if (existingTrackOfSource && publication.source !== Track.Source.Unknown) {
21721
+ this.log.debug("received a second track publication for ".concat(this.identity, " with the same source: ").concat(publication.source), Object.assign(Object.assign({}, this.logContext), {
21722
+ oldTrack: getLogContextFromTrack(existingTrackOfSource),
21723
+ newTrack: getLogContextFromTrack(publication)
21701
21724
  }));
21702
- this.engine.client.sendMuteTrack(ti.sid, mutedOnServer);
21703
21725
  }
21726
+ this.addTrackPublication(publication);
21727
+ } else {
21728
+ publication.updateInfo(ti);
21729
+ }
21730
+ validTracks.set(ti.sid, publication);
21731
+ });
21732
+ // detect removed tracks
21733
+ this.trackPublications.forEach(publication => {
21734
+ if (!validTracks.has(publication.trackSid)) {
21735
+ this.log.trace('detected removed track on remote participant, unpublishing', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
21736
+ this.unpublishTrack(publication.trackSid, true);
21704
21737
  }
21705
21738
  });
21739
+ // always emit events for new publications, Room will not forward them unless it's ready
21740
+ newTracks.forEach(publication => {
21741
+ this.emit(ParticipantEvent.TrackPublished, publication);
21742
+ });
21706
21743
  return true;
21707
21744
  }
21708
- getPublicationForTrack(track) {
21709
- let publication;
21710
- this.tracks.forEach(pub => {
21711
- const localTrack = pub.track;
21712
- if (!localTrack) {
21713
- return;
21714
- }
21715
- // this looks overly complicated due to this object tree
21716
- if (track instanceof MediaStreamTrack) {
21717
- if (localTrack instanceof LocalAudioTrack || localTrack instanceof LocalVideoTrack) {
21718
- if (localTrack.mediaStreamTrack === track) {
21719
- publication = pub;
21720
- }
21745
+ /** @internal */
21746
+ unpublishTrack(sid, sendUnpublish) {
21747
+ const publication = this.trackPublications.get(sid);
21748
+ if (!publication) {
21749
+ return;
21750
+ }
21751
+ // also send unsubscribe, if track is actively subscribed
21752
+ const {
21753
+ track
21754
+ } = publication;
21755
+ if (track) {
21756
+ track.stop();
21757
+ publication.setTrack(undefined);
21758
+ }
21759
+ // remove track from maps only after unsubscribed has been fired
21760
+ this.trackPublications.delete(sid);
21761
+ // remove from the right type map
21762
+ switch (publication.kind) {
21763
+ case Track.Kind.Audio:
21764
+ this.audioTrackPublications.delete(sid);
21765
+ break;
21766
+ case Track.Kind.Video:
21767
+ this.videoTrackPublications.delete(sid);
21768
+ break;
21769
+ }
21770
+ if (sendUnpublish) {
21771
+ this.emit(ParticipantEvent.TrackUnpublished, publication);
21772
+ }
21773
+ }
21774
+ /**
21775
+ * @internal
21776
+ */
21777
+ setAudioOutput(output) {
21778
+ return __awaiter(this, void 0, void 0, function* () {
21779
+ this.audioOutput = output;
21780
+ const promises = [];
21781
+ this.audioTrackPublications.forEach(pub => {
21782
+ var _a;
21783
+ if (pub.track instanceof RemoteAudioTrack) {
21784
+ promises.push(pub.track.setSinkId((_a = output.deviceId) !== null && _a !== void 0 ? _a : 'default'));
21721
21785
  }
21722
- } else if (track === localTrack) {
21723
- publication = pub;
21724
- }
21786
+ });
21787
+ yield Promise.all(promises);
21725
21788
  });
21726
- return publication;
21789
+ }
21790
+ /** @internal */
21791
+ emit(event) {
21792
+ for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
21793
+ args[_key - 1] = arguments[_key];
21794
+ }
21795
+ this.log.trace('participant event', Object.assign(Object.assign({}, this.logContext), {
21796
+ event,
21797
+ args
21798
+ }));
21799
+ return super.emit(event, ...args);
21727
21800
  }
21728
21801
  }
21729
21802
 
@@ -21735,8 +21808,6 @@ var ConnectionState;
21735
21808
  ConnectionState["Reconnecting"] = "reconnecting";
21736
21809
  })(ConnectionState || (ConnectionState = {}));
21737
21810
  const connectionReconcileFrequency = 2 * 1000;
21738
- /** @deprecated RoomState has been renamed to [[ConnectionState]] */
21739
- const RoomState = ConnectionState;
21740
21811
  /**
21741
21812
  * In LiveKit, a room is the logical grouping for a list of participants.
21742
21813
  * Participants in a room can publish tracks, and subscribe to others' tracks.
@@ -21767,6 +21838,7 @@ class Room extends eventsExports.EventEmitter {
21767
21838
  this.isVideoPlaybackBlocked = false;
21768
21839
  this.log = livekitLogger;
21769
21840
  this.bufferedEvents = [];
21841
+ this.isResuming = false;
21770
21842
  this.connect = (url, token, opts) => __awaiter(this, void 0, void 0, function* () {
21771
21843
  var _c;
21772
21844
  // In case a disconnect called happened right before the connect call, make sure the disconnect is completed first by awaiting its lock
@@ -21853,7 +21925,6 @@ class Room extends eventsExports.EventEmitter {
21853
21925
  var _e, _f, _g;
21854
21926
  const joinResponse = yield engine.join(url, token, {
21855
21927
  autoSubscribe: connectOptions.autoSubscribe,
21856
- publishOnly: connectOptions.publishOnly,
21857
21928
  adaptiveStream: typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
21858
21929
  maxRetries: connectOptions.maxRetries,
21859
21930
  e2eeEnabled: !!this.e2eeManager,
@@ -21898,8 +21969,8 @@ class Room extends eventsExports.EventEmitter {
21898
21969
  }
21899
21970
  };
21900
21971
  this.attemptConnection = (url, token, opts, abortController) => __awaiter(this, void 0, void 0, function* () {
21901
- var _h, _j;
21902
- if (this.state === ConnectionState.Reconnecting) {
21972
+ var _h, _j, _k;
21973
+ if (this.state === ConnectionState.Reconnecting || this.isResuming || ((_h = this.engine) === null || _h === void 0 ? void 0 : _h.pendingReconnect)) {
21903
21974
  this.log.info('Reconnection attempt replaced by new connection attempt', this.logContext);
21904
21975
  // make sure we close and recreate the existing engine in order to get rid of any potentially ongoing reconnection attempts
21905
21976
  this.recreateEngine();
@@ -21907,7 +21978,7 @@ class Room extends eventsExports.EventEmitter {
21907
21978
  // create engine if previously disconnected
21908
21979
  this.maybeCreateEngine();
21909
21980
  }
21910
- if ((_h = this.regionUrlProvider) === null || _h === void 0 ? void 0 : _h.isCloud()) {
21981
+ if ((_j = this.regionUrlProvider) === null || _j === void 0 ? void 0 : _j.isCloud()) {
21911
21982
  this.engine.setRegionUrlProvider(this.regionUrlProvider);
21912
21983
  }
21913
21984
  this.acquireAudioContext();
@@ -21960,7 +22031,7 @@ class Room extends eventsExports.EventEmitter {
21960
22031
  }
21961
22032
  if (isWeb()) {
21962
22033
  document.addEventListener('freeze', this.onPageLeave);
21963
- (_j = navigator.mediaDevices) === null || _j === void 0 ? void 0 : _j.addEventListener('devicechange', this.handleDeviceChange);
22034
+ (_k = navigator.mediaDevices) === null || _k === void 0 ? void 0 : _k.addEventListener('devicechange', this.handleDeviceChange);
21964
22035
  }
21965
22036
  this.setAndEmitConnectionState(ConnectionState.Connected);
21966
22037
  this.emit(RoomEvent.Connected);
@@ -21972,7 +22043,7 @@ class Room extends eventsExports.EventEmitter {
21972
22043
  this.disconnect = function () {
21973
22044
  let stopTracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
21974
22045
  return __awaiter(_this, void 0, void 0, function* () {
21975
- var _k, _l, _m, _o;
22046
+ var _l, _m, _o, _p;
21976
22047
  const unlock = yield this.disconnectLock.lock();
21977
22048
  try {
21978
22049
  if (this.state === ConnectionState.Disconnected) {
@@ -21980,16 +22051,16 @@ class Room extends eventsExports.EventEmitter {
21980
22051
  return;
21981
22052
  }
21982
22053
  this.log.info('disconnect from room', Object.assign({}, this.logContext));
21983
- if (this.state === ConnectionState.Connecting || this.state === ConnectionState.Reconnecting) {
22054
+ if (this.state === ConnectionState.Connecting || this.state === ConnectionState.Reconnecting || this.isResuming) {
21984
22055
  // try aborting pending connection attempt
21985
22056
  this.log.warn('abort connection attempt', this.logContext);
21986
- (_k = this.abortController) === null || _k === void 0 ? void 0 : _k.abort();
22057
+ (_l = this.abortController) === null || _l === void 0 ? void 0 : _l.abort();
21987
22058
  // in case the abort controller didn't manage to cancel the connection attempt, reject the connect promise explicitly
21988
- (_m = (_l = this.connectFuture) === null || _l === void 0 ? void 0 : _l.reject) === null || _m === void 0 ? void 0 : _m.call(_l, new ConnectionError('Client initiated disconnect'));
22059
+ (_o = (_m = this.connectFuture) === null || _m === void 0 ? void 0 : _m.reject) === null || _o === void 0 ? void 0 : _o.call(_m, new ConnectionError('Client initiated disconnect'));
21989
22060
  this.connectFuture = undefined;
21990
22061
  }
21991
22062
  // send leave
21992
- if (!((_o = this.engine) === null || _o === void 0 ? void 0 : _o.client.isDisconnected)) {
22063
+ if (!((_p = this.engine) === null || _p === void 0 ? void 0 : _p.client.isDisconnected)) {
21993
22064
  yield this.engine.client.sendLeave();
21994
22065
  }
21995
22066
  // close engine (also closes client)
@@ -22056,8 +22127,8 @@ class Room extends eventsExports.EventEmitter {
22056
22127
  }
22057
22128
  elements.push(dummyAudioEl);
22058
22129
  }
22059
- this.participants.forEach(p => {
22060
- p.audioTracks.forEach(t => {
22130
+ this.remoteParticipants.forEach(p => {
22131
+ p.audioTrackPublications.forEach(t => {
22061
22132
  if (t.track) {
22062
22133
  t.track.attachedElements.forEach(e => {
22063
22134
  elements.push(e);
@@ -22078,8 +22149,8 @@ class Room extends eventsExports.EventEmitter {
22078
22149
  });
22079
22150
  this.startVideo = () => __awaiter(this, void 0, void 0, function* () {
22080
22151
  const elements = [];
22081
- for (const p of this.participants.values()) {
22082
- p.videoTracks.forEach(tr => {
22152
+ for (const p of this.remoteParticipants.values()) {
22153
+ p.videoTrackPublications.forEach(tr => {
22083
22154
  var _a;
22084
22155
  (_a = tr.track) === null || _a === void 0 ? void 0 : _a.attachedElements.forEach(el => {
22085
22156
  if (!elements.includes(el)) {
@@ -22100,9 +22171,11 @@ class Room extends eventsExports.EventEmitter {
22100
22171
  });
22101
22172
  this.handleRestarting = () => {
22102
22173
  this.clearConnectionReconcile();
22174
+ // in case we went from resuming to full-reconnect, make sure to reflect it on the isResuming flag
22175
+ this.isResuming = false;
22103
22176
  // also unwind existing participants & existing subscriptions
22104
- for (const p of this.participants.values()) {
22105
- this.handleParticipantDisconnected(p.sid, p);
22177
+ for (const p of this.remoteParticipants.values()) {
22178
+ this.handleParticipantDisconnected(p.identity, p);
22106
22179
  }
22107
22180
  if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
22108
22181
  this.emit(RoomEvent.Reconnecting);
@@ -22127,7 +22200,7 @@ class Room extends eventsExports.EventEmitter {
22127
22200
  this.log.debug("fully reconnected to server", Object.assign(Object.assign({}, this.logContext), {
22128
22201
  region: joinResponse.serverRegion
22129
22202
  }));
22130
- } catch (_p) {
22203
+ } catch (_q) {
22131
22204
  // reconnection failed, handleDisconnect is being invoked already, just return here
22132
22205
  return;
22133
22206
  }
@@ -22139,28 +22212,23 @@ class Room extends eventsExports.EventEmitter {
22139
22212
  this.handleParticipantUpdates = participantInfos => {
22140
22213
  // handle changes to participant state, and send events
22141
22214
  participantInfos.forEach(info => {
22215
+ var _a;
22142
22216
  if (info.identity === this.localParticipant.identity) {
22143
22217
  this.localParticipant.updateInfo(info);
22144
22218
  return;
22145
22219
  }
22146
- // ensure identity <=> sid mapping
22147
- const sid = this.identityToSid.get(info.identity);
22148
- if (sid && sid !== info.sid) {
22149
- // sid had changed, need to remove previous participant
22150
- this.handleParticipantDisconnected(sid, this.participants.get(sid));
22220
+ // LiveKit server doesn't send identity info prior to version 1.5.2 in disconnect updates
22221
+ // so we try to map an empty identity to an already known sID manually
22222
+ if (info.identity === '') {
22223
+ info.identity = (_a = this.sidToIdentity.get(info.sid)) !== null && _a !== void 0 ? _a : '';
22151
22224
  }
22152
- let remoteParticipant = this.participants.get(info.sid);
22153
- const isNewParticipant = !remoteParticipant;
22225
+ let remoteParticipant = this.remoteParticipants.get(info.identity);
22154
22226
  // when it's disconnected, send updates
22155
22227
  if (info.state === ParticipantInfo_State.DISCONNECTED) {
22156
- this.handleParticipantDisconnected(info.sid, remoteParticipant);
22228
+ this.handleParticipantDisconnected(info.identity, remoteParticipant);
22157
22229
  } else {
22158
22230
  // create participant if doesn't exist
22159
- remoteParticipant = this.getOrCreateParticipant(info.sid, info);
22160
- if (!isNewParticipant) {
22161
- // just update, no events
22162
- remoteParticipant.updateInfo(info);
22163
- }
22231
+ remoteParticipant = this.getOrCreateParticipant(info.identity, info);
22164
22232
  }
22165
22233
  });
22166
22234
  };
@@ -22175,7 +22243,7 @@ class Room extends eventsExports.EventEmitter {
22175
22243
  this.localParticipant.setIsSpeaking(true);
22176
22244
  activeSpeakers.push(this.localParticipant);
22177
22245
  } else {
22178
- const p = this.participants.get(speaker.sid);
22246
+ const p = this.getRemoteParticipantBySid(speaker.sid);
22179
22247
  if (p) {
22180
22248
  p.audioLevel = speaker.level;
22181
22249
  p.setIsSpeaking(true);
@@ -22187,7 +22255,7 @@ class Room extends eventsExports.EventEmitter {
22187
22255
  this.localParticipant.audioLevel = 0;
22188
22256
  this.localParticipant.setIsSpeaking(false);
22189
22257
  }
22190
- this.participants.forEach(p => {
22258
+ this.remoteParticipants.forEach(p => {
22191
22259
  if (!seenSids[p.sid]) {
22192
22260
  p.audioLevel = 0;
22193
22261
  p.setIsSpeaking(false);
@@ -22203,7 +22271,7 @@ class Room extends eventsExports.EventEmitter {
22203
22271
  lastSpeakers.set(p.sid, p);
22204
22272
  });
22205
22273
  speakerUpdates.forEach(speaker => {
22206
- let p = this.participants.get(speaker.sid);
22274
+ let p = this.getRemoteParticipantBySid(speaker.sid);
22207
22275
  if (speaker.sid === this.localParticipant.sid) {
22208
22276
  p = this.localParticipant;
22209
22277
  }
@@ -22225,11 +22293,11 @@ class Room extends eventsExports.EventEmitter {
22225
22293
  };
22226
22294
  this.handleStreamStateUpdate = streamStateUpdate => {
22227
22295
  streamStateUpdate.streamStates.forEach(streamState => {
22228
- const participant = this.participants.get(streamState.participantSid);
22296
+ const participant = this.getRemoteParticipantBySid(streamState.participantSid);
22229
22297
  if (!participant) {
22230
22298
  return;
22231
22299
  }
22232
- const pub = participant.getTrackPublication(streamState.trackSid);
22300
+ const pub = participant.getTrackPublicationBySid(streamState.trackSid);
22233
22301
  if (!pub || !pub.track) {
22234
22302
  return;
22235
22303
  }
@@ -22239,22 +22307,22 @@ class Room extends eventsExports.EventEmitter {
22239
22307
  });
22240
22308
  };
22241
22309
  this.handleSubscriptionPermissionUpdate = update => {
22242
- const participant = this.participants.get(update.participantSid);
22310
+ const participant = this.getRemoteParticipantBySid(update.participantSid);
22243
22311
  if (!participant) {
22244
22312
  return;
22245
22313
  }
22246
- const pub = participant.getTrackPublication(update.trackSid);
22314
+ const pub = participant.getTrackPublicationBySid(update.trackSid);
22247
22315
  if (!pub) {
22248
22316
  return;
22249
22317
  }
22250
22318
  pub.setAllowed(update.allowed);
22251
22319
  };
22252
22320
  this.handleSubscriptionError = update => {
22253
- const participant = Array.from(this.participants.values()).find(p => p.tracks.has(update.trackSid));
22321
+ const participant = Array.from(this.remoteParticipants.values()).find(p => p.trackPublications.has(update.trackSid));
22254
22322
  if (!participant) {
22255
22323
  return;
22256
22324
  }
22257
- const pub = participant.getTrackPublication(update.trackSid);
22325
+ const pub = participant.getTrackPublicationBySid(update.trackSid);
22258
22326
  if (!pub) {
22259
22327
  return;
22260
22328
  }
@@ -22262,7 +22330,7 @@ class Room extends eventsExports.EventEmitter {
22262
22330
  };
22263
22331
  this.handleDataPacket = (userPacket, kind) => {
22264
22332
  // find the participant
22265
- const participant = this.participants.get(userPacket.participantSid);
22333
+ const participant = this.remoteParticipants.get(userPacket.participantIdentity);
22266
22334
  this.emit(RoomEvent.DataReceived, userPacket.payload, participant, kind, userPacket.topic);
22267
22335
  // also emit on the participant
22268
22336
  participant === null || participant === void 0 ? void 0 : participant.emit(ParticipantEvent.DataReceived, userPacket.payload, kind);
@@ -22315,7 +22383,7 @@ class Room extends eventsExports.EventEmitter {
22315
22383
  this.localParticipant.setConnectionQuality(info.quality);
22316
22384
  return;
22317
22385
  }
22318
- const participant = this.participants.get(info.participantSid);
22386
+ const participant = this.getRemoteParticipantBySid(info.participantSid);
22319
22387
  if (participant) {
22320
22388
  participant.setConnectionQuality(info.quality);
22321
22389
  }
@@ -22334,7 +22402,7 @@ class Room extends eventsExports.EventEmitter {
22334
22402
  this.emit(RoomEvent.TrackUnmuted, pub, this.localParticipant);
22335
22403
  };
22336
22404
  this.onLocalTrackPublished = pub => __awaiter(this, void 0, void 0, function* () {
22337
- var _q;
22405
+ var _r;
22338
22406
  this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant);
22339
22407
  if (pub.track instanceof LocalAudioTrack) {
22340
22408
  const trackIsSilent = yield pub.track.checkForSilence();
@@ -22342,7 +22410,7 @@ class Room extends eventsExports.EventEmitter {
22342
22410
  this.emit(RoomEvent.LocalAudioSilenceDetected, pub);
22343
22411
  }
22344
22412
  }
22345
- const deviceId = yield (_q = pub.track) === null || _q === void 0 ? void 0 : _q.getDeviceId();
22413
+ const deviceId = yield (_r = pub.track) === null || _r === void 0 ? void 0 : _r.getDeviceId();
22346
22414
  const deviceKind = sourceToKind(pub.source);
22347
22415
  if (deviceKind && deviceId && deviceId !== this.localParticipant.activeDeviceMap.get(deviceKind)) {
22348
22416
  this.localParticipant.activeDeviceMap.set(deviceKind, deviceId);
@@ -22362,8 +22430,8 @@ class Room extends eventsExports.EventEmitter {
22362
22430
  this.emit(RoomEvent.ParticipantPermissionsChanged, prevPermissions, this.localParticipant);
22363
22431
  };
22364
22432
  this.setMaxListeners(100);
22365
- this.participants = new Map();
22366
- this.identityToSid = new Map();
22433
+ this.remoteParticipants = new Map();
22434
+ this.sidToIdentity = new Map();
22367
22435
  this.options = Object.assign(Object.assign({}, roomOptionDefaults), options);
22368
22436
  this.log = getLogger((_a = this.options.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Room);
22369
22437
  this.options.audioCaptureDefaults = Object.assign(Object.assign({}, audioDefaults), options === null || options === void 0 ? void 0 : options.audioCaptureDefaults);
@@ -22415,9 +22483,10 @@ class Room extends eventsExports.EventEmitter {
22415
22483
  }
22416
22484
  }
22417
22485
  get logContext() {
22486
+ var _a;
22418
22487
  return {
22419
22488
  room: this.name,
22420
- roomSid: this.sid,
22489
+ roomSid: (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.sid,
22421
22490
  identity: this.localParticipant.identity
22422
22491
  };
22423
22492
  }
@@ -22428,10 +22497,32 @@ class Room extends eventsExports.EventEmitter {
22428
22497
  var _a, _b;
22429
22498
  return (_b = (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.activeRecording) !== null && _b !== void 0 ? _b : false;
22430
22499
  }
22431
- /** server assigned unique room id */
22432
- get sid() {
22433
- var _a, _b;
22434
- return (_b = (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.sid) !== null && _b !== void 0 ? _b : '';
22500
+ /**
22501
+ * server assigned unique room id.
22502
+ * returns once a sid has been issued by the server.
22503
+ */
22504
+ getSid() {
22505
+ return __awaiter(this, void 0, void 0, function* () {
22506
+ if (this.state === ConnectionState.Disconnected) {
22507
+ return '';
22508
+ }
22509
+ if (this.roomInfo && this.roomInfo.sid !== '') {
22510
+ return this.roomInfo.sid;
22511
+ }
22512
+ return new Promise((resolve, reject) => {
22513
+ const handleRoomUpdate = roomInfo => {
22514
+ if (roomInfo.sid !== '') {
22515
+ this.engine.off(EngineEvent.RoomUpdate, handleRoomUpdate);
22516
+ resolve(roomInfo.sid);
22517
+ }
22518
+ };
22519
+ this.engine.on(EngineEvent.RoomUpdate, handleRoomUpdate);
22520
+ this.once(RoomEvent.Disconnected, () => {
22521
+ this.engine.off(EngineEvent.RoomUpdate, handleRoomUpdate);
22522
+ reject('Room disconnected before room server id was available');
22523
+ });
22524
+ });
22525
+ });
22435
22526
  }
22436
22527
  /** user assigned name, derived from JWT token */
22437
22528
  get name() {
@@ -22462,18 +22553,17 @@ class Room extends eventsExports.EventEmitter {
22462
22553
  this.handleDisconnect(this.options.stopLocalTrackOnUnpublish, reason);
22463
22554
  }).on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate).on(EngineEvent.DataPacketReceived, this.handleDataPacket).on(EngineEvent.Resuming, () => {
22464
22555
  this.clearConnectionReconcile();
22465
- if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
22466
- this.emit(RoomEvent.Reconnecting);
22467
- }
22556
+ this.isResuming = true;
22557
+ this.log.info('Resuming signal connection', this.logContext);
22468
22558
  }).on(EngineEvent.Resumed, () => {
22469
- this.setAndEmitConnectionState(ConnectionState.Connected);
22470
- this.emit(RoomEvent.Reconnected);
22471
22559
  this.registerConnectionReconcile();
22560
+ this.isResuming = false;
22561
+ this.log.info('Resumed signal connection', this.logContext);
22472
22562
  this.updateSubscriptions();
22473
22563
  this.emitBufferedEvents();
22474
22564
  }).on(EngineEvent.SignalResumed, () => {
22475
22565
  this.bufferedEvents = [];
22476
- if (this.state === ConnectionState.Reconnecting) {
22566
+ if (this.state === ConnectionState.Reconnecting || this.isResuming) {
22477
22567
  this.sendSyncState();
22478
22568
  }
22479
22569
  }).on(EngineEvent.Restarting, this.handleRestarting).on(EngineEvent.SignalRestarted, this.handleSignalRestarted).on(EngineEvent.DCBufferStatusChanged, (status, kind) => {
@@ -22547,10 +22637,7 @@ class Room extends eventsExports.EventEmitter {
22547
22637
  if (this.localParticipant.identity === identity) {
22548
22638
  return this.localParticipant;
22549
22639
  }
22550
- const sid = this.identityToSid.get(identity);
22551
- if (sid) {
22552
- return this.participants.get(sid);
22553
- }
22640
+ return this.remoteParticipants.get(identity);
22554
22641
  }
22555
22642
  clearConnectionFutures() {
22556
22643
  this.connectFuture = undefined;
@@ -22681,15 +22768,6 @@ class Room extends eventsExports.EventEmitter {
22681
22768
  get canPlaybackVideo() {
22682
22769
  return !this.isVideoPlaybackBlocked;
22683
22770
  }
22684
- /**
22685
- * Returns the active audio output device used in this room.
22686
- * @return the previously successfully set audio output device ID or an empty string if the default device is used.
22687
- * @deprecated use `getActiveDevice('audiooutput')` instead
22688
- */
22689
- getActiveAudioOutputDevice() {
22690
- var _a, _b;
22691
- return (_b = (_a = this.options.audioOutput) === null || _a === void 0 ? void 0 : _a.deviceId) !== null && _b !== void 0 ? _b : '';
22692
- }
22693
22771
  getActiveDevice(kind) {
22694
22772
  return this.localParticipant.activeDeviceMap.get(kind);
22695
22773
  }
@@ -22717,7 +22795,7 @@ class Room extends eventsExports.EventEmitter {
22717
22795
  const prevDeviceId = this.options.audioCaptureDefaults.deviceId;
22718
22796
  this.options.audioCaptureDefaults.deviceId = deviceConstraint;
22719
22797
  deviceHasChanged = prevDeviceId !== deviceConstraint;
22720
- const tracks = Array.from(this.localParticipant.audioTracks.values()).filter(track => track.source === Track.Source.Microphone);
22798
+ const tracks = Array.from(this.localParticipant.audioTrackPublications.values()).filter(track => track.source === Track.Source.Microphone);
22721
22799
  try {
22722
22800
  success = (yield Promise.all(tracks.map(t => {
22723
22801
  var _a;
@@ -22731,7 +22809,7 @@ class Room extends eventsExports.EventEmitter {
22731
22809
  const prevDeviceId = this.options.videoCaptureDefaults.deviceId;
22732
22810
  this.options.videoCaptureDefaults.deviceId = deviceConstraint;
22733
22811
  deviceHasChanged = prevDeviceId !== deviceConstraint;
22734
- const tracks = Array.from(this.localParticipant.videoTracks.values()).filter(track => track.source === Track.Source.Camera);
22812
+ const tracks = Array.from(this.localParticipant.videoTrackPublications.values()).filter(track => track.source === Track.Source.Camera);
22735
22813
  try {
22736
22814
  success = (yield Promise.all(tracks.map(t => {
22737
22815
  var _a;
@@ -22742,7 +22820,7 @@ class Room extends eventsExports.EventEmitter {
22742
22820
  throw e;
22743
22821
  }
22744
22822
  } else if (kind === 'audiooutput') {
22745
- if (!supportsSetSinkId() && !this.options.expWebAudioMix || this.options.expWebAudioMix && this.audioContext && !('setSinkId' in this.audioContext)) {
22823
+ if (!supportsSetSinkId() && !this.options.webAudioMix || this.options.webAudioMix && this.audioContext && !('setSinkId' in this.audioContext)) {
22746
22824
  throw new Error('cannot switch audio output, setSinkId not supported');
22747
22825
  }
22748
22826
  (_a = (_c = this.options).audioOutput) !== null && _a !== void 0 ? _a : _c.audioOutput = {};
@@ -22750,11 +22828,11 @@ class Room extends eventsExports.EventEmitter {
22750
22828
  this.options.audioOutput.deviceId = deviceId;
22751
22829
  deviceHasChanged = prevDeviceId !== deviceConstraint;
22752
22830
  try {
22753
- if (this.options.expWebAudioMix) {
22831
+ if (this.options.webAudioMix) {
22754
22832
  // @ts-expect-error setSinkId is not yet in the typescript type of AudioContext
22755
22833
  (_b = this.audioContext) === null || _b === void 0 ? void 0 : _b.setSinkId(deviceId);
22756
22834
  } else {
22757
- yield Promise.all(Array.from(this.participants.values()).map(p => p.setAudioOutput({
22835
+ yield Promise.all(Array.from(this.remoteParticipants.values()).map(p => p.setAudioOutput({
22758
22836
  deviceId
22759
22837
  })));
22760
22838
  }
@@ -22778,9 +22856,11 @@ class Room extends eventsExports.EventEmitter {
22778
22856
  (_a = this.engine) === null || _a === void 0 ? void 0 : _a.close();
22779
22857
  /* @ts-ignore */
22780
22858
  this.engine = undefined;
22859
+ this.isResuming = false;
22781
22860
  // clear out existing remote participants, since they may have attached
22782
22861
  // the old engine
22783
- this.participants.clear();
22862
+ this.remoteParticipants.clear();
22863
+ this.sidToIdentity.clear();
22784
22864
  this.bufferedEvents = [];
22785
22865
  this.maybeCreateEngine();
22786
22866
  }
@@ -22810,19 +22890,19 @@ class Room extends eventsExports.EventEmitter {
22810
22890
  return;
22811
22891
  }
22812
22892
  const parts = unpackStreamId(stream.id);
22813
- const participantId = parts[0];
22893
+ const participantSid = parts[0];
22814
22894
  let streamId = parts[1];
22815
22895
  let trackId = mediaTrack.id;
22816
22896
  // firefox will get streamId (pID|trackId) instead of (pID|streamId) as it doesn't support sync tracks by stream
22817
22897
  // and generates its own track id instead of infer from sdp track id.
22818
22898
  if (streamId && streamId.startsWith('TR')) trackId = streamId;
22819
- if (participantId === this.localParticipant.sid) {
22899
+ if (participantSid === this.localParticipant.sid) {
22820
22900
  this.log.warn('tried to create RemoteParticipant for local participant', this.logContext);
22821
22901
  return;
22822
22902
  }
22823
- const participant = this.participants.get(participantId);
22903
+ const participant = Array.from(this.remoteParticipants.values()).find(p => p.sid === participantSid);
22824
22904
  if (!participant) {
22825
- this.log.error("Tried to add a track for a participant, that's not present. Sid: ".concat(participantId), this.logContext);
22905
+ this.log.error("Tried to add a track for a participant, that's not present. Sid: ".concat(participantSid), this.logContext);
22826
22906
  return;
22827
22907
  }
22828
22908
  let adaptiveStreamSettings;
@@ -22840,18 +22920,19 @@ class Room extends eventsExports.EventEmitter {
22840
22920
  let reason = arguments.length > 1 ? arguments[1] : undefined;
22841
22921
  var _a;
22842
22922
  this.clearConnectionReconcile();
22923
+ this.isResuming = false;
22843
22924
  this.bufferedEvents = [];
22844
22925
  if (this.state === ConnectionState.Disconnected) {
22845
22926
  return;
22846
22927
  }
22847
22928
  this.regionUrl = undefined;
22848
22929
  try {
22849
- this.participants.forEach(p => {
22850
- p.tracks.forEach(pub => {
22930
+ this.remoteParticipants.forEach(p => {
22931
+ p.trackPublications.forEach(pub => {
22851
22932
  p.unpublishTrack(pub.trackSid);
22852
22933
  });
22853
22934
  });
22854
- this.localParticipant.tracks.forEach(pub => {
22935
+ this.localParticipant.trackPublications.forEach(pub => {
22855
22936
  var _a, _b;
22856
22937
  if (pub.track) {
22857
22938
  this.localParticipant.unpublishTrack(pub.track, shouldStopTracks);
@@ -22862,12 +22943,13 @@ class Room extends eventsExports.EventEmitter {
22862
22943
  }
22863
22944
  });
22864
22945
  this.localParticipant.off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged).off(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged).off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted).off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted).off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished).off(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished).off(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged).off(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError).off(ParticipantEvent.AudioStreamAcquired, this.startAudio).off(ParticipantEvent.ParticipantPermissionsChanged, this.onLocalParticipantPermissionsChanged);
22865
- this.localParticipant.tracks.clear();
22866
- this.localParticipant.videoTracks.clear();
22867
- this.localParticipant.audioTracks.clear();
22868
- this.participants.clear();
22946
+ this.localParticipant.trackPublications.clear();
22947
+ this.localParticipant.videoTrackPublications.clear();
22948
+ this.localParticipant.audioTrackPublications.clear();
22949
+ this.remoteParticipants.clear();
22950
+ this.sidToIdentity.clear();
22869
22951
  this.activeSpeakers = [];
22870
- if (this.audioContext && typeof this.options.expWebAudioMix === 'boolean') {
22952
+ if (this.audioContext && typeof this.options.webAudioMix === 'boolean') {
22871
22953
  this.audioContext.close();
22872
22954
  this.audioContext = undefined;
22873
22955
  }
@@ -22882,14 +22964,13 @@ class Room extends eventsExports.EventEmitter {
22882
22964
  this.emit(RoomEvent.Disconnected, reason);
22883
22965
  }
22884
22966
  }
22885
- handleParticipantDisconnected(sid, participant) {
22967
+ handleParticipantDisconnected(identity, participant) {
22886
22968
  // remove and send event
22887
- this.participants.delete(sid);
22969
+ this.remoteParticipants.delete(identity);
22888
22970
  if (!participant) {
22889
22971
  return;
22890
22972
  }
22891
- this.identityToSid.delete(participant.identity);
22892
- participant.tracks.forEach(publication => {
22973
+ participant.trackPublications.forEach(publication => {
22893
22974
  participant.unpublishTrack(publication.trackSid, true);
22894
22975
  });
22895
22976
  this.emit(RoomEvent.ParticipantDisconnected, participant);
@@ -22897,9 +22978,9 @@ class Room extends eventsExports.EventEmitter {
22897
22978
  acquireAudioContext() {
22898
22979
  var _a, _b;
22899
22980
  return __awaiter(this, void 0, void 0, function* () {
22900
- if (typeof this.options.expWebAudioMix !== 'boolean' && this.options.expWebAudioMix.audioContext) {
22981
+ if (typeof this.options.webAudioMix !== 'boolean' && this.options.webAudioMix.audioContext) {
22901
22982
  // override audio context with custom audio context if supplied by user
22902
- this.audioContext = this.options.expWebAudioMix.audioContext;
22983
+ this.audioContext = this.options.webAudioMix.audioContext;
22903
22984
  } else if (!this.audioContext || this.audioContext.state === 'closed') {
22904
22985
  // by using an AudioContext, it reduces lag on audio elements
22905
22986
  // https://stackoverflow.com/questions/9811429/html5-audio-tag-on-safari-has-a-delay/54119854#54119854
@@ -22916,8 +22997,8 @@ class Room extends eventsExports.EventEmitter {
22916
22997
  }));
22917
22998
  }
22918
22999
  }
22919
- if (this.options.expWebAudioMix) {
22920
- this.participants.forEach(participant => participant.setAudioContext(this.audioContext));
23000
+ if (this.options.webAudioMix) {
23001
+ this.remoteParticipants.forEach(participant => participant.setAudioContext(this.audioContext));
22921
23002
  }
22922
23003
  this.localParticipant.setAudioContext(this.audioContext);
22923
23004
  const newContextIsRunning = ((_b = this.audioContext) === null || _b === void 0 ? void 0 : _b.state) === 'running';
@@ -22927,18 +23008,18 @@ class Room extends eventsExports.EventEmitter {
22927
23008
  }
22928
23009
  });
22929
23010
  }
22930
- createParticipant(id, info) {
23011
+ createParticipant(identity, info) {
22931
23012
  var _a;
22932
23013
  let participant;
22933
23014
  if (info) {
22934
23015
  participant = RemoteParticipant.fromParticipantInfo(this.engine.client, info);
22935
23016
  } else {
22936
- participant = new RemoteParticipant(this.engine.client, id, '', undefined, undefined, {
23017
+ participant = new RemoteParticipant(this.engine.client, '', identity, undefined, undefined, {
22937
23018
  loggerContextCb: () => this.logContext,
22938
23019
  loggerName: this.options.loggerName
22939
23020
  });
22940
23021
  }
22941
- if (this.options.expWebAudioMix) {
23022
+ if (this.options.webAudioMix) {
22942
23023
  participant.setAudioContext(this.audioContext);
22943
23024
  }
22944
23025
  if ((_a = this.options.audioOutput) === null || _a === void 0 ? void 0 : _a.deviceId) {
@@ -22946,13 +23027,20 @@ class Room extends eventsExports.EventEmitter {
22946
23027
  }
22947
23028
  return participant;
22948
23029
  }
22949
- getOrCreateParticipant(id, info) {
22950
- if (this.participants.has(id)) {
22951
- return this.participants.get(id);
23030
+ getOrCreateParticipant(identity, info) {
23031
+ if (this.remoteParticipants.has(identity)) {
23032
+ const existingParticipant = this.remoteParticipants.get(identity);
23033
+ if (info) {
23034
+ const wasUpdated = existingParticipant.updateInfo(info);
23035
+ if (wasUpdated) {
23036
+ this.sidToIdentity.set(info.sid, info.identity);
23037
+ }
23038
+ }
23039
+ return existingParticipant;
22952
23040
  }
22953
- const participant = this.createParticipant(id, info);
22954
- this.participants.set(id, participant);
22955
- this.identityToSid.set(info.identity, info.sid);
23041
+ const participant = this.createParticipant(identity, info);
23042
+ this.remoteParticipants.set(identity, participant);
23043
+ this.sidToIdentity.set(info.sid, info.identity);
22956
23044
  // if we have valid info and the participant wasn't in the map before, we can assume the participant is new
22957
23045
  // firing here to make sure that `ParticipantConnected` fires before the initial track events
22958
23046
  this.emitWhenConnected(RoomEvent.ParticipantConnected, participant);
@@ -23003,11 +23091,11 @@ class Room extends eventsExports.EventEmitter {
23003
23091
  return participant;
23004
23092
  }
23005
23093
  sendSyncState() {
23006
- const remoteTracks = Array.from(this.participants.values()).reduce((acc, participant) => {
23007
- acc.push(...participant.getTracks()); // FIXME would be nice to have this return RemoteTrackPublications directly instead of the type cast
23094
+ const remoteTracks = Array.from(this.remoteParticipants.values()).reduce((acc, participant) => {
23095
+ acc.push(...participant.getTrackPublications()); // FIXME would be nice to have this return RemoteTrackPublications directly instead of the type cast
23008
23096
  return acc;
23009
23097
  }, []);
23010
- const localTracks = this.localParticipant.getTracks(); // FIXME would be nice to have this return LocalTrackPublications directly instead of the type cast
23098
+ const localTracks = this.localParticipant.getTrackPublications(); // FIXME would be nice to have this return LocalTrackPublications directly instead of the type cast
23011
23099
  this.engine.sendSyncState(remoteTracks, localTracks);
23012
23100
  }
23013
23101
  /**
@@ -23015,14 +23103,20 @@ class Room extends eventsExports.EventEmitter {
23015
23103
  * subscription settings.
23016
23104
  */
23017
23105
  updateSubscriptions() {
23018
- for (const p of this.participants.values()) {
23019
- for (const pub of p.videoTracks.values()) {
23106
+ for (const p of this.remoteParticipants.values()) {
23107
+ for (const pub of p.videoTrackPublications.values()) {
23020
23108
  if (pub.isSubscribed && pub instanceof RemoteTrackPublication) {
23021
23109
  pub.emitTrackUpdate();
23022
23110
  }
23023
23111
  }
23024
23112
  }
23025
23113
  }
23114
+ getRemoteParticipantBySid(sid) {
23115
+ const identity = this.sidToIdentity.get(sid);
23116
+ if (identity) {
23117
+ return this.remoteParticipants.get(identity);
23118
+ }
23119
+ }
23026
23120
  registerConnectionReconcile() {
23027
23121
  this.clearConnectionReconcile();
23028
23122
  let consecutiveFailures = 0;
@@ -23076,11 +23170,11 @@ class Room extends eventsExports.EventEmitter {
23076
23170
  for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
23077
23171
  args[_key - 1] = arguments[_key];
23078
23172
  }
23079
- if (this.state === ConnectionState.Connected) {
23080
- return this.emit(event, ...args);
23081
- } else if (this.state === ConnectionState.Reconnecting) {
23173
+ if (this.state === ConnectionState.Reconnecting || this.isResuming || !this.engine || this.engine.pendingReconnect) {
23082
23174
  // in case the room is reconnecting, buffer the events by firing them later after emitting RoomEvent.Reconnected
23083
23175
  this.bufferedEvents.push([event, args]);
23176
+ } else if (this.state === ConnectionState.Connected) {
23177
+ return this.emit(event, ...args);
23084
23178
  }
23085
23179
  return false;
23086
23180
  }
@@ -23859,5 +23953,5 @@ function isFacingModeValue(item) {
23859
23953
  return item === undefined || allowedValues.includes(item);
23860
23954
  }
23861
23955
 
23862
- export { AudioPresets, BaseKeyProvider, ConnectionCheck, ConnectionError, ConnectionQuality, ConnectionState, CriticalTimers, CryptorEvent, DataPacket_Kind, DefaultReconnectPolicy, DeviceUnsupportedError, DisconnectReason, EncryptionEvent, EngineEvent, ExternalE2EEKeyProvider, KeyHandlerEvent, KeyProviderEvent, LivekitError, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, MediaDeviceFailure, NegotiationError, Participant, ParticipantEvent, PublishDataError, RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, RoomEvent, RoomState, ScreenSharePresets, Track, TrackEvent, TrackInvalidError, TrackPublication, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, attachToElement, createAudioAnalyser, createE2EEKey, createKeyMaterialFromBuffer, createKeyMaterialFromString, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, deriveKeys, detachTrack, facingModeFromDeviceLabel, facingModeFromLocalTrack, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, getLogger, importKey, isBackupCodec, isBrowserSupported, isE2EESupported, isInsertableStreamSupported, isScriptTransformSupported, isVideoFrame, needsRbspUnescaping, parseRbsp, protocolVersion, ratchet, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, version, videoCodecs, writeRbsp };
23956
+ export { AudioPresets, BaseKeyProvider, ConnectionCheck, ConnectionError, ConnectionQuality, ConnectionState, CriticalTimers, CryptorEvent, DataPacket_Kind, DefaultReconnectPolicy, DeviceUnsupportedError, DisconnectReason, EncryptionEvent, EngineEvent, ExternalE2EEKeyProvider, KeyHandlerEvent, KeyProviderEvent, LivekitError, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, MediaDeviceFailure, NegotiationError, Participant, ParticipantEvent, PublishDataError, RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, RoomEvent, ScreenSharePresets, Track, TrackEvent, TrackInvalidError, TrackPublication, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, attachToElement, createAudioAnalyser, createE2EEKey, createKeyMaterialFromBuffer, createKeyMaterialFromString, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, deriveKeys, detachTrack, facingModeFromDeviceLabel, facingModeFromLocalTrack, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, getLogger, importKey, isBackupCodec, isBrowserSupported, isE2EESupported, isInsertableStreamSupported, isScriptTransformSupported, isVideoFrame, needsRbspUnescaping, parseRbsp, protocolVersion, ratchet, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, version, videoCodecs, writeRbsp };
23863
23957
  //# sourceMappingURL=livekit-client.esm.mjs.map