livekit-client 1.15.10 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. package/README.md +21 -17
  2. package/dist/livekit-client.esm.mjs +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