livekit-client 1.15.10 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +21 -17
  2. package/dist/livekit-client.esm.mjs +1603 -1493
  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 +1 -3
  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 +14 -16
  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 +1 -3
  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 +14 -16
  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 +10 -10
  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 +169 -129
  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.10";
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
  });
@@ -14938,7 +15012,7 @@ class SignalClient {
14938
15012
  abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener('abort', abortHandler);
14939
15013
  this.log.debug("connecting to ".concat(url + params), this.logContext);
14940
15014
  if (this.ws) {
14941
- yield this.close();
15015
+ yield this.close(false);
14942
15016
  }
14943
15017
  this.ws = new WebSocket(url + params);
14944
15018
  this.ws.binaryType = 'arraybuffer';
@@ -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);
@@ -15035,10 +15111,13 @@ class SignalClient {
15035
15111
  }));
15036
15112
  }
15037
15113
  close() {
15114
+ let updateState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
15038
15115
  return __awaiter(this, void 0, void 0, function* () {
15039
15116
  const unlock = yield this.closingLock.lock();
15040
15117
  try {
15041
- this.state = SignalConnectionState.DISCONNECTING;
15118
+ if (updateState) {
15119
+ this.state = SignalConnectionState.DISCONNECTING;
15120
+ }
15042
15121
  if (this.ws) {
15043
15122
  this.ws.onmessage = null;
15044
15123
  this.ws.onopen = null;
@@ -15061,7 +15140,9 @@ class SignalClient {
15061
15140
  this.ws = undefined;
15062
15141
  }
15063
15142
  } finally {
15064
- this.state = SignalConnectionState.DISCONNECTED;
15143
+ if (updateState) {
15144
+ this.state = SignalConnectionState.DISCONNECTED;
15145
+ }
15065
15146
  this.clearPingInterval();
15066
15147
  unlock();
15067
15148
  }
@@ -15436,9 +15517,6 @@ function createConnectionParams(token, info, opts) {
15436
15517
  if (info.browserVersion) {
15437
15518
  params.set('browser_version', info.browserVersion);
15438
15519
  }
15439
- if (opts.publishOnly !== undefined) {
15440
- params.set('publish', opts.publishOnly);
15441
- }
15442
15520
  if (opts.adaptiveStream) {
15443
15521
  params.set('adaptive_stream', '1');
15444
15522
  }
@@ -16652,10 +16730,6 @@ function extractStereoAndNackAudioFromOffer(offer) {
16652
16730
 
16653
16731
  const defaultVideoCodec = 'vp8';
16654
16732
  const publishDefaults = {
16655
- /**
16656
- * @deprecated
16657
- */
16658
- audioBitrate: AudioPresets.music.maxBitrate,
16659
16733
  audioPreset: AudioPresets.music,
16660
16734
  dtx: true,
16661
16735
  red: true,
@@ -16680,7 +16754,7 @@ const roomOptionDefaults = {
16680
16754
  stopLocalTrackOnUnpublish: true,
16681
16755
  reconnectPolicy: new DefaultReconnectPolicy(),
16682
16756
  disconnectOnPageLeave: true,
16683
- expWebAudioMix: false
16757
+ webAudioMix: true
16684
16758
  };
16685
16759
  const roomConnectOptionDefaults = {
16686
16760
  autoSubscribe: true,
@@ -16984,6 +17058,9 @@ class RTCEngine extends eventsExports.EventEmitter {
16984
17058
  get isClosed() {
16985
17059
  return this._isClosed;
16986
17060
  }
17061
+ get pendingReconnect() {
17062
+ return !!this.reconnectTimeout;
17063
+ }
16987
17064
  constructor(options) {
16988
17065
  var _a;
16989
17066
  super();
@@ -17108,7 +17185,7 @@ class RTCEngine extends eventsExports.EventEmitter {
17108
17185
  // since the current engine may have inherited a regional url
17109
17186
  this.regionUrlProvider.updateToken(this.token);
17110
17187
  }
17111
- this.reconnectTimeout = CriticalTimers.setTimeout(() => this.attemptReconnect(disconnectReason), delay);
17188
+ this.reconnectTimeout = CriticalTimers.setTimeout(() => this.attemptReconnect(disconnectReason).finally(() => this.reconnectTimeout = undefined), delay);
17112
17189
  };
17113
17190
  this.waitForRestarted = () => {
17114
17191
  return new Promise((resolve, reject) => {
@@ -18953,7 +19030,8 @@ class LocalVideoTrack extends LocalTrack {
18953
19030
  }
18954
19031
  addSimulcastTrack(codec, encodings) {
18955
19032
  if (this.simulcastCodecs.has(codec)) {
18956
- throw new Error("".concat(codec, " already added"));
19033
+ this.log.error("".concat(codec, " already added, skipping adding simulcast codec"), this.logContext);
19034
+ return;
18957
19035
  }
18958
19036
  const simulcastCodecInfo = {
18959
19037
  codec,
@@ -19982,7 +20060,7 @@ class Participant extends eventsExports.EventEmitter {
19982
20060
  });
19983
20061
  }
19984
20062
  get isEncrypted() {
19985
- 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);
19986
20064
  }
19987
20065
  get isAgent() {
19988
20066
  var _a, _b;
@@ -20005,21 +20083,19 @@ class Participant extends eventsExports.EventEmitter {
20005
20083
  this.identity = identity;
20006
20084
  this.name = name;
20007
20085
  this.metadata = metadata;
20008
- this.audioTracks = new Map();
20009
- this.videoTracks = new Map();
20010
- this.tracks = new Map();
20086
+ this.audioTrackPublications = new Map();
20087
+ this.videoTrackPublications = new Map();
20088
+ this.trackPublications = new Map();
20011
20089
  }
20012
- getTracks() {
20013
- return Array.from(this.tracks.values());
20090
+ getTrackPublications() {
20091
+ return Array.from(this.trackPublications.values());
20014
20092
  }
20015
20093
  /**
20016
20094
  * Finds the first track that matches the source filter, for example, getting
20017
20095
  * the user's camera track with getTrackBySource(Track.Source.Camera).
20018
- * @param source
20019
- * @returns
20020
20096
  */
20021
- getTrack(source) {
20022
- for (const [, pub] of this.tracks) {
20097
+ getTrackPublication(source) {
20098
+ for (const [, pub] of this.trackPublications) {
20023
20099
  if (pub.source === source) {
20024
20100
  return pub;
20025
20101
  }
@@ -20027,11 +20103,9 @@ class Participant extends eventsExports.EventEmitter {
20027
20103
  }
20028
20104
  /**
20029
20105
  * Finds the first track that matches the track's name.
20030
- * @param name
20031
- * @returns
20032
20106
  */
20033
- getTrackByName(name) {
20034
- for (const [, pub] of this.tracks) {
20107
+ getTrackPublicationByName(name) {
20108
+ for (const [, pub] of this.trackPublications) {
20035
20109
  if (pub.trackName === name) {
20036
20110
  return pub;
20037
20111
  }
@@ -20042,16 +20116,16 @@ class Participant extends eventsExports.EventEmitter {
20042
20116
  }
20043
20117
  get isCameraEnabled() {
20044
20118
  var _a;
20045
- const track = this.getTrack(Track.Source.Camera);
20119
+ const track = this.getTrackPublication(Track.Source.Camera);
20046
20120
  return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true);
20047
20121
  }
20048
20122
  get isMicrophoneEnabled() {
20049
20123
  var _a;
20050
- const track = this.getTrack(Track.Source.Microphone);
20124
+ const track = this.getTrackPublication(Track.Source.Microphone);
20051
20125
  return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true);
20052
20126
  }
20053
20127
  get isScreenShareEnabled() {
20054
- const track = this.getTrack(Track.Source.ScreenShare);
20128
+ const track = this.getTrackPublication(Track.Source.ScreenShare);
20055
20129
  return !!track;
20056
20130
  }
20057
20131
  get isLocal() {
@@ -20145,7 +20219,7 @@ class Participant extends eventsExports.EventEmitter {
20145
20219
  */
20146
20220
  setAudioContext(ctx) {
20147
20221
  this.audioContext = ctx;
20148
- 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));
20149
20223
  }
20150
20224
  addTrackPublication(publication) {
20151
20225
  // forward publication driven events
@@ -20159,13 +20233,13 @@ class Participant extends eventsExports.EventEmitter {
20159
20233
  if (pub.track) {
20160
20234
  pub.track.sid = publication.trackSid;
20161
20235
  }
20162
- this.tracks.set(publication.trackSid, publication);
20236
+ this.trackPublications.set(publication.trackSid, publication);
20163
20237
  switch (publication.kind) {
20164
20238
  case Track.Kind.Audio:
20165
- this.audioTracks.set(publication.trackSid, publication);
20239
+ this.audioTrackPublications.set(publication.trackSid, publication);
20166
20240
  break;
20167
20241
  case Track.Kind.Video:
20168
- this.videoTracks.set(publication.trackSid, publication);
20242
+ this.videoTrackPublications.set(publication.trackSid, publication);
20169
20243
  break;
20170
20244
  }
20171
20245
  }
@@ -20184,993 +20258,502 @@ function trackPermissionToProto(perms) {
20184
20258
  });
20185
20259
  }
20186
20260
 
20187
- class RemoteTrackPublication extends TrackPublication {
20188
- constructor(kind, ti, autoSubscribe, loggerOptions) {
20189
- super(kind, ti.sid, ti.name, loggerOptions);
20190
- 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
+ };
20191
20299
  /** @internal */
20192
- this.allowed = true;
20193
- this.disabled = false;
20194
- this.currentVideoQuality = VideoQuality.HIGH;
20195
- this.handleEnded = track => {
20196
- this.setTrack(undefined);
20197
- this.emit(TrackEvent.Ended, track);
20300
+ this.onTrackUnmuted = track => {
20301
+ this.onTrackMuted(track, track.isUpstreamPaused);
20198
20302
  };
20199
- this.handleVisibilityChange = visible => {
20200
- this.log.debug("adaptivestream video visibility ".concat(this.trackSid, ", visible=").concat(visible), this.logContext);
20201
- this.disabled = !visible;
20202
- 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);
20203
20314
  };
20204
- this.handleVideoDimensionsChange = dimensions => {
20205
- this.log.debug("adaptivestream video dimensions ".concat(dimensions.width, "x").concat(dimensions.height), this.logContext);
20206
- this.videoDimensions = dimensions;
20207
- 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);
20208
20318
  };
20209
- this.subscribed = autoSubscribe;
20210
- 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
+ }
20211
20446
  }
20212
20447
  /**
20213
- * Subscribe or unsubscribe to this remote track
20214
- * @param subscribed true to subscribe to a track, false to unsubscribe
20448
+ * @internal
20215
20449
  */
20216
- setSubscribed(subscribed) {
20217
- const prevStatus = this.subscriptionStatus;
20218
- const prevPermission = this.permissionStatus;
20219
- this.subscribed = subscribed;
20220
- // reset allowed status when desired subscription state changes
20221
- // server will notify client via signal message if it's not allowed
20222
- if (subscribed) {
20223
- this.allowed = true;
20224
- }
20225
- const sub = new UpdateSubscription({
20226
- trackSids: [this.trackSid],
20227
- subscribe: this.subscribed,
20228
- participantTracks: [new ParticipantTracks({
20229
- // sending an empty participant id since TrackPublication doesn't keep it
20230
- // this is filled in by the participant that receives this message
20231
- participantSid: '',
20232
- trackSids: [this.trackSid]
20233
- })]
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
+ }
20234
20462
  });
20235
- this.emit(TrackEvent.UpdateSubscription, sub);
20236
- this.emitSubscriptionUpdateIfChanged(prevStatus);
20237
- this.emitPermissionUpdateIfChanged(prevPermission);
20238
- }
20239
- get subscriptionStatus() {
20240
- if (this.subscribed === false) {
20241
- return TrackPublication.SubscriptionStatus.Unsubscribed;
20242
- }
20243
- if (!super.isSubscribed) {
20244
- return TrackPublication.SubscriptionStatus.Desired;
20245
- }
20246
- return TrackPublication.SubscriptionStatus.Subscribed;
20247
- }
20248
- get permissionStatus() {
20249
- 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);
20250
20464
  }
20251
20465
  /**
20252
- * 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
20253
20471
  */
20254
- get isSubscribed() {
20255
- if (this.subscribed === false) {
20256
- return false;
20257
- }
20258
- return super.isSubscribed;
20259
- }
20260
- // returns client's desire to subscribe to a track, also true if autoSubscribe is enabled
20261
- get isDesired() {
20262
- return this.subscribed !== false;
20263
- }
20264
- get isEnabled() {
20265
- return !this.disabled;
20472
+ setMetadata(metadata) {
20473
+ var _a;
20474
+ this.engine.client.sendUpdateLocalMetadata(metadata, (_a = this.name) !== null && _a !== void 0 ? _a : '');
20266
20475
  }
20267
20476
  /**
20268
- * disable server from sending down data for this track. this is useful when
20269
- * the participant is off screen, you may disable streaming down their video
20270
- * to reduce bandwidth requirements
20271
- * @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
20272
20482
  */
20273
- setEnabled(enabled) {
20274
- if (!this.isManualOperationAllowed() || this.disabled === !enabled) {
20275
- return;
20276
- }
20277
- this.disabled = !enabled;
20278
- this.emitTrackUpdate();
20483
+ setName(name) {
20484
+ var _a;
20485
+ this.engine.client.sendUpdateLocalMetadata((_a = this.metadata) !== null && _a !== void 0 ? _a : '', name);
20279
20486
  }
20280
20487
  /**
20281
- * for tracks that support simulcasting, adjust subscribed quality
20488
+ * Enable or disable a participant's camera track.
20282
20489
  *
20283
- * This indicates the highest quality the client can accept. if network
20284
- * bandwidth does not allow, server will automatically reduce quality to
20285
- * 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
20286
20492
  */
20287
- setVideoQuality(quality) {
20288
- if (!this.isManualOperationAllowed() || this.currentVideoQuality === quality) {
20289
- return;
20290
- }
20291
- this.currentVideoQuality = quality;
20292
- this.videoDimensions = undefined;
20293
- this.emitTrackUpdate();
20294
- }
20295
- setVideoDimensions(dimensions) {
20296
- var _a, _b;
20297
- if (!this.isManualOperationAllowed()) {
20298
- return;
20299
- }
20300
- 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) {
20301
- return;
20302
- }
20303
- if (this.track instanceof RemoteVideoTrack) {
20304
- this.videoDimensions = dimensions;
20305
- }
20306
- this.currentVideoQuality = undefined;
20307
- this.emitTrackUpdate();
20493
+ setCameraEnabled(enabled, options, publishOptions) {
20494
+ return this.setTrackEnabled(Track.Source.Camera, enabled, options, publishOptions);
20308
20495
  }
20309
- setVideoFPS(fps) {
20310
- if (!this.isManualOperationAllowed()) {
20311
- return;
20312
- }
20313
- if (!(this.track instanceof RemoteVideoTrack)) {
20314
- return;
20315
- }
20316
- if (this.fps === fps) {
20317
- return;
20318
- }
20319
- this.fps = fps;
20320
- 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);
20321
20504
  }
20322
- get videoQuality() {
20323
- 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);
20324
20511
  }
20325
20512
  /** @internal */
20326
- setTrack(track) {
20327
- const prevStatus = this.subscriptionStatus;
20328
- const prevPermission = this.permissionStatus;
20329
- const prevTrack = this.track;
20330
- if (prevTrack === track) {
20331
- return;
20332
- }
20333
- if (prevTrack) {
20334
- // unregister listener
20335
- prevTrack.off(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
20336
- prevTrack.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
20337
- prevTrack.off(TrackEvent.Ended, this.handleEnded);
20338
- prevTrack.detach();
20339
- prevTrack.stopMonitor();
20340
- this.emit(TrackEvent.Unsubscribed, prevTrack);
20341
- }
20342
- super.setTrack(track);
20343
- if (track) {
20344
- track.sid = this.trackSid;
20345
- track.on(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
20346
- track.on(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
20347
- track.on(TrackEvent.Ended, this.handleEnded);
20348
- 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);
20349
20518
  }
20350
- this.emitPermissionUpdateIfChanged(prevPermission);
20351
- this.emitSubscriptionUpdateIfChanged(prevStatus);
20352
- }
20353
- /** @internal */
20354
- setAllowed(allowed) {
20355
- const prevStatus = this.subscriptionStatus;
20356
- const prevPermission = this.permissionStatus;
20357
- this.allowed = allowed;
20358
- this.emitPermissionUpdateIfChanged(prevPermission);
20359
- this.emitSubscriptionUpdateIfChanged(prevStatus);
20360
- }
20361
- /** @internal */
20362
- setSubscriptionError(error) {
20363
- this.emit(TrackEvent.SubscriptionFailed, error);
20519
+ return changed;
20364
20520
  }
20365
20521
  /** @internal */
20366
- updateInfo(info) {
20367
- super.updateInfo(info);
20368
- const prevMetadataMuted = this.metadataMuted;
20369
- this.metadataMuted = info.muted;
20370
- if (this.track) {
20371
- this.track.setMuted(info.muted);
20372
- } else if (prevMetadataMuted !== info.muted) {
20373
- this.emit(info.muted ? TrackEvent.Muted : TrackEvent.Unmuted);
20374
- }
20375
- }
20376
- emitSubscriptionUpdateIfChanged(previousStatus) {
20377
- const currentStatus = this.subscriptionStatus;
20378
- if (previousStatus === currentStatus) {
20379
- return;
20380
- }
20381
- this.emit(TrackEvent.SubscriptionStatusChanged, currentStatus, previousStatus);
20382
- }
20383
- emitPermissionUpdateIfChanged(previousPermissionStatus) {
20384
- const currentPermissionStatus = this.permissionStatus;
20385
- if (currentPermissionStatus !== previousPermissionStatus) {
20386
- this.emit(TrackEvent.SubscriptionPermissionChanged, this.permissionStatus, previousPermissionStatus);
20387
- }
20388
- }
20389
- isManualOperationAllowed() {
20390
- if (this.kind === Track.Kind.Video && this.isAdaptiveStream) {
20391
- this.log.warn('adaptive stream is enabled, cannot change video track settings', this.logContext);
20392
- return false;
20393
- }
20394
- if (!this.isDesired) {
20395
- this.log.warn('cannot update track settings when not subscribed', this.logContext);
20396
- return false;
20397
- }
20398
- return true;
20399
- }
20400
- get isAdaptiveStream() {
20401
- return this.track instanceof RemoteVideoTrack && this.track.isAdaptiveStream;
20402
- }
20403
- /* @internal */
20404
- emitTrackUpdate() {
20405
- const settings = new UpdateTrackSettings({
20406
- trackSids: [this.trackSid],
20407
- disabled: this.disabled,
20408
- 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);
20409
20526
  });
20410
- if (this.videoDimensions) {
20411
- settings.width = Math.ceil(this.videoDimensions.width);
20412
- settings.height = Math.ceil(this.videoDimensions.height);
20413
- } else if (this.currentVideoQuality !== undefined) {
20414
- settings.quality = this.currentVideoQuality;
20415
- } else {
20416
- // defaults to high quality
20417
- settings.quality = VideoQuality.HIGH;
20418
- }
20419
- this.emit(TrackEvent.UpdateSettings, settings);
20420
- }
20421
- }
20422
-
20423
- class RemoteParticipant extends Participant {
20424
- /** @internal */
20425
- static fromParticipantInfo(signalClient, pi) {
20426
- return new RemoteParticipant(signalClient, pi.sid, pi.identity, pi.name, pi.metadata);
20427
- }
20428
- /** @internal */
20429
- constructor(signalClient, sid, identity, name, metadata, loggerOptions) {
20430
- super(sid, identity || '', name, metadata, loggerOptions);
20431
- this.signalClient = signalClient;
20432
- this.tracks = new Map();
20433
- this.audioTracks = new Map();
20434
- this.videoTracks = new Map();
20435
- this.volumeMap = new Map();
20436
20527
  }
20437
- addTrackPublication(publication) {
20438
- super.addTrackPublication(publication);
20439
- // register action events
20440
- publication.on(TrackEvent.UpdateSettings, settings => {
20441
- this.log.debug('send update settings', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
20442
- this.signalClient.sendUpdateTrackSettings(settings);
20443
- });
20444
- publication.on(TrackEvent.UpdateSubscription, sub => {
20445
- sub.participantTracks.forEach(pt => {
20446
- pt.participantSid = this.sid;
20447
- });
20448
- this.signalClient.sendUpdateSubscription(sub);
20449
- });
20450
- publication.on(TrackEvent.SubscriptionPermissionChanged, status => {
20451
- this.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, publication, status);
20452
- });
20453
- publication.on(TrackEvent.SubscriptionStatusChanged, status => {
20454
- this.emit(ParticipantEvent.TrackSubscriptionStatusChanged, publication, status);
20455
- });
20456
- publication.on(TrackEvent.Subscribed, track => {
20457
- this.emit(ParticipantEvent.TrackSubscribed, track, publication);
20458
- });
20459
- publication.on(TrackEvent.Unsubscribed, previousTrack => {
20460
- this.emit(ParticipantEvent.TrackUnsubscribed, previousTrack, publication);
20461
- });
20462
- publication.on(TrackEvent.SubscriptionFailed, error => {
20463
- this.emit(ParticipantEvent.TrackSubscriptionFailed, publication.trackSid, error);
20464
- });
20465
- }
20466
- getTrack(source) {
20467
- const track = super.getTrack(source);
20468
- if (track) {
20469
- return track;
20470
- }
20471
- }
20472
- getTrackByName(name) {
20473
- const track = super.getTrackByName(name);
20474
- 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
+ }
20475
20600
  return track;
20476
- }
20601
+ });
20477
20602
  }
20478
20603
  /**
20479
- * sets the volume on the participant's audio track
20480
- * by default, this affects the microphone publication
20481
- * a different source can be passed in as a second argument
20482
- * 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.
20483
20606
  */
20484
- setVolume(volume) {
20485
- let source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Track.Source.Microphone;
20486
- this.volumeMap.set(source, volume);
20487
- const audioPublication = this.getTrack(source);
20488
- if (audioPublication && audioPublication.track) {
20489
- audioPublication.track.setVolume(volume);
20490
- }
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
+ });
20491
20626
  }
20492
20627
  /**
20493
- * gets the volume on the participant's microphone track
20628
+ * Create local camera and/or microphone tracks
20629
+ * @param options
20630
+ * @returns
20494
20631
  */
20495
- getVolume() {
20496
- let source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Track.Source.Microphone;
20497
- const audioPublication = this.getTrack(source);
20498
- if (audioPublication && audioPublication.track) {
20499
- return audioPublication.track.getVolume();
20500
- }
20501
- return this.volumeMap.get(source);
20502
- }
20503
- /** @internal */
20504
- addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft) {
20505
- // find the track publication
20506
- // it's possible for the media track to arrive before participant info
20507
- let publication = this.getTrackPublication(sid);
20508
- // it's also possible that the browser didn't honor our original track id
20509
- // FireFox would use its own local uuid instead of server track id
20510
- if (!publication) {
20511
- if (!sid.startsWith('TR')) {
20512
- // find the first track that matches type
20513
- this.tracks.forEach(p => {
20514
- if (!publication && mediaTrack.kind === p.kind.toString()) {
20515
- 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;
20516
20644
  }
20517
- });
20645
+ if (constraints.video) {
20646
+ this.cameraError = err;
20647
+ }
20648
+ }
20649
+ throw err;
20518
20650
  }
20519
- }
20520
- // when we couldn't locate the track, it's possible that the metadata hasn't
20521
- // yet arrived. Wait a bit longer for it to arrive, or fire an error
20522
- if (!publication) {
20523
- if (triesLeft === 0) {
20524
- this.log.error('could not find published track', Object.assign(Object.assign({}, this.logContext), {
20525
- trackSid: sid
20526
- }));
20527
- this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
20528
- return;
20651
+ if (constraints.audio) {
20652
+ this.microphoneError = undefined;
20653
+ this.emit(ParticipantEvent.AudioStreamAcquired);
20529
20654
  }
20530
- if (triesLeft === undefined) triesLeft = 20;
20531
- setTimeout(() => {
20532
- this.addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft - 1);
20533
- }, 150);
20534
- return;
20535
- }
20536
- if (mediaTrack.readyState === 'ended') {
20537
- this.log.error('unable to subscribe because MediaStreamTrack is ended. Do not call MediaStreamTrack.stop()', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
20538
- this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
20539
- return;
20540
- }
20541
- const isVideo = mediaTrack.kind === 'video';
20542
- let track;
20543
- if (isVideo) {
20544
- track = new RemoteVideoTrack(mediaTrack, sid, receiver, adaptiveStreamSettings);
20545
- } else {
20546
- track = new RemoteAudioTrack(mediaTrack, sid, receiver, this.audioContext, this.audioOutput);
20547
- }
20548
- // set track info
20549
- track.source = publication.source;
20550
- // keep publication's muted status
20551
- track.isMuted = publication.isMuted;
20552
- track.setMediaStream(mediaStream);
20553
- track.start();
20554
- publication.setTrack(track);
20555
- // set participant volumes on new audio tracks
20556
- if (this.volumeMap.has(publication.source) && track instanceof RemoteAudioTrack) {
20557
- track.setVolume(this.volumeMap.get(publication.source));
20558
- }
20559
- return publication;
20560
- }
20561
- /** @internal */
20562
- get hasMetadata() {
20563
- return !!this.participantInfo;
20564
- }
20565
- getTrackPublication(sid) {
20566
- return this.tracks.get(sid);
20567
- }
20568
- /** @internal */
20569
- updateInfo(info) {
20570
- if (!super.updateInfo(info)) {
20571
- return false;
20572
- }
20573
- // we are getting a list of all available tracks, reconcile in here
20574
- // and send out events for changes
20575
- // reconcile track publications, publish events only if metadata is already there
20576
- // i.e. changes since the local participant has joined
20577
- const validTracks = new Map();
20578
- const newTracks = new Map();
20579
- info.tracks.forEach(ti => {
20580
- var _a, _b;
20581
- let publication = this.getTrackPublication(ti.sid);
20582
- if (!publication) {
20583
- // new publication
20584
- const kind = Track.kindFromProto(ti.type);
20585
- if (!kind) {
20586
- 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;
20587
20665
  }
20588
- publication = new RemoteTrackPublication(kind, ti, (_a = this.signalClient.connectOptions) === null || _a === void 0 ? void 0 : _a.autoSubscribe, {
20589
- loggerContextCb: () => this.logContext,
20590
- loggerName: (_b = this.loggerOptions) === null || _b === void 0 ? void 0 : _b.loggerName
20666
+ const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints, {
20667
+ loggerName: this.roomOptions.loggerName,
20668
+ loggerContextCb: () => this.logContext
20591
20669
  });
20592
- publication.updateInfo(ti);
20593
- newTracks.set(ti.sid, publication);
20594
- const existingTrackOfSource = Array.from(this.tracks.values()).find(publishedTrack => publishedTrack.source === (publication === null || publication === void 0 ? void 0 : publication.source));
20595
- if (existingTrackOfSource && publication.source !== Track.Source.Unknown) {
20596
- 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), {
20597
- oldTrack: getLogContextFromTrack(existingTrackOfSource),
20598
- newTrack: getLogContextFromTrack(publication)
20599
- }));
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;
20600
20674
  }
20601
- this.addTrackPublication(publication);
20602
- } else {
20603
- publication.updateInfo(ti);
20604
- }
20605
- validTracks.set(ti.sid, publication);
20606
- });
20607
- // detect removed tracks
20608
- this.tracks.forEach(publication => {
20609
- if (!validTracks.has(publication.trackSid)) {
20610
- this.log.trace('detected removed track on remote participant, unpublishing', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
20611
- this.unpublishTrack(publication.trackSid, true);
20612
- }
20613
- });
20614
- // always emit events for new publications, Room will not forward them unless it's ready
20615
- newTracks.forEach(publication => {
20616
- this.emit(ParticipantEvent.TrackPublished, publication);
20675
+ track.mediaStream = stream;
20676
+ return track;
20677
+ });
20617
20678
  });
20618
- return true;
20619
- }
20620
- /** @internal */
20621
- unpublishTrack(sid, sendUnpublish) {
20622
- const publication = this.tracks.get(sid);
20623
- if (!publication) {
20624
- return;
20625
- }
20626
- // also send unsubscribe, if track is actively subscribed
20627
- const {
20628
- track
20629
- } = publication;
20630
- if (track) {
20631
- track.stop();
20632
- publication.setTrack(undefined);
20633
- }
20634
- // remove track from maps only after unsubscribed has been fired
20635
- this.tracks.delete(sid);
20636
- // remove from the right type map
20637
- switch (publication.kind) {
20638
- case Track.Kind.Audio:
20639
- this.audioTracks.delete(sid);
20640
- break;
20641
- case Track.Kind.Video:
20642
- this.videoTracks.delete(sid);
20643
- break;
20644
- }
20645
- if (sendUnpublish) {
20646
- this.emit(ParticipantEvent.TrackUnpublished, publication);
20647
- }
20648
20679
  }
20649
20680
  /**
20650
- * @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.
20651
20684
  */
20652
- setAudioOutput(output) {
20685
+ createScreenTracks(options) {
20653
20686
  return __awaiter(this, void 0, void 0, function* () {
20654
- this.audioOutput = output;
20655
- const promises = [];
20656
- this.audioTracks.forEach(pub => {
20657
- var _a;
20658
- if (pub.track instanceof RemoteAudioTrack) {
20659
- promises.push(pub.track.setSinkId((_a = output.deviceId) !== null && _a !== void 0 ? _a : 'default'));
20660
- }
20661
- });
20662
- yield Promise.all(promises);
20663
- });
20664
- }
20665
- /** @internal */
20666
- emit(event) {
20667
- for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
20668
- args[_key - 1] = arguments[_key];
20669
- }
20670
- this.log.trace('participant event', Object.assign(Object.assign({}, this.logContext), {
20671
- event,
20672
- args
20673
- }));
20674
- return super.emit(event, ...args);
20675
- }
20676
- }
20677
-
20678
- class LocalParticipant extends Participant {
20679
- /** @internal */
20680
- constructor(sid, identity, engine, options) {
20681
- super(sid, identity, undefined, undefined, {
20682
- loggerName: options.loggerName,
20683
- loggerContextCb: () => this.engine.logContext
20684
- });
20685
- this.pendingPublishing = new Set();
20686
- this.pendingPublishPromises = new Map();
20687
- this.participantTrackPermissions = [];
20688
- this.allParticipantsAllowedToSubscribe = true;
20689
- this.encryptionType = Encryption_Type.NONE;
20690
- this.handleReconnecting = () => {
20691
- if (!this.reconnectFuture) {
20692
- this.reconnectFuture = new Future();
20687
+ if (options === undefined) {
20688
+ options = {};
20693
20689
  }
20694
- };
20695
- this.handleReconnected = () => {
20696
- var _a, _b;
20697
- (_b = (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.resolve) === null || _b === void 0 ? void 0 : _b.call(_a);
20698
- this.reconnectFuture = undefined;
20699
- this.updateTrackSubscriptionPermissions();
20700
- };
20701
- this.handleDisconnected = () => {
20702
- var _a, _b;
20703
- if (this.reconnectFuture) {
20704
- this.reconnectFuture.promise.catch(e => this.log.warn(e.message, this.logContext));
20705
- (_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');
20706
- this.reconnectFuture = undefined;
20690
+ if (navigator.mediaDevices.getDisplayMedia === undefined) {
20691
+ throw new DeviceUnsupportedError('getDisplayMedia not supported');
20707
20692
  }
20708
- };
20709
- this.updateTrackSubscriptionPermissions = () => {
20710
- this.log.debug('updating track subscription permissions', Object.assign(Object.assign({}, this.logContext), {
20711
- allParticipantsAllowed: this.allParticipantsAllowedToSubscribe,
20712
- participantTrackPermissions: this.participantTrackPermissions
20713
- }));
20714
- this.engine.client.sendUpdateSubscriptionPermissions(this.allParticipantsAllowedToSubscribe, this.participantTrackPermissions.map(p => trackPermissionToProto(p)));
20715
- };
20716
- /** @internal */
20717
- this.onTrackUnmuted = track => {
20718
- this.onTrackMuted(track, track.isUpstreamPaused);
20719
- };
20720
- // when the local track changes in mute status, we'll notify server as such
20721
- /** @internal */
20722
- this.onTrackMuted = (track, muted) => {
20723
- if (muted === undefined) {
20724
- 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;
20725
20698
  }
20726
- if (!track.sid) {
20727
- this.log.error('could not update mute status for unpublished track', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20728
- 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');
20729
20704
  }
20730
- this.engine.updateMuteStatus(track.sid, muted);
20731
- };
20732
- this.onTrackUpstreamPaused = track => {
20733
- this.log.debug('upstream paused', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20734
- this.onTrackMuted(track, true);
20735
- };
20736
- this.onTrackUpstreamResumed = track => {
20737
- this.log.debug('upstream resumed', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20738
- this.onTrackMuted(track, track.isMuted);
20739
- };
20740
- this.handleSubscribedQualityUpdate = update => __awaiter(this, void 0, void 0, function* () {
20741
- var _a, e_1, _b, _c;
20742
- var _d, _e;
20743
- if (!((_d = this.roomOptions) === null || _d === void 0 ? void 0 : _d.dynacast)) {
20744
- 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;
20745
20712
  }
20746
- const pub = this.videoTracks.get(update.trackSid);
20747
- if (!pub) {
20748
- this.log.warn('received subscribed quality update for unknown track', Object.assign(Object.assign({}, this.logContext), {
20749
- trackSid: update.trackSid
20750
- }));
20751
- 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);
20752
20722
  }
20753
- if (update.subscribedCodecs.length > 0) {
20754
- if (!pub.videoTrack) {
20755
- 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';
20756
20752
  }
20757
- const newCodecs = yield pub.videoTrack.setPublishingCodecs(update.subscribedCodecs);
20758
- try {
20759
- 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) {
20760
- _c = newCodecs_1_1.value;
20761
- _f = false;
20762
- const codec = _c;
20763
- if (isBackupCodec(codec)) {
20764
- this.log.debug("publish ".concat(codec, " for ").concat(pub.videoTrack.sid), Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(pub)));
20765
- yield this.publishAdditionalCodecForTrack(pub.videoTrack, codec, pub.options);
20766
- }
20767
- }
20768
- } catch (e_1_1) {
20769
- e_1 = {
20770
- error: e_1_1
20771
- };
20772
- } finally {
20773
- try {
20774
- if (!_f && !_a && (_b = newCodecs_1.return)) yield _b.call(newCodecs_1);
20775
- } finally {
20776
- if (e_1) throw e_1.error;
20777
- }
20778
- }
20779
- } else if (update.subscribedQualities.length > 0) {
20780
- yield (_e = pub.videoTrack) === null || _e === void 0 ? void 0 : _e.setPublishingLayers(update.subscribedQualities);
20781
- }
20782
- });
20783
- this.handleLocalTrackUnpublished = unpublished => {
20784
- const track = this.tracks.get(unpublished.trackSid);
20785
- if (!track) {
20786
- this.log.warn('received unpublished event for unknown track', Object.assign(Object.assign({}, this.logContext), {
20787
- trackSid: unpublished.trackSid
20788
- }));
20789
- return;
20790
- }
20791
- this.unpublishTrack(track.track);
20792
- };
20793
- this.handleTrackEnded = track => __awaiter(this, void 0, void 0, function* () {
20794
- if (track.source === Track.Source.ScreenShare || track.source === Track.Source.ScreenShareAudio) {
20795
- this.log.debug('unpublishing local track due to TrackEnded', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20796
- this.unpublishTrack(track);
20797
- } else if (track.isUserProvided) {
20798
- yield track.mute();
20799
- } else if (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) {
20800
- try {
20801
- if (isWeb()) {
20802
- try {
20803
- const currentPermissions = yield navigator === null || navigator === void 0 ? void 0 : navigator.permissions.query({
20804
- // the permission query for camera and microphone currently not supported in Safari and Firefox
20805
- // @ts-ignore
20806
- name: track.source === Track.Source.Camera ? 'camera' : 'microphone'
20807
- });
20808
- if (currentPermissions && currentPermissions.state === 'denied') {
20809
- this.log.warn("user has revoked access to ".concat(track.source), Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20810
- // detect granted change after permissions were denied to try and resume then
20811
- currentPermissions.onchange = () => {
20812
- if (currentPermissions.state !== 'denied') {
20813
- if (!track.isMuted) {
20814
- track.restartTrack();
20815
- }
20816
- currentPermissions.onchange = null;
20817
- }
20818
- };
20819
- throw new Error('GetUserMedia Permission denied');
20820
- }
20821
- } catch (e) {
20822
- // permissions query fails for firefox, we continue and try to restart the track
20823
- }
20824
- }
20825
- if (!track.isMuted) {
20826
- this.log.debug('track ended, attempting to use a different device', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20827
- yield track.restartTrack();
20828
- }
20829
- } catch (e) {
20830
- this.log.warn("could not restart track, muting instead", Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20831
- yield track.mute();
20832
- }
20833
- }
20834
- });
20835
- this.audioTracks = new Map();
20836
- this.videoTracks = new Map();
20837
- this.tracks = new Map();
20838
- this.engine = engine;
20839
- this.roomOptions = options;
20840
- this.setupEngine(engine);
20841
- this.activeDeviceMap = new Map();
20842
- }
20843
- get lastCameraError() {
20844
- return this.cameraError;
20845
- }
20846
- get lastMicrophoneError() {
20847
- return this.microphoneError;
20848
- }
20849
- get isE2EEEnabled() {
20850
- return this.encryptionType !== Encryption_Type.NONE;
20851
- }
20852
- getTrack(source) {
20853
- const track = super.getTrack(source);
20854
- if (track) {
20855
- return track;
20856
- }
20857
- }
20858
- getTrackByName(name) {
20859
- const track = super.getTrackByName(name);
20860
- if (track) {
20861
- return track;
20862
- }
20863
- }
20864
- /**
20865
- * @internal
20866
- */
20867
- setupEngine(engine) {
20868
- this.engine = engine;
20869
- this.engine.on(EngineEvent.RemoteMute, (trackSid, muted) => {
20870
- const pub = this.tracks.get(trackSid);
20871
- if (!pub || !pub.track) {
20872
- return;
20873
- }
20874
- if (muted) {
20875
- pub.mute();
20876
- } else {
20877
- pub.unmute();
20878
- }
20879
- });
20880
- 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);
20881
- }
20882
- /**
20883
- * Sets and updates the metadata of the local participant.
20884
- * The change does not take immediate effect.
20885
- * If successful, a `ParticipantEvent.MetadataChanged` event will be emitted on the local participant.
20886
- * Note: this requires `canUpdateOwnMetadata` permission.
20887
- * @param metadata
20888
- */
20889
- setMetadata(metadata) {
20890
- var _a;
20891
- this.engine.client.sendUpdateLocalMetadata(metadata, (_a = this.name) !== null && _a !== void 0 ? _a : '');
20892
- }
20893
- /**
20894
- * Sets and updates the name of the local participant.
20895
- * The change does not take immediate effect.
20896
- * If successful, a `ParticipantEvent.ParticipantNameChanged` event will be emitted on the local participant.
20897
- * Note: this requires `canUpdateOwnMetadata` permission.
20898
- * @param metadata
20899
- */
20900
- setName(name) {
20901
- var _a;
20902
- this.engine.client.sendUpdateLocalMetadata((_a = this.metadata) !== null && _a !== void 0 ? _a : '', name);
20903
- }
20904
- /**
20905
- * Enable or disable a participant's camera track.
20906
- *
20907
- * If a track has already published, it'll mute or unmute the track.
20908
- * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20909
- */
20910
- setCameraEnabled(enabled, options, publishOptions) {
20911
- return this.setTrackEnabled(Track.Source.Camera, enabled, options, publishOptions);
20912
- }
20913
- /**
20914
- * Enable or disable a participant's microphone track.
20915
- *
20916
- * If a track has already published, it'll mute or unmute the track.
20917
- * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20918
- */
20919
- setMicrophoneEnabled(enabled, options, publishOptions) {
20920
- return this.setTrackEnabled(Track.Source.Microphone, enabled, options, publishOptions);
20921
- }
20922
- /**
20923
- * Start or stop sharing a participant's screen
20924
- * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20925
- */
20926
- setScreenShareEnabled(enabled, options, publishOptions) {
20927
- return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options, publishOptions);
20928
- }
20929
- /** @internal */
20930
- setPermissions(permissions) {
20931
- const prevPermissions = this.permissions;
20932
- const changed = super.setPermissions(permissions);
20933
- if (changed && prevPermissions) {
20934
- this.emit(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions);
20935
- }
20936
- return changed;
20937
- }
20938
- /** @internal */
20939
- setE2EEEnabled(enabled) {
20940
- return __awaiter(this, void 0, void 0, function* () {
20941
- this.encryptionType = enabled ? Encryption_Type.GCM : Encryption_Type.NONE;
20942
- yield this.republishAllTracks(undefined, false);
20943
- });
20944
- }
20945
- setTrackEnabled(source, enabled, options, publishOptions) {
20946
- var _a, _b;
20947
- return __awaiter(this, void 0, void 0, function* () {
20948
- this.log.debug('setTrackEnabled', Object.assign(Object.assign({}, this.logContext), {
20949
- source,
20950
- enabled
20951
- }));
20952
- let track = this.getTrack(source);
20953
- if (enabled) {
20954
- if (track) {
20955
- yield track.unmute();
20956
- } else {
20957
- let localTracks;
20958
- if (this.pendingPublishing.has(source)) {
20959
- this.log.info('skipping duplicate published source', Object.assign(Object.assign({}, this.logContext), {
20960
- source
20961
- }));
20962
- // no-op it's already been requested
20963
- return;
20964
- }
20965
- this.pendingPublishing.add(source);
20966
- try {
20967
- switch (source) {
20968
- case Track.Source.Camera:
20969
- localTracks = yield this.createTracks({
20970
- video: (_a = options) !== null && _a !== void 0 ? _a : true
20971
- });
20972
- break;
20973
- case Track.Source.Microphone:
20974
- localTracks = yield this.createTracks({
20975
- audio: (_b = options) !== null && _b !== void 0 ? _b : true
20976
- });
20977
- break;
20978
- case Track.Source.ScreenShare:
20979
- localTracks = yield this.createScreenTracks(Object.assign({}, options));
20980
- break;
20981
- default:
20982
- throw new TrackInvalidError(source);
20983
- }
20984
- const publishPromises = [];
20985
- for (const localTrack of localTracks) {
20986
- this.log.info('publishing track', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(localTrack)));
20987
- publishPromises.push(this.publishTrack(localTrack, publishOptions));
20988
- }
20989
- const publishedTracks = yield Promise.all(publishPromises);
20990
- // for screen share publications including audio, this will only return the screen share publication, not the screen share audio one
20991
- // revisit if we want to return an array of tracks instead for v2
20992
- [track] = publishedTracks;
20993
- } catch (e) {
20994
- localTracks === null || localTracks === void 0 ? void 0 : localTracks.forEach(tr => {
20995
- tr.stop();
20996
- });
20997
- if (e instanceof Error && !(e instanceof TrackInvalidError)) {
20998
- this.emit(ParticipantEvent.MediaDevicesError, e);
20999
- }
21000
- throw e;
21001
- } finally {
21002
- this.pendingPublishing.delete(source);
21003
- }
21004
- }
21005
- } else if (track && track.track) {
21006
- // screenshare cannot be muted, unpublish instead
21007
- if (source === Track.Source.ScreenShare) {
21008
- track = yield this.unpublishTrack(track.track);
21009
- const screenAudioTrack = this.getTrack(Track.Source.ScreenShareAudio);
21010
- if (screenAudioTrack && screenAudioTrack.track) {
21011
- this.unpublishTrack(screenAudioTrack.track);
21012
- }
21013
- } else {
21014
- yield track.mute();
21015
- }
21016
- }
21017
- return track;
21018
- });
21019
- }
21020
- /**
21021
- * Publish both camera and microphone at the same time. This is useful for
21022
- * displaying a single Permission Dialog box to the end user.
21023
- */
21024
- enableCameraAndMicrophone() {
21025
- return __awaiter(this, void 0, void 0, function* () {
21026
- if (this.pendingPublishing.has(Track.Source.Camera) || this.pendingPublishing.has(Track.Source.Microphone)) {
21027
- // no-op it's already been requested
21028
- return;
21029
- }
21030
- this.pendingPublishing.add(Track.Source.Camera);
21031
- this.pendingPublishing.add(Track.Source.Microphone);
21032
- try {
21033
- const tracks = yield this.createTracks({
21034
- audio: true,
21035
- video: true
21036
- });
21037
- yield Promise.all(tracks.map(track => this.publishTrack(track)));
21038
- } finally {
21039
- this.pendingPublishing.delete(Track.Source.Camera);
21040
- this.pendingPublishing.delete(Track.Source.Microphone);
21041
- }
21042
- });
21043
- }
21044
- /**
21045
- * Create local camera and/or microphone tracks
21046
- * @param options
21047
- * @returns
21048
- */
21049
- createTracks(options) {
21050
- var _a, _b;
21051
- return __awaiter(this, void 0, void 0, function* () {
21052
- 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);
21053
- const constraints = constraintsForOptions(opts);
21054
- let stream;
21055
- try {
21056
- stream = yield navigator.mediaDevices.getUserMedia(constraints);
21057
- } catch (err) {
21058
- if (err instanceof Error) {
21059
- if (constraints.audio) {
21060
- this.microphoneError = err;
21061
- }
21062
- if (constraints.video) {
21063
- this.cameraError = err;
21064
- }
21065
- }
21066
- throw err;
21067
- }
21068
- if (constraints.audio) {
21069
- this.microphoneError = undefined;
21070
- this.emit(ParticipantEvent.AudioStreamAcquired);
21071
- }
21072
- if (constraints.video) {
21073
- this.cameraError = undefined;
21074
- }
21075
- return stream.getTracks().map(mediaStreamTrack => {
21076
- const isAudio = mediaStreamTrack.kind === 'audio';
21077
- isAudio ? options.audio : options.video;
21078
- let trackConstraints;
21079
- const conOrBool = isAudio ? constraints.audio : constraints.video;
21080
- if (typeof conOrBool !== 'boolean') {
21081
- trackConstraints = conOrBool;
21082
- }
21083
- const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints, {
21084
- loggerName: this.roomOptions.loggerName,
21085
- loggerContextCb: () => this.logContext
21086
- });
21087
- if (track.kind === Track.Kind.Video) {
21088
- track.source = Track.Source.Camera;
21089
- } else if (track.kind === Track.Kind.Audio) {
21090
- track.source = Track.Source.Microphone;
21091
- }
21092
- track.mediaStream = stream;
21093
- return track;
21094
- });
21095
- });
21096
- }
21097
- /**
21098
- * Creates a screen capture tracks with getDisplayMedia().
21099
- * A LocalVideoTrack is always created and returned.
21100
- * If { audio: true }, and the browser supports audio capture, a LocalAudioTrack is also created.
21101
- */
21102
- createScreenTracks(options) {
21103
- return __awaiter(this, void 0, void 0, function* () {
21104
- if (options === undefined) {
21105
- options = {};
21106
- }
21107
- if (navigator.mediaDevices.getDisplayMedia === undefined) {
21108
- throw new DeviceUnsupportedError('getDisplayMedia not supported');
21109
- }
21110
- if (options.resolution === undefined && !isSafari17()) {
21111
- // we need to constrain the dimensions, otherwise it could lead to low bitrate
21112
- // due to encoding a huge video. Encoding such large surfaces is really expensive
21113
- // unfortunately Safari 17 has a but and cannot be constrained by default
21114
- options.resolution = ScreenSharePresets.h1080fps30.resolution;
21115
- }
21116
- const constraints = screenCaptureToDisplayMediaStreamOptions(options);
21117
- const stream = yield navigator.mediaDevices.getDisplayMedia(constraints);
21118
- const tracks = stream.getVideoTracks();
21119
- if (tracks.length === 0) {
21120
- throw new TrackInvalidError('no video track found');
21121
- }
21122
- const screenVideo = new LocalVideoTrack(tracks[0], undefined, false, {
21123
- loggerName: this.roomOptions.loggerName,
21124
- loggerContextCb: () => this.logContext
21125
- });
21126
- screenVideo.source = Track.Source.ScreenShare;
21127
- if (options.contentHint) {
21128
- screenVideo.mediaStreamTrack.contentHint = options.contentHint;
21129
- }
21130
- const localTracks = [screenVideo];
21131
- if (stream.getAudioTracks().length > 0) {
21132
- this.emit(ParticipantEvent.AudioStreamAcquired);
21133
- const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0], undefined, false, this.audioContext, {
21134
- loggerName: this.roomOptions.loggerName,
21135
- loggerContextCb: () => this.logContext
21136
- });
21137
- screenAudio.source = Track.Source.ScreenShareAudio;
21138
- localTracks.push(screenAudio);
21139
- }
21140
- return localTracks;
21141
- });
21142
- }
21143
- /**
21144
- * Publish a new track to the room
21145
- * @param track
21146
- * @param options
21147
- */
21148
- publishTrack(track, options) {
21149
- var _a, _b, _c, _d;
21150
- return __awaiter(this, void 0, void 0, function* () {
21151
- yield (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.promise;
21152
- if (track instanceof LocalTrack && this.pendingPublishPromises.has(track)) {
21153
- yield this.pendingPublishPromises.get(track);
21154
- }
21155
- let defaultConstraints;
21156
- if (track instanceof MediaStreamTrack) {
21157
- defaultConstraints = track.getConstraints();
21158
- } else {
21159
- // we want to access constraints directly as `track.mediaStreamTrack`
21160
- // might be pointing to a non-device track (e.g. processed track) already
21161
- defaultConstraints = track.constraints;
21162
- let deviceKind = undefined;
21163
- switch (track.source) {
21164
- case Track.Source.Microphone:
21165
- deviceKind = 'audioinput';
21166
- break;
21167
- case Track.Source.Camera:
21168
- deviceKind = 'videoinput';
21169
- }
21170
- if (deviceKind && this.activeDeviceMap.has(deviceKind)) {
21171
- defaultConstraints = Object.assign(Object.assign({}, defaultConstraints), {
21172
- deviceId: this.activeDeviceMap.get(deviceKind)
21173
- });
20753
+ if (deviceKind && this.activeDeviceMap.has(deviceKind)) {
20754
+ defaultConstraints = Object.assign(Object.assign({}, defaultConstraints), {
20755
+ deviceId: this.activeDeviceMap.get(deviceKind)
20756
+ });
21174
20757
  }
21175
20758
  }
21176
20759
  // convert raw media track into audio or video track
@@ -21202,7 +20785,7 @@ class LocalParticipant extends Participant {
21202
20785
  }
21203
20786
  // is it already published? if so skip
21204
20787
  let existingPublication;
21205
- this.tracks.forEach(publication => {
20788
+ this.trackPublications.forEach(publication => {
21206
20789
  if (!publication.track) {
21207
20790
  return;
21208
20791
  }
@@ -21254,9 +20837,9 @@ class LocalParticipant extends Participant {
21254
20837
  });
21255
20838
  }
21256
20839
  publish(track, opts, isStereo) {
21257
- 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;
21258
20841
  return __awaiter(this, void 0, void 0, function* () {
21259
- 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);
21260
20843
  if (existingTrackOfSource && track.source !== Track.Source.Unknown) {
21261
20844
  this.log.info("publishing a second track with the same source: ".concat(track.source), Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21262
20845
  }
@@ -21287,438 +20870,933 @@ class LocalParticipant extends Participant {
21287
20870
  track.on(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
21288
20871
  // create track publication from track
21289
20872
  const req = new AddTrackRequest({
21290
- // get local track id for use during publishing
21291
- cid: track.mediaStreamTrack.id,
21292
- 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,
21293
21075
  type: Track.kindToProto(track.kind),
21294
21076
  muted: track.isMuted,
21295
21077
  source: Track.sourceToProto(track.source),
21296
- disableDtx: !((_a = opts.dtx) !== null && _a !== void 0 ? _a : true),
21297
- encryption: this.encryptionType,
21298
- stereo: isStereo,
21299
- disableRed: this.isE2EEEnabled || !((_b = opts.red) !== null && _b !== void 0 ? _b : true),
21300
- 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
+ }]
21301
21083
  });
21302
- // compute encodings and layers for video
21303
- let encodings;
21304
- if (track.kind === Track.Kind.Video) {
21305
- let dims = {
21306
- width: 0,
21307
- height: 0
21308
- };
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) {
21309
21124
  try {
21310
- dims = yield track.waitForDimensions();
21311
- } catch (e) {
21312
- // use defaults, it's quite painful for congestion control without simulcast
21313
- // so using default dims according to publish settings
21314
- const defaultRes = (_d = (_c = this.roomOptions.videoCaptureDefaults) === null || _c === void 0 ? void 0 : _c.resolution) !== null && _d !== void 0 ? _d : VideoPresets.h720.resolution;
21315
- dims = {
21316
- width: defaultRes.width,
21317
- height: defaultRes.height
21318
- };
21319
- // log failure
21320
- this.log.error('could not determine track dimensions, using defaults', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
21321
- dims
21322
- }));
21323
- }
21324
- // width and height should be defined for video
21325
- req.width = dims.width;
21326
- req.height = dims.height;
21327
- // for svc codecs, disable simulcast and use vp8 for backup codec
21328
- if (track instanceof LocalVideoTrack) {
21329
- if (isSVCCodec(videoCodec)) {
21330
- // vp9 svc with screenshare has problem to encode, always use L1T3 here
21331
- if (track.source === Track.Source.ScreenShare && videoCodec === 'vp9') {
21332
- 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;
21333
21133
  }
21334
- // set scalabilityMode to 'L3T3_KEY' by default
21335
- opts.scalabilityMode = (_e = opts.scalabilityMode) !== null && _e !== void 0 ? _e : 'L3T3_KEY';
21336
21134
  }
21337
- req.simulcastCodecs = [new SimulcastCodec({
21338
- codec: videoCodec,
21339
- cid: track.mediaStreamTrack.id
21340
- })];
21341
- // set up backup
21342
- if (opts.backupCodec === true) {
21343
- opts.backupCodec = {
21344
- codec: defaultVideoCodec
21345
- };
21135
+ if (this.engine.removeTrack(trackSender)) {
21136
+ negotiationNeeded = true;
21346
21137
  }
21347
- if (opts.backupCodec && videoCodec !== opts.backupCodec.codec &&
21348
- // TODO remove this once e2ee is supported for backup codecs
21349
- req.encryption === Encryption_Type.NONE) {
21350
- // multi-codec simulcast requires dynacast
21351
- if (!this.roomOptions.dynacast) {
21352
- 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
+ }
21353
21146
  }
21354
- req.simulcastCodecs.push(new SimulcastCodec({
21355
- codec: opts.backupCodec.codec,
21356
- cid: ''
21357
- }));
21147
+ track.simulcastCodecs.clear();
21358
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
+ }));
21359
21153
  }
21360
- encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts);
21361
- req.layers = videoLayersFromEncodings(req.width, req.height, encodings, isSVCCodec(opts.videoCodec));
21362
- } else if (track.kind === Track.Kind.Audio) {
21363
- encodings = [{
21364
- maxBitrate: (_g = (_f = opts.audioPreset) === null || _f === void 0 ? void 0 : _f.maxBitrate) !== null && _g !== void 0 ? _g : opts.audioBitrate,
21365
- priority: (_j = (_h = opts.audioPreset) === null || _h === void 0 ? void 0 : _h.priority) !== null && _j !== void 0 ? _j : 'high',
21366
- networkPriority: (_l = (_k = opts.audioPreset) === null || _k === void 0 ? void 0 : _k.priority) !== null && _l !== void 0 ? _l : 'high'
21367
- }];
21368
21154
  }
21369
- if (!this.engine || this.engine.isClosed) {
21370
- 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;
21371
21164
  }
21372
- const ti = yield this.engine.addTrack(req);
21373
- // server might not support the codec the client has requested, in that case, fallback
21374
- // to a supported codec
21375
- let primaryCodecMime;
21376
- ti.codecs.forEach(codec => {
21377
- if (primaryCodecMime === undefined) {
21378
- 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);
21379
21189
  }
21380
21190
  });
21381
- if (primaryCodecMime && track.kind === Track.Kind.Video) {
21382
- const updatedCodec = mimeTypeToVideoCodecString(primaryCodecMime);
21383
- if (updatedCodec !== videoCodec) {
21384
- this.log.debug('falling back to server selected codec', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
21385
- 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
21386
21199
  }));
21387
- /* @ts-ignore */
21388
- opts.videoCodec = updatedCodec;
21389
- // recompute encodings since bitrates/etc could have changed
21390
- encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts);
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
21280
+ }));
21281
+ this.engine.client.sendMuteTrack(ti.sid, mutedOnServer);
21391
21282
  }
21392
21283
  }
21393
- const publication = new LocalTrackPublication(track.kind, ti, track, {
21394
- loggerName: this.roomOptions.loggerName,
21395
- loggerContextCb: () => this.logContext
21396
- });
21397
- // save options for when it needs to be republished again
21398
- publication.options = opts;
21399
- track.sid = ti.sid;
21400
- if (!this.engine.pcManager) {
21401
- 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;
21402
21293
  }
21403
- this.log.debug("publishing ".concat(track.kind, " with encodings"), Object.assign(Object.assign({}, this.logContext), {
21404
- encodings,
21405
- trackInfo: ti
21406
- }));
21407
- track.sender = yield this.engine.createSender(track, opts, encodings);
21408
- if (encodings) {
21409
- if (isFireFox() && track.kind === Track.Kind.Audio) {
21410
- /* Refer to RFC https://datatracker.ietf.org/doc/html/rfc7587#section-6.1,
21411
- livekit-server uses maxaveragebitrate=510000 in the answer sdp to permit client to
21412
- publish high quality audio track. But firefox always uses this value as the actual
21413
- bitrates, causing the audio bitrates to rise to 510Kbps in any stereo case unexpectedly.
21414
- So the client need to modify maxaverragebitrates in answer sdp to user provided value to
21415
- fix the issue.
21416
- */
21417
- let trackTransceiver = undefined;
21418
- for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
21419
- if (transceiver.sender === track.sender) {
21420
- trackTransceiver = transceiver;
21421
- break;
21422
- }
21423
- }
21424
- if (trackTransceiver) {
21425
- this.engine.pcManager.publisher.setTrackCodecBitrate({
21426
- transceiver: trackTransceiver,
21427
- codec: 'opus',
21428
- maxbr: ((_m = encodings[0]) === null || _m === void 0 ? void 0 : _m.maxBitrate) ? encodings[0].maxBitrate / 1000 : 0
21429
- });
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;
21430
21299
  }
21431
- } else if (track.codec && isSVCCodec(track.codec) && ((_o = encodings[0]) === null || _o === void 0 ? void 0 : _o.maxBitrate)) {
21432
- this.engine.pcManager.publisher.setTrackCodecBitrate({
21433
- cid: req.cid,
21434
- codec: track.codec,
21435
- maxbr: encodings[0].maxBitrate / 1000
21436
- });
21437
21300
  }
21301
+ } else if (track === localTrack) {
21302
+ publication = pub;
21438
21303
  }
21439
- yield this.engine.negotiate();
21440
- if (track instanceof LocalVideoTrack) {
21441
- track.startMonitor(this.engine.client);
21442
- } else if (track instanceof LocalAudioTrack) {
21443
- track.startMonitor();
21444
- }
21445
- this.addTrackPublication(publication);
21446
- // send event for publication
21447
- this.emit(ParticipantEvent.LocalTrackPublished, publication);
21448
- return publication;
21449
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
+ }
21450
21497
  }
21451
- 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
+ }
21452
21520
  return true;
21453
21521
  }
21454
- /** @internal
21455
- * publish additional codec to existing track
21456
- */
21457
- publishAdditionalCodecForTrack(track, videoCodec, options) {
21458
- var _a;
21459
- return __awaiter(this, void 0, void 0, function* () {
21460
- // TODO remove once e2ee is supported for backup tracks
21461
- if (this.encryptionType !== Encryption_Type.NONE) {
21462
- return;
21463
- }
21464
- // is it not published? if so skip
21465
- let existingPublication;
21466
- this.tracks.forEach(publication => {
21467
- if (!publication.track) {
21468
- return;
21469
- }
21470
- if (publication.track === track) {
21471
- existingPublication = publication;
21472
- }
21473
- });
21474
- if (!existingPublication) {
21475
- throw new TrackInvalidError('track is not published');
21476
- }
21477
- if (!(track instanceof LocalVideoTrack)) {
21478
- throw new TrackInvalidError('track is not a video track');
21479
- }
21480
- const opts = Object.assign(Object.assign({}, (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.publishDefaults), options);
21481
- const encodings = computeTrackBackupEncodings(track, videoCodec, opts);
21482
- if (!encodings) {
21483
- this.log.info("backup codec has been disabled, ignoring request to add additional codec for track", Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21484
- return;
21485
- }
21486
- const simulcastTrack = track.addSimulcastTrack(videoCodec, encodings);
21487
- const req = new AddTrackRequest({
21488
- cid: simulcastTrack.mediaStreamTrack.id,
21489
- type: Track.kindToProto(track.kind),
21490
- muted: track.isMuted,
21491
- source: Track.sourceToProto(track.source),
21492
- sid: track.sid,
21493
- simulcastCodecs: [{
21494
- codec: opts.videoCodec,
21495
- cid: simulcastTrack.mediaStreamTrack.id
21496
- }]
21497
- });
21498
- req.layers = videoLayersFromEncodings(req.width, req.height, encodings);
21499
- if (!this.engine || this.engine.isClosed) {
21500
- throw new UnexpectedConnectionState('cannot publish track when not connected');
21501
- }
21502
- const ti = yield this.engine.addTrack(req);
21503
- yield this.engine.createSimulcastSender(track, simulcastTrack, opts, encodings);
21504
- yield this.engine.negotiate();
21505
- this.log.debug("published ".concat(videoCodec, " for track ").concat(track.sid), Object.assign(Object.assign({}, this.logContext), {
21506
- encodings,
21507
- trackInfo: ti
21508
- }));
21509
- });
21522
+ get isAdaptiveStream() {
21523
+ return this.track instanceof RemoteVideoTrack && this.track.isAdaptiveStream;
21510
21524
  }
21511
- unpublishTrack(track, stopOnUnpublish) {
21512
- var _a, _b;
21513
- return __awaiter(this, void 0, void 0, function* () {
21514
- // look through all published tracks to find the right ones
21515
- const publication = this.getPublicationForTrack(track);
21516
- const pubLogContext = publication ? getLogContextFromTrack(publication) : undefined;
21517
- this.log.debug('unpublishing track', Object.assign(Object.assign({}, this.logContext), pubLogContext));
21518
- if (!publication || !publication.track) {
21519
- this.log.warn('track was not unpublished because no publication was found', Object.assign(Object.assign({}, this.logContext), pubLogContext));
21520
- return undefined;
21521
- }
21522
- track = publication.track;
21523
- track.off(TrackEvent.Muted, this.onTrackMuted);
21524
- track.off(TrackEvent.Unmuted, this.onTrackUnmuted);
21525
- track.off(TrackEvent.Ended, this.handleTrackEnded);
21526
- track.off(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
21527
- track.off(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
21528
- if (stopOnUnpublish === undefined) {
21529
- stopOnUnpublish = (_b = (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.stopLocalTrackOnUnpublish) !== null && _b !== void 0 ? _b : true;
21530
- }
21531
- if (stopOnUnpublish) {
21532
- track.stop();
21533
- }
21534
- let negotiationNeeded = false;
21535
- const trackSender = track.sender;
21536
- track.sender = undefined;
21537
- if (this.engine.pcManager && this.engine.pcManager.currentState < PCTransportState.FAILED && trackSender) {
21538
- try {
21539
- for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
21540
- // if sender is not currently sending (after replaceTrack(null))
21541
- // removeTrack would have no effect.
21542
- // to ensure we end up successfully removing the track, manually set
21543
- // the transceiver to inactive
21544
- if (transceiver.sender === trackSender) {
21545
- transceiver.direction = 'inactive';
21546
- negotiationNeeded = true;
21547
- }
21548
- }
21549
- if (this.engine.removeTrack(trackSender)) {
21550
- negotiationNeeded = true;
21551
- }
21552
- if (track instanceof LocalVideoTrack) {
21553
- for (const [, trackInfo] of track.simulcastCodecs) {
21554
- if (trackInfo.sender) {
21555
- if (this.engine.removeTrack(trackInfo.sender)) {
21556
- negotiationNeeded = true;
21557
- }
21558
- trackInfo.sender = undefined;
21559
- }
21560
- }
21561
- track.simulcastCodecs.clear();
21562
- }
21563
- } catch (e) {
21564
- this.log.warn('failed to unpublish track', Object.assign(Object.assign(Object.assign({}, this.logContext), pubLogContext), {
21565
- error: e
21566
- }));
21567
- }
21568
- }
21569
- // remove from our maps
21570
- this.tracks.delete(publication.trackSid);
21571
- switch (publication.kind) {
21572
- case Track.Kind.Audio:
21573
- this.audioTracks.delete(publication.trackSid);
21574
- break;
21575
- case Track.Kind.Video:
21576
- this.videoTracks.delete(publication.trackSid);
21577
- break;
21578
- }
21579
- this.emit(ParticipantEvent.LocalTrackUnpublished, publication);
21580
- publication.setTrack(undefined);
21581
- if (negotiationNeeded) {
21582
- yield this.engine.negotiate();
21583
- }
21584
- return publication;
21525
+ /* @internal */
21526
+ emitTrackUpdate() {
21527
+ const settings = new UpdateTrackSettings({
21528
+ trackSids: [this.trackSid],
21529
+ disabled: this.disabled,
21530
+ fps: this.fps
21585
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);
21586
21542
  }
21587
- unpublishTracks(tracks) {
21588
- return __awaiter(this, void 0, void 0, function* () {
21589
- const results = yield Promise.all(tracks.map(track => this.unpublishTrack(track)));
21590
- return results.filter(track => track instanceof LocalTrackPublication);
21591
- });
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);
21592
21549
  }
21593
- republishAllTracks(options) {
21594
- let restartTracks = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
21595
- return __awaiter(this, void 0, void 0, function* () {
21596
- const localPubs = [];
21597
- this.tracks.forEach(pub => {
21598
- if (pub.track) {
21599
- if (options) {
21600
- pub.options = Object.assign(Object.assign({}, pub.options), options);
21601
- }
21602
- localPubs.push(pub);
21603
- }
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;
21604
21569
  });
21605
- yield Promise.all(localPubs.map(pub => __awaiter(this, void 0, void 0, function* () {
21606
- const track = pub.track;
21607
- yield this.unpublishTrack(track, false);
21608
- if (restartTracks && !track.isMuted && track.source !== Track.Source.ScreenShare && track.source !== Track.Source.ScreenShareAudio && (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) && !track.isUserProvided) {
21609
- // generally we need to restart the track before publishing, often a full reconnect
21610
- // is necessary because computer had gone to sleep.
21611
- this.log.debug('restarting existing track', Object.assign(Object.assign({}, this.logContext), {
21612
- track: pub.trackSid
21613
- }));
21614
- yield track.restartTrack();
21615
- }
21616
- yield this.publishTrack(track, pub.options);
21617
- })));
21570
+ this.signalClient.sendUpdateSubscription(sub);
21571
+ });
21572
+ publication.on(TrackEvent.SubscriptionPermissionChanged, status => {
21573
+ this.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, publication, status);
21618
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);
21586
+ });
21587
+ }
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
+ }
21619
21613
  }
21620
- publishData(data, kind) {
21621
- let publishOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
21622
- return __awaiter(this, void 0, void 0, function* () {
21623
- const destination = Array.isArray(publishOptions) ? publishOptions : publishOptions === null || publishOptions === void 0 ? void 0 : publishOptions.destination;
21624
- const destinationSids = [];
21625
- const topic = !Array.isArray(publishOptions) ? publishOptions.topic : undefined;
21626
- if (destination !== undefined) {
21627
- destination.forEach(val => {
21628
- if (val instanceof RemoteParticipant) {
21629
- destinationSids.push(val.sid);
21630
- } else {
21631
- destinationSids.push(val);
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;
21632
21638
  }
21633
21639
  });
21634
21640
  }
21635
- const packet = new DataPacket({
21636
- kind,
21637
- value: {
21638
- case: 'user',
21639
- value: new UserPacket({
21640
- participantSid: this.sid,
21641
- payload: data,
21642
- destinationSids: destinationSids,
21643
- topic
21644
- })
21645
- }
21646
- });
21647
- yield this.engine.sendDataPacket(packet, kind);
21648
- });
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;
21649
21686
  }
21650
21687
  /**
21651
- * Control who can subscribe to LocalParticipant's published tracks.
21652
- *
21653
- * By default, all participants can subscribe. This allows fine-grained control over
21654
- * who is able to subscribe at a participant and track level.
21655
- *
21656
- * Note: if access is given at a track-level (i.e. both [allParticipantsAllowed] and
21657
- * [ParticipantTrackPermission.allTracksAllowed] are false), any newer published tracks
21658
- * will not grant permissions to any participants and will require a subsequent
21659
- * permissions update to allow subscription.
21660
- *
21661
- * @param allParticipantsAllowed Allows all participants to subscribe all tracks.
21662
- * Takes precedence over [[participantTrackPermissions]] if set to true.
21663
- * By default this is set to true.
21664
- * @param participantTrackPermissions Full list of individual permissions per
21665
- * participant/track. Any omitted participants will not receive any permissions.
21688
+ * @internal
21666
21689
  */
21667
- setTrackSubscriptionPermissions(allParticipantsAllowed) {
21668
- let participantTrackPermissions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
21669
- this.participantTrackPermissions = participantTrackPermissions;
21670
- this.allParticipantsAllowedToSubscribe = allParticipantsAllowed;
21671
- if (!this.engine.client.isDisconnected) {
21672
- this.updateTrackSubscriptionPermissions();
21673
- }
21690
+ getTrackPublicationBySid(sid) {
21691
+ return this.trackPublications.get(sid);
21674
21692
  }
21675
21693
  /** @internal */
21676
21694
  updateInfo(info) {
21677
- if (info.sid !== this.sid) {
21678
- // drop updates that specify a wrong sid.
21679
- // the sid for local participant is only explicitly set on join and full reconnect
21680
- return false;
21681
- }
21682
21695
  if (!super.updateInfo(info)) {
21683
21696
  return false;
21684
21697
  }
21685
- // reconcile track mute status.
21686
- // if server's track mute status doesn't match actual, we'll have to update
21687
- // 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();
21688
21704
  info.tracks.forEach(ti => {
21689
21705
  var _a, _b;
21690
- const pub = this.tracks.get(ti.sid);
21691
- if (pub) {
21692
- const mutedOnServer = pub.isMuted || ((_b = (_a = pub.track) === null || _a === void 0 ? void 0 : _a.isUpstreamPaused) !== null && _b !== void 0 ? _b : false);
21693
- if (mutedOnServer !== ti.muted) {
21694
- this.log.debug('updating server mute state after reconcile', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(pub)), {
21695
- 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)
21696
21724
  }));
21697
- this.engine.client.sendMuteTrack(ti.sid, mutedOnServer);
21698
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);
21699
21737
  }
21700
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
+ });
21701
21743
  return true;
21702
21744
  }
21703
- getPublicationForTrack(track) {
21704
- let publication;
21705
- this.tracks.forEach(pub => {
21706
- const localTrack = pub.track;
21707
- if (!localTrack) {
21708
- return;
21709
- }
21710
- // this looks overly complicated due to this object tree
21711
- if (track instanceof MediaStreamTrack) {
21712
- if (localTrack instanceof LocalAudioTrack || localTrack instanceof LocalVideoTrack) {
21713
- if (localTrack.mediaStreamTrack === track) {
21714
- publication = pub;
21715
- }
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'));
21716
21785
  }
21717
- } else if (track === localTrack) {
21718
- publication = pub;
21719
- }
21786
+ });
21787
+ yield Promise.all(promises);
21720
21788
  });
21721
- 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);
21722
21800
  }
21723
21801
  }
21724
21802
 
@@ -21730,8 +21808,6 @@ var ConnectionState;
21730
21808
  ConnectionState["Reconnecting"] = "reconnecting";
21731
21809
  })(ConnectionState || (ConnectionState = {}));
21732
21810
  const connectionReconcileFrequency = 2 * 1000;
21733
- /** @deprecated RoomState has been renamed to [[ConnectionState]] */
21734
- const RoomState = ConnectionState;
21735
21811
  /**
21736
21812
  * In LiveKit, a room is the logical grouping for a list of participants.
21737
21813
  * Participants in a room can publish tracks, and subscribe to others' tracks.
@@ -21761,6 +21837,8 @@ class Room extends eventsExports.EventEmitter {
21761
21837
  this.audioEnabled = true;
21762
21838
  this.isVideoPlaybackBlocked = false;
21763
21839
  this.log = livekitLogger;
21840
+ this.bufferedEvents = [];
21841
+ this.isResuming = false;
21764
21842
  this.connect = (url, token, opts) => __awaiter(this, void 0, void 0, function* () {
21765
21843
  var _c;
21766
21844
  // In case a disconnect called happened right before the connect call, make sure the disconnect is completed first by awaiting its lock
@@ -21847,7 +21925,6 @@ class Room extends eventsExports.EventEmitter {
21847
21925
  var _e, _f, _g;
21848
21926
  const joinResponse = yield engine.join(url, token, {
21849
21927
  autoSubscribe: connectOptions.autoSubscribe,
21850
- publishOnly: connectOptions.publishOnly,
21851
21928
  adaptiveStream: typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
21852
21929
  maxRetries: connectOptions.maxRetries,
21853
21930
  e2eeEnabled: !!this.e2eeManager,
@@ -21892,8 +21969,8 @@ class Room extends eventsExports.EventEmitter {
21892
21969
  }
21893
21970
  };
21894
21971
  this.attemptConnection = (url, token, opts, abortController) => __awaiter(this, void 0, void 0, function* () {
21895
- var _h, _j;
21896
- 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)) {
21897
21974
  this.log.info('Reconnection attempt replaced by new connection attempt', this.logContext);
21898
21975
  // make sure we close and recreate the existing engine in order to get rid of any potentially ongoing reconnection attempts
21899
21976
  this.recreateEngine();
@@ -21901,7 +21978,7 @@ class Room extends eventsExports.EventEmitter {
21901
21978
  // create engine if previously disconnected
21902
21979
  this.maybeCreateEngine();
21903
21980
  }
21904
- 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()) {
21905
21982
  this.engine.setRegionUrlProvider(this.regionUrlProvider);
21906
21983
  }
21907
21984
  this.acquireAudioContext();
@@ -21954,7 +22031,7 @@ class Room extends eventsExports.EventEmitter {
21954
22031
  }
21955
22032
  if (isWeb()) {
21956
22033
  document.addEventListener('freeze', this.onPageLeave);
21957
- (_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);
21958
22035
  }
21959
22036
  this.setAndEmitConnectionState(ConnectionState.Connected);
21960
22037
  this.emit(RoomEvent.Connected);
@@ -21966,7 +22043,7 @@ class Room extends eventsExports.EventEmitter {
21966
22043
  this.disconnect = function () {
21967
22044
  let stopTracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
21968
22045
  return __awaiter(_this, void 0, void 0, function* () {
21969
- var _k, _l, _m, _o;
22046
+ var _l, _m, _o, _p;
21970
22047
  const unlock = yield this.disconnectLock.lock();
21971
22048
  try {
21972
22049
  if (this.state === ConnectionState.Disconnected) {
@@ -21974,16 +22051,16 @@ class Room extends eventsExports.EventEmitter {
21974
22051
  return;
21975
22052
  }
21976
22053
  this.log.info('disconnect from room', Object.assign({}, this.logContext));
21977
- if (this.state === ConnectionState.Connecting || this.state === ConnectionState.Reconnecting) {
22054
+ if (this.state === ConnectionState.Connecting || this.state === ConnectionState.Reconnecting || this.isResuming) {
21978
22055
  // try aborting pending connection attempt
21979
22056
  this.log.warn('abort connection attempt', this.logContext);
21980
- (_k = this.abortController) === null || _k === void 0 ? void 0 : _k.abort();
22057
+ (_l = this.abortController) === null || _l === void 0 ? void 0 : _l.abort();
21981
22058
  // in case the abort controller didn't manage to cancel the connection attempt, reject the connect promise explicitly
21982
- (_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'));
21983
22060
  this.connectFuture = undefined;
21984
22061
  }
21985
22062
  // send leave
21986
- 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)) {
21987
22064
  yield this.engine.client.sendLeave();
21988
22065
  }
21989
22066
  // close engine (also closes client)
@@ -22037,16 +22114,21 @@ class Room extends eventsExports.EventEmitter {
22037
22114
  }
22038
22115
  // set the srcObject to null on page hide in order to prevent lock screen controls to show up for it
22039
22116
  dummyAudioEl.srcObject = document.hidden ? null : stream;
22117
+ if (!document.hidden) {
22118
+ this.log.debug('page visible again, triggering startAudio to resume playback and update playback status', this.logContext);
22119
+ this.startAudio();
22120
+ }
22040
22121
  });
22041
22122
  document.body.append(dummyAudioEl);
22042
22123
  this.once(RoomEvent.Disconnected, () => {
22043
22124
  dummyAudioEl === null || dummyAudioEl === void 0 ? void 0 : dummyAudioEl.remove();
22125
+ dummyAudioEl = null;
22044
22126
  });
22045
22127
  }
22046
22128
  elements.push(dummyAudioEl);
22047
22129
  }
22048
- this.participants.forEach(p => {
22049
- p.audioTracks.forEach(t => {
22130
+ this.remoteParticipants.forEach(p => {
22131
+ p.audioTrackPublications.forEach(t => {
22050
22132
  if (t.track) {
22051
22133
  t.track.attachedElements.forEach(e => {
22052
22134
  elements.push(e);
@@ -22067,8 +22149,8 @@ class Room extends eventsExports.EventEmitter {
22067
22149
  });
22068
22150
  this.startVideo = () => __awaiter(this, void 0, void 0, function* () {
22069
22151
  const elements = [];
22070
- for (const p of this.participants.values()) {
22071
- p.videoTracks.forEach(tr => {
22152
+ for (const p of this.remoteParticipants.values()) {
22153
+ p.videoTrackPublications.forEach(tr => {
22072
22154
  var _a;
22073
22155
  (_a = tr.track) === null || _a === void 0 ? void 0 : _a.attachedElements.forEach(el => {
22074
22156
  if (!elements.includes(el)) {
@@ -22089,9 +22171,11 @@ class Room extends eventsExports.EventEmitter {
22089
22171
  });
22090
22172
  this.handleRestarting = () => {
22091
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;
22092
22176
  // also unwind existing participants & existing subscriptions
22093
- for (const p of this.participants.values()) {
22094
- this.handleParticipantDisconnected(p.sid, p);
22177
+ for (const p of this.remoteParticipants.values()) {
22178
+ this.handleParticipantDisconnected(p.identity, p);
22095
22179
  }
22096
22180
  if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
22097
22181
  this.emit(RoomEvent.Reconnecting);
@@ -22101,7 +22185,7 @@ class Room extends eventsExports.EventEmitter {
22101
22185
  this.log.debug("signal reconnected to server, region ".concat(joinResponse.serverRegion), Object.assign(Object.assign({}, this.logContext), {
22102
22186
  region: joinResponse.serverRegion
22103
22187
  }));
22104
- this.cachedParticipantSids = [];
22188
+ this.bufferedEvents = [];
22105
22189
  this.applyJoinResponse(joinResponse);
22106
22190
  try {
22107
22191
  // unpublish & republish tracks
@@ -22116,43 +22200,35 @@ class Room extends eventsExports.EventEmitter {
22116
22200
  this.log.debug("fully reconnected to server", Object.assign(Object.assign({}, this.logContext), {
22117
22201
  region: joinResponse.serverRegion
22118
22202
  }));
22119
- } catch (_p) {
22203
+ } catch (_q) {
22120
22204
  // reconnection failed, handleDisconnect is being invoked already, just return here
22121
22205
  return;
22122
22206
  }
22123
22207
  this.setAndEmitConnectionState(ConnectionState.Connected);
22124
22208
  this.emit(RoomEvent.Reconnected);
22125
22209
  this.registerConnectionReconcile();
22126
- // emit participant connected events after connection has been re-established
22127
- this.participants.forEach(participant => {
22128
- this.emit(RoomEvent.ParticipantConnected, participant);
22129
- });
22210
+ this.emitBufferedEvents();
22130
22211
  });
22131
22212
  this.handleParticipantUpdates = participantInfos => {
22132
22213
  // handle changes to participant state, and send events
22133
22214
  participantInfos.forEach(info => {
22215
+ var _a;
22134
22216
  if (info.identity === this.localParticipant.identity) {
22135
22217
  this.localParticipant.updateInfo(info);
22136
22218
  return;
22137
22219
  }
22138
- // ensure identity <=> sid mapping
22139
- const sid = this.identityToSid.get(info.identity);
22140
- if (sid && sid !== info.sid) {
22141
- // sid had changed, need to remove previous participant
22142
- 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 : '';
22143
22224
  }
22144
- let remoteParticipant = this.participants.get(info.sid);
22145
- const isNewParticipant = !remoteParticipant;
22225
+ let remoteParticipant = this.remoteParticipants.get(info.identity);
22146
22226
  // when it's disconnected, send updates
22147
22227
  if (info.state === ParticipantInfo_State.DISCONNECTED) {
22148
- this.handleParticipantDisconnected(info.sid, remoteParticipant);
22228
+ this.handleParticipantDisconnected(info.identity, remoteParticipant);
22149
22229
  } else {
22150
22230
  // create participant if doesn't exist
22151
- remoteParticipant = this.getOrCreateParticipant(info.sid, info);
22152
- if (!isNewParticipant) {
22153
- // just update, no events
22154
- remoteParticipant.updateInfo(info);
22155
- }
22231
+ remoteParticipant = this.getOrCreateParticipant(info.identity, info);
22156
22232
  }
22157
22233
  });
22158
22234
  };
@@ -22167,7 +22243,7 @@ class Room extends eventsExports.EventEmitter {
22167
22243
  this.localParticipant.setIsSpeaking(true);
22168
22244
  activeSpeakers.push(this.localParticipant);
22169
22245
  } else {
22170
- const p = this.participants.get(speaker.sid);
22246
+ const p = this.getRemoteParticipantBySid(speaker.sid);
22171
22247
  if (p) {
22172
22248
  p.audioLevel = speaker.level;
22173
22249
  p.setIsSpeaking(true);
@@ -22179,7 +22255,7 @@ class Room extends eventsExports.EventEmitter {
22179
22255
  this.localParticipant.audioLevel = 0;
22180
22256
  this.localParticipant.setIsSpeaking(false);
22181
22257
  }
22182
- this.participants.forEach(p => {
22258
+ this.remoteParticipants.forEach(p => {
22183
22259
  if (!seenSids[p.sid]) {
22184
22260
  p.audioLevel = 0;
22185
22261
  p.setIsSpeaking(false);
@@ -22195,7 +22271,7 @@ class Room extends eventsExports.EventEmitter {
22195
22271
  lastSpeakers.set(p.sid, p);
22196
22272
  });
22197
22273
  speakerUpdates.forEach(speaker => {
22198
- let p = this.participants.get(speaker.sid);
22274
+ let p = this.getRemoteParticipantBySid(speaker.sid);
22199
22275
  if (speaker.sid === this.localParticipant.sid) {
22200
22276
  p = this.localParticipant;
22201
22277
  }
@@ -22217,11 +22293,11 @@ class Room extends eventsExports.EventEmitter {
22217
22293
  };
22218
22294
  this.handleStreamStateUpdate = streamStateUpdate => {
22219
22295
  streamStateUpdate.streamStates.forEach(streamState => {
22220
- const participant = this.participants.get(streamState.participantSid);
22296
+ const participant = this.getRemoteParticipantBySid(streamState.participantSid);
22221
22297
  if (!participant) {
22222
22298
  return;
22223
22299
  }
22224
- const pub = participant.getTrackPublication(streamState.trackSid);
22300
+ const pub = participant.getTrackPublicationBySid(streamState.trackSid);
22225
22301
  if (!pub || !pub.track) {
22226
22302
  return;
22227
22303
  }
@@ -22231,22 +22307,22 @@ class Room extends eventsExports.EventEmitter {
22231
22307
  });
22232
22308
  };
22233
22309
  this.handleSubscriptionPermissionUpdate = update => {
22234
- const participant = this.participants.get(update.participantSid);
22310
+ const participant = this.getRemoteParticipantBySid(update.participantSid);
22235
22311
  if (!participant) {
22236
22312
  return;
22237
22313
  }
22238
- const pub = participant.getTrackPublication(update.trackSid);
22314
+ const pub = participant.getTrackPublicationBySid(update.trackSid);
22239
22315
  if (!pub) {
22240
22316
  return;
22241
22317
  }
22242
22318
  pub.setAllowed(update.allowed);
22243
22319
  };
22244
22320
  this.handleSubscriptionError = update => {
22245
- 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));
22246
22322
  if (!participant) {
22247
22323
  return;
22248
22324
  }
22249
- const pub = participant.getTrackPublication(update.trackSid);
22325
+ const pub = participant.getTrackPublicationBySid(update.trackSid);
22250
22326
  if (!pub) {
22251
22327
  return;
22252
22328
  }
@@ -22254,7 +22330,7 @@ class Room extends eventsExports.EventEmitter {
22254
22330
  };
22255
22331
  this.handleDataPacket = (userPacket, kind) => {
22256
22332
  // find the participant
22257
- const participant = this.participants.get(userPacket.participantSid);
22333
+ const participant = this.remoteParticipants.get(userPacket.participantIdentity);
22258
22334
  this.emit(RoomEvent.DataReceived, userPacket.payload, participant, kind, userPacket.topic);
22259
22335
  // also emit on the participant
22260
22336
  participant === null || participant === void 0 ? void 0 : participant.emit(ParticipantEvent.DataReceived, userPacket.payload, kind);
@@ -22307,7 +22383,7 @@ class Room extends eventsExports.EventEmitter {
22307
22383
  this.localParticipant.setConnectionQuality(info.quality);
22308
22384
  return;
22309
22385
  }
22310
- const participant = this.participants.get(info.participantSid);
22386
+ const participant = this.getRemoteParticipantBySid(info.participantSid);
22311
22387
  if (participant) {
22312
22388
  participant.setConnectionQuality(info.quality);
22313
22389
  }
@@ -22326,7 +22402,7 @@ class Room extends eventsExports.EventEmitter {
22326
22402
  this.emit(RoomEvent.TrackUnmuted, pub, this.localParticipant);
22327
22403
  };
22328
22404
  this.onLocalTrackPublished = pub => __awaiter(this, void 0, void 0, function* () {
22329
- var _q;
22405
+ var _r;
22330
22406
  this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant);
22331
22407
  if (pub.track instanceof LocalAudioTrack) {
22332
22408
  const trackIsSilent = yield pub.track.checkForSilence();
@@ -22334,7 +22410,7 @@ class Room extends eventsExports.EventEmitter {
22334
22410
  this.emit(RoomEvent.LocalAudioSilenceDetected, pub);
22335
22411
  }
22336
22412
  }
22337
- 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();
22338
22414
  const deviceKind = sourceToKind(pub.source);
22339
22415
  if (deviceKind && deviceId && deviceId !== this.localParticipant.activeDeviceMap.get(deviceKind)) {
22340
22416
  this.localParticipant.activeDeviceMap.set(deviceKind, deviceId);
@@ -22354,9 +22430,8 @@ class Room extends eventsExports.EventEmitter {
22354
22430
  this.emit(RoomEvent.ParticipantPermissionsChanged, prevPermissions, this.localParticipant);
22355
22431
  };
22356
22432
  this.setMaxListeners(100);
22357
- this.participants = new Map();
22358
- this.cachedParticipantSids = [];
22359
- this.identityToSid = new Map();
22433
+ this.remoteParticipants = new Map();
22434
+ this.sidToIdentity = new Map();
22360
22435
  this.options = Object.assign(Object.assign({}, roomOptionDefaults), options);
22361
22436
  this.log = getLogger((_a = this.options.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Room);
22362
22437
  this.options.audioCaptureDefaults = Object.assign(Object.assign({}, audioDefaults), options === null || options === void 0 ? void 0 : options.audioCaptureDefaults);
@@ -22408,9 +22483,10 @@ class Room extends eventsExports.EventEmitter {
22408
22483
  }
22409
22484
  }
22410
22485
  get logContext() {
22486
+ var _a;
22411
22487
  return {
22412
22488
  room: this.name,
22413
- roomSid: this.sid,
22489
+ roomSid: (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.sid,
22414
22490
  identity: this.localParticipant.identity
22415
22491
  };
22416
22492
  }
@@ -22421,10 +22497,32 @@ class Room extends eventsExports.EventEmitter {
22421
22497
  var _a, _b;
22422
22498
  return (_b = (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.activeRecording) !== null && _b !== void 0 ? _b : false;
22423
22499
  }
22424
- /** server assigned unique room id */
22425
- get sid() {
22426
- var _a, _b;
22427
- 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
+ });
22428
22526
  }
22429
22527
  /** user assigned name, derived from JWT token */
22430
22528
  get name() {
@@ -22455,21 +22553,17 @@ class Room extends eventsExports.EventEmitter {
22455
22553
  this.handleDisconnect(this.options.stopLocalTrackOnUnpublish, reason);
22456
22554
  }).on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate).on(EngineEvent.DataPacketReceived, this.handleDataPacket).on(EngineEvent.Resuming, () => {
22457
22555
  this.clearConnectionReconcile();
22458
- if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
22459
- this.emit(RoomEvent.Reconnecting);
22460
- }
22461
- this.cachedParticipantSids = Array.from(this.participants.keys());
22556
+ this.isResuming = true;
22557
+ this.log.info('Resuming signal connection', this.logContext);
22462
22558
  }).on(EngineEvent.Resumed, () => {
22463
- this.setAndEmitConnectionState(ConnectionState.Connected);
22464
- this.emit(RoomEvent.Reconnected);
22465
22559
  this.registerConnectionReconcile();
22560
+ this.isResuming = false;
22561
+ this.log.info('Resumed signal connection', this.logContext);
22466
22562
  this.updateSubscriptions();
22467
- // once reconnected, figure out if any participants connected during reconnect and emit events for it
22468
- const diffParticipants = Array.from(this.participants.values()).filter(p => !this.cachedParticipantSids.includes(p.sid));
22469
- diffParticipants.forEach(p => this.emit(RoomEvent.ParticipantConnected, p));
22470
- this.cachedParticipantSids = [];
22563
+ this.emitBufferedEvents();
22471
22564
  }).on(EngineEvent.SignalResumed, () => {
22472
- if (this.state === ConnectionState.Reconnecting) {
22565
+ this.bufferedEvents = [];
22566
+ if (this.state === ConnectionState.Reconnecting || this.isResuming) {
22473
22567
  this.sendSyncState();
22474
22568
  }
22475
22569
  }).on(EngineEvent.Restarting, this.handleRestarting).on(EngineEvent.SignalRestarted, this.handleSignalRestarted).on(EngineEvent.DCBufferStatusChanged, (status, kind) => {
@@ -22543,10 +22637,7 @@ class Room extends eventsExports.EventEmitter {
22543
22637
  if (this.localParticipant.identity === identity) {
22544
22638
  return this.localParticipant;
22545
22639
  }
22546
- const sid = this.identityToSid.get(identity);
22547
- if (sid) {
22548
- return this.participants.get(sid);
22549
- }
22640
+ return this.remoteParticipants.get(identity);
22550
22641
  }
22551
22642
  clearConnectionFutures() {
22552
22643
  this.connectFuture = undefined;
@@ -22677,15 +22768,6 @@ class Room extends eventsExports.EventEmitter {
22677
22768
  get canPlaybackVideo() {
22678
22769
  return !this.isVideoPlaybackBlocked;
22679
22770
  }
22680
- /**
22681
- * Returns the active audio output device used in this room.
22682
- * @return the previously successfully set audio output device ID or an empty string if the default device is used.
22683
- * @deprecated use `getActiveDevice('audiooutput')` instead
22684
- */
22685
- getActiveAudioOutputDevice() {
22686
- var _a, _b;
22687
- return (_b = (_a = this.options.audioOutput) === null || _a === void 0 ? void 0 : _a.deviceId) !== null && _b !== void 0 ? _b : '';
22688
- }
22689
22771
  getActiveDevice(kind) {
22690
22772
  return this.localParticipant.activeDeviceMap.get(kind);
22691
22773
  }
@@ -22713,7 +22795,7 @@ class Room extends eventsExports.EventEmitter {
22713
22795
  const prevDeviceId = this.options.audioCaptureDefaults.deviceId;
22714
22796
  this.options.audioCaptureDefaults.deviceId = deviceConstraint;
22715
22797
  deviceHasChanged = prevDeviceId !== deviceConstraint;
22716
- 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);
22717
22799
  try {
22718
22800
  success = (yield Promise.all(tracks.map(t => {
22719
22801
  var _a;
@@ -22727,7 +22809,7 @@ class Room extends eventsExports.EventEmitter {
22727
22809
  const prevDeviceId = this.options.videoCaptureDefaults.deviceId;
22728
22810
  this.options.videoCaptureDefaults.deviceId = deviceConstraint;
22729
22811
  deviceHasChanged = prevDeviceId !== deviceConstraint;
22730
- 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);
22731
22813
  try {
22732
22814
  success = (yield Promise.all(tracks.map(t => {
22733
22815
  var _a;
@@ -22738,7 +22820,7 @@ class Room extends eventsExports.EventEmitter {
22738
22820
  throw e;
22739
22821
  }
22740
22822
  } else if (kind === 'audiooutput') {
22741
- 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)) {
22742
22824
  throw new Error('cannot switch audio output, setSinkId not supported');
22743
22825
  }
22744
22826
  (_a = (_c = this.options).audioOutput) !== null && _a !== void 0 ? _a : _c.audioOutput = {};
@@ -22746,11 +22828,11 @@ class Room extends eventsExports.EventEmitter {
22746
22828
  this.options.audioOutput.deviceId = deviceId;
22747
22829
  deviceHasChanged = prevDeviceId !== deviceConstraint;
22748
22830
  try {
22749
- if (this.options.expWebAudioMix) {
22831
+ if (this.options.webAudioMix) {
22750
22832
  // @ts-expect-error setSinkId is not yet in the typescript type of AudioContext
22751
22833
  (_b = this.audioContext) === null || _b === void 0 ? void 0 : _b.setSinkId(deviceId);
22752
22834
  } else {
22753
- 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({
22754
22836
  deviceId
22755
22837
  })));
22756
22838
  }
@@ -22774,9 +22856,12 @@ class Room extends eventsExports.EventEmitter {
22774
22856
  (_a = this.engine) === null || _a === void 0 ? void 0 : _a.close();
22775
22857
  /* @ts-ignore */
22776
22858
  this.engine = undefined;
22859
+ this.isResuming = false;
22777
22860
  // clear out existing remote participants, since they may have attached
22778
22861
  // the old engine
22779
- this.participants.clear();
22862
+ this.remoteParticipants.clear();
22863
+ this.sidToIdentity.clear();
22864
+ this.bufferedEvents = [];
22780
22865
  this.maybeCreateEngine();
22781
22866
  }
22782
22867
  onTrackAdded(mediaTrack, stream, receiver) {
@@ -22805,19 +22890,19 @@ class Room extends eventsExports.EventEmitter {
22805
22890
  return;
22806
22891
  }
22807
22892
  const parts = unpackStreamId(stream.id);
22808
- const participantId = parts[0];
22893
+ const participantSid = parts[0];
22809
22894
  let streamId = parts[1];
22810
22895
  let trackId = mediaTrack.id;
22811
22896
  // firefox will get streamId (pID|trackId) instead of (pID|streamId) as it doesn't support sync tracks by stream
22812
22897
  // and generates its own track id instead of infer from sdp track id.
22813
22898
  if (streamId && streamId.startsWith('TR')) trackId = streamId;
22814
- if (participantId === this.localParticipant.sid) {
22899
+ if (participantSid === this.localParticipant.sid) {
22815
22900
  this.log.warn('tried to create RemoteParticipant for local participant', this.logContext);
22816
22901
  return;
22817
22902
  }
22818
- const participant = this.participants.get(participantId);
22903
+ const participant = Array.from(this.remoteParticipants.values()).find(p => p.sid === participantSid);
22819
22904
  if (!participant) {
22820
- 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);
22821
22906
  return;
22822
22907
  }
22823
22908
  let adaptiveStreamSettings;
@@ -22835,17 +22920,19 @@ class Room extends eventsExports.EventEmitter {
22835
22920
  let reason = arguments.length > 1 ? arguments[1] : undefined;
22836
22921
  var _a;
22837
22922
  this.clearConnectionReconcile();
22923
+ this.isResuming = false;
22924
+ this.bufferedEvents = [];
22838
22925
  if (this.state === ConnectionState.Disconnected) {
22839
22926
  return;
22840
22927
  }
22841
22928
  this.regionUrl = undefined;
22842
22929
  try {
22843
- this.participants.forEach(p => {
22844
- p.tracks.forEach(pub => {
22930
+ this.remoteParticipants.forEach(p => {
22931
+ p.trackPublications.forEach(pub => {
22845
22932
  p.unpublishTrack(pub.trackSid);
22846
22933
  });
22847
22934
  });
22848
- this.localParticipant.tracks.forEach(pub => {
22935
+ this.localParticipant.trackPublications.forEach(pub => {
22849
22936
  var _a, _b;
22850
22937
  if (pub.track) {
22851
22938
  this.localParticipant.unpublishTrack(pub.track, shouldStopTracks);
@@ -22856,12 +22943,13 @@ class Room extends eventsExports.EventEmitter {
22856
22943
  }
22857
22944
  });
22858
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);
22859
- this.localParticipant.tracks.clear();
22860
- this.localParticipant.videoTracks.clear();
22861
- this.localParticipant.audioTracks.clear();
22862
- 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();
22863
22951
  this.activeSpeakers = [];
22864
- if (this.audioContext && typeof this.options.expWebAudioMix === 'boolean') {
22952
+ if (this.audioContext && typeof this.options.webAudioMix === 'boolean') {
22865
22953
  this.audioContext.close();
22866
22954
  this.audioContext = undefined;
22867
22955
  }
@@ -22876,14 +22964,13 @@ class Room extends eventsExports.EventEmitter {
22876
22964
  this.emit(RoomEvent.Disconnected, reason);
22877
22965
  }
22878
22966
  }
22879
- handleParticipantDisconnected(sid, participant) {
22967
+ handleParticipantDisconnected(identity, participant) {
22880
22968
  // remove and send event
22881
- this.participants.delete(sid);
22969
+ this.remoteParticipants.delete(identity);
22882
22970
  if (!participant) {
22883
22971
  return;
22884
22972
  }
22885
- this.identityToSid.delete(participant.identity);
22886
- participant.tracks.forEach(publication => {
22973
+ participant.trackPublications.forEach(publication => {
22887
22974
  participant.unpublishTrack(publication.trackSid, true);
22888
22975
  });
22889
22976
  this.emit(RoomEvent.ParticipantDisconnected, participant);
@@ -22891,9 +22978,9 @@ class Room extends eventsExports.EventEmitter {
22891
22978
  acquireAudioContext() {
22892
22979
  var _a, _b;
22893
22980
  return __awaiter(this, void 0, void 0, function* () {
22894
- if (typeof this.options.expWebAudioMix !== 'boolean' && this.options.expWebAudioMix.audioContext) {
22981
+ if (typeof this.options.webAudioMix !== 'boolean' && this.options.webAudioMix.audioContext) {
22895
22982
  // override audio context with custom audio context if supplied by user
22896
- this.audioContext = this.options.expWebAudioMix.audioContext;
22983
+ this.audioContext = this.options.webAudioMix.audioContext;
22897
22984
  } else if (!this.audioContext || this.audioContext.state === 'closed') {
22898
22985
  // by using an AudioContext, it reduces lag on audio elements
22899
22986
  // https://stackoverflow.com/questions/9811429/html5-audio-tag-on-safari-has-a-delay/54119854#54119854
@@ -22910,8 +22997,8 @@ class Room extends eventsExports.EventEmitter {
22910
22997
  }));
22911
22998
  }
22912
22999
  }
22913
- if (this.options.expWebAudioMix) {
22914
- this.participants.forEach(participant => participant.setAudioContext(this.audioContext));
23000
+ if (this.options.webAudioMix) {
23001
+ this.remoteParticipants.forEach(participant => participant.setAudioContext(this.audioContext));
22915
23002
  }
22916
23003
  this.localParticipant.setAudioContext(this.audioContext);
22917
23004
  const newContextIsRunning = ((_b = this.audioContext) === null || _b === void 0 ? void 0 : _b.state) === 'running';
@@ -22921,18 +23008,18 @@ class Room extends eventsExports.EventEmitter {
22921
23008
  }
22922
23009
  });
22923
23010
  }
22924
- createParticipant(id, info) {
23011
+ createParticipant(identity, info) {
22925
23012
  var _a;
22926
23013
  let participant;
22927
23014
  if (info) {
22928
23015
  participant = RemoteParticipant.fromParticipantInfo(this.engine.client, info);
22929
23016
  } else {
22930
- participant = new RemoteParticipant(this.engine.client, id, '', undefined, undefined, {
23017
+ participant = new RemoteParticipant(this.engine.client, '', identity, undefined, undefined, {
22931
23018
  loggerContextCb: () => this.logContext,
22932
23019
  loggerName: this.options.loggerName
22933
23020
  });
22934
23021
  }
22935
- if (this.options.expWebAudioMix) {
23022
+ if (this.options.webAudioMix) {
22936
23023
  participant.setAudioContext(this.audioContext);
22937
23024
  }
22938
23025
  if ((_a = this.options.audioOutput) === null || _a === void 0 ? void 0 : _a.deviceId) {
@@ -22940,13 +23027,20 @@ class Room extends eventsExports.EventEmitter {
22940
23027
  }
22941
23028
  return participant;
22942
23029
  }
22943
- getOrCreateParticipant(id, info) {
22944
- if (this.participants.has(id)) {
22945
- 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;
22946
23040
  }
22947
- const participant = this.createParticipant(id, info);
22948
- this.participants.set(id, participant);
22949
- 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);
22950
23044
  // if we have valid info and the participant wasn't in the map before, we can assume the participant is new
22951
23045
  // firing here to make sure that `ParticipantConnected` fires before the initial track events
22952
23046
  this.emitWhenConnected(RoomEvent.ParticipantConnected, participant);
@@ -22997,11 +23091,11 @@ class Room extends eventsExports.EventEmitter {
22997
23091
  return participant;
22998
23092
  }
22999
23093
  sendSyncState() {
23000
- const remoteTracks = Array.from(this.participants.values()).reduce((acc, participant) => {
23001
- 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
23002
23096
  return acc;
23003
23097
  }, []);
23004
- 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
23005
23099
  this.engine.sendSyncState(remoteTracks, localTracks);
23006
23100
  }
23007
23101
  /**
@@ -23009,14 +23103,20 @@ class Room extends eventsExports.EventEmitter {
23009
23103
  * subscription settings.
23010
23104
  */
23011
23105
  updateSubscriptions() {
23012
- for (const p of this.participants.values()) {
23013
- for (const pub of p.videoTracks.values()) {
23106
+ for (const p of this.remoteParticipants.values()) {
23107
+ for (const pub of p.videoTrackPublications.values()) {
23014
23108
  if (pub.isSubscribed && pub instanceof RemoteTrackPublication) {
23015
23109
  pub.emitTrackUpdate();
23016
23110
  }
23017
23111
  }
23018
23112
  }
23019
23113
  }
23114
+ getRemoteParticipantBySid(sid) {
23115
+ const identity = this.sidToIdentity.get(sid);
23116
+ if (identity) {
23117
+ return this.remoteParticipants.get(identity);
23118
+ }
23119
+ }
23020
23120
  registerConnectionReconcile() {
23021
23121
  this.clearConnectionReconcile();
23022
23122
  let consecutiveFailures = 0;
@@ -23059,11 +23159,21 @@ class Room extends eventsExports.EventEmitter {
23059
23159
  this.emit(RoomEvent.ConnectionStateChanged, this.state);
23060
23160
  return true;
23061
23161
  }
23162
+ emitBufferedEvents() {
23163
+ this.bufferedEvents.forEach(_ref2 => {
23164
+ let [ev, args] = _ref2;
23165
+ this.emit(ev, ...args);
23166
+ });
23167
+ this.bufferedEvents = [];
23168
+ }
23062
23169
  emitWhenConnected(event) {
23063
- if (this.state === ConnectionState.Connected) {
23064
- for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
23065
- args[_key - 1] = arguments[_key];
23066
- }
23170
+ for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
23171
+ args[_key - 1] = arguments[_key];
23172
+ }
23173
+ if (this.state === ConnectionState.Reconnecting || this.isResuming || !this.engine || this.engine.pendingReconnect) {
23174
+ // in case the room is reconnecting, buffer the events by firing them later after emitting RoomEvent.Reconnected
23175
+ this.bufferedEvents.push([event, args]);
23176
+ } else if (this.state === ConnectionState.Connected) {
23067
23177
  return this.emit(event, ...args);
23068
23178
  }
23069
23179
  return false;
@@ -23843,5 +23953,5 @@ function isFacingModeValue(item) {
23843
23953
  return item === undefined || allowedValues.includes(item);
23844
23954
  }
23845
23955
 
23846
- 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 };
23847
23957
  //# sourceMappingURL=livekit-client.esm.mjs.map