livekit-client 1.15.11 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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