livekit-client 1.15.12 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +27 -15
  2. package/dist/livekit-client.esm.mjs +1750 -1581
  3. package/dist/livekit-client.esm.mjs.map +1 -1
  4. package/dist/livekit-client.umd.js +1 -1
  5. package/dist/livekit-client.umd.js.map +1 -1
  6. package/dist/src/api/SignalClient.d.ts +0 -2
  7. package/dist/src/api/SignalClient.d.ts.map +1 -1
  8. package/dist/src/index.d.ts +3 -3
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/options.d.ts +3 -9
  11. package/dist/src/options.d.ts.map +1 -1
  12. package/dist/src/proto/livekit_models_pb.d.ts +56 -1
  13. package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
  14. package/dist/src/proto/livekit_rtc_pb.d.ts +38 -0
  15. package/dist/src/proto/livekit_rtc_pb.d.ts.map +1 -1
  16. package/dist/src/room/PCTransport.d.ts +1 -2
  17. package/dist/src/room/PCTransport.d.ts.map +1 -1
  18. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  19. package/dist/src/room/RTCEngine.d.ts +1 -0
  20. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  21. package/dist/src/room/Room.d.ts +12 -15
  22. package/dist/src/room/Room.d.ts.map +1 -1
  23. package/dist/src/room/defaults.d.ts.map +1 -1
  24. package/dist/src/room/events.d.ts +8 -5
  25. package/dist/src/room/events.d.ts.map +1 -1
  26. package/dist/src/room/participant/LocalParticipant.d.ts +8 -25
  27. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  28. package/dist/src/room/participant/Participant.d.ts +6 -10
  29. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  30. package/dist/src/room/participant/RemoteParticipant.d.ts +9 -6
  31. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  32. package/dist/src/room/timers.d.ts +4 -5
  33. package/dist/src/room/timers.d.ts.map +1 -1
  34. package/dist/src/room/track/LocalVideoTrack.d.ts +3 -3
  35. package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
  36. package/dist/src/room/track/RemoteTrackPublication.d.ts +2 -2
  37. package/dist/src/room/track/RemoteTrackPublication.d.ts.map +1 -1
  38. package/dist/src/room/track/Track.d.ts +5 -0
  39. package/dist/src/room/track/Track.d.ts.map +1 -1
  40. package/dist/src/room/track/options.d.ts +0 -5
  41. package/dist/src/room/track/options.d.ts.map +1 -1
  42. package/dist/src/room/types.d.ts +11 -3
  43. package/dist/src/room/types.d.ts.map +1 -1
  44. package/dist/src/version.d.ts +1 -1
  45. package/dist/ts4.2/src/api/SignalClient.d.ts +0 -2
  46. package/dist/ts4.2/src/index.d.ts +3 -3
  47. package/dist/ts4.2/src/options.d.ts +3 -9
  48. package/dist/ts4.2/src/proto/livekit_models_pb.d.ts +56 -1
  49. package/dist/ts4.2/src/proto/livekit_rtc_pb.d.ts +38 -0
  50. package/dist/ts4.2/src/room/PCTransport.d.ts +1 -2
  51. package/dist/ts4.2/src/room/RTCEngine.d.ts +1 -0
  52. package/dist/ts4.2/src/room/Room.d.ts +12 -15
  53. package/dist/ts4.2/src/room/events.d.ts +8 -5
  54. package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +8 -25
  55. package/dist/ts4.2/src/room/participant/Participant.d.ts +6 -10
  56. package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +9 -6
  57. package/dist/ts4.2/src/room/timers.d.ts +4 -5
  58. package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +3 -3
  59. package/dist/ts4.2/src/room/track/RemoteTrackPublication.d.ts +2 -2
  60. package/dist/ts4.2/src/room/track/Track.d.ts +5 -0
  61. package/dist/ts4.2/src/room/track/options.d.ts +0 -5
  62. package/dist/ts4.2/src/room/types.d.ts +11 -3
  63. package/dist/ts4.2/src/version.d.ts +1 -1
  64. package/package.json +10 -9
  65. package/src/api/SignalClient.ts +2 -6
  66. package/src/e2ee/E2eeManager.ts +2 -2
  67. package/src/index.ts +2 -4
  68. package/src/options.ts +3 -10
  69. package/src/proto/livekit_models_pb.ts +78 -0
  70. package/src/proto/livekit_rtc_pb.ts +53 -0
  71. package/src/room/PCTransport.ts +3 -13
  72. package/src/room/PCTransportManager.ts +2 -3
  73. package/src/room/RTCEngine.ts +11 -1
  74. package/src/room/Room.ts +145 -114
  75. package/src/room/defaults.ts +1 -5
  76. package/src/room/events.ts +8 -6
  77. package/src/room/participant/LocalParticipant.ts +36 -77
  78. package/src/room/participant/Participant.ts +23 -24
  79. package/src/room/participant/RemoteParticipant.ts +27 -24
  80. package/src/room/track/LocalVideoTrack.test.ts +1 -1
  81. package/src/room/track/LocalVideoTrack.ts +11 -7
  82. package/src/room/track/RemoteTrackPublication.ts +2 -7
  83. package/src/room/track/Track.ts +10 -1
  84. package/src/room/track/options.ts +0 -6
  85. package/src/room/types.ts +11 -4
  86. 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
  }, {
@@ -3874,6 +3874,14 @@ var DisconnectReason;
3874
3874
  * @generated from enum value: JOIN_FAILURE = 7;
3875
3875
  */
3876
3876
  DisconnectReason[DisconnectReason["JOIN_FAILURE"] = 7] = "JOIN_FAILURE";
3877
+ /**
3878
+ * @generated from enum value: MIGRATION = 8;
3879
+ */
3880
+ DisconnectReason[DisconnectReason["MIGRATION"] = 8] = "MIGRATION";
3881
+ /**
3882
+ * @generated from enum value: SIGNAL_CLOSE = 9;
3883
+ */
3884
+ DisconnectReason[DisconnectReason["SIGNAL_CLOSE"] = 9] = "SIGNAL_CLOSE";
3877
3885
  })(DisconnectReason || (DisconnectReason = {}));
3878
3886
  // Retrieve enum metadata with: proto3.getEnumType(DisconnectReason)
3879
3887
  proto3.util.setEnumType(DisconnectReason, "livekit.DisconnectReason", [{
@@ -3900,6 +3908,12 @@ proto3.util.setEnumType(DisconnectReason, "livekit.DisconnectReason", [{
3900
3908
  }, {
3901
3909
  no: 7,
3902
3910
  name: "JOIN_FAILURE"
3911
+ }, {
3912
+ no: 8,
3913
+ name: "MIGRATION"
3914
+ }, {
3915
+ no: 9,
3916
+ name: "SIGNAL_CLOSE"
3903
3917
  }]);
3904
3918
  /**
3905
3919
  * @generated from enum livekit.ReconnectReason
@@ -4096,6 +4110,11 @@ Room$1.fields = proto3.util.newFieldList(() => [{
4096
4110
  name: "active_recording",
4097
4111
  kind: "scalar",
4098
4112
  T: 8 /* ScalarType.BOOL */
4113
+ }, {
4114
+ no: 13,
4115
+ name: "version",
4116
+ kind: "message",
4117
+ T: TimedVersion
4099
4118
  }]);
4100
4119
  /**
4101
4120
  * @generated from message livekit.Codec
@@ -4354,6 +4373,10 @@ class ParticipantInfo extends Message {
4354
4373
  * @generated from field: bool is_publisher = 13;
4355
4374
  */
4356
4375
  this.isPublisher = false;
4376
+ /**
4377
+ * @generated from field: livekit.ParticipantInfo.Kind kind = 14;
4378
+ */
4379
+ this.kind = ParticipantInfo_Kind.STANDARD;
4357
4380
  proto3.util.initPartial(data, this);
4358
4381
  }
4359
4382
  static fromBinary(bytes, options) {
@@ -4427,6 +4450,11 @@ ParticipantInfo.fields = proto3.util.newFieldList(() => [{
4427
4450
  name: "is_publisher",
4428
4451
  kind: "scalar",
4429
4452
  T: 8 /* ScalarType.BOOL */
4453
+ }, {
4454
+ no: 14,
4455
+ name: "kind",
4456
+ kind: "enum",
4457
+ T: proto3.getEnumType(ParticipantInfo_Kind)
4430
4458
  }]);
4431
4459
  /**
4432
4460
  * @generated from enum livekit.ParticipantInfo.State
@@ -4472,6 +4500,59 @@ proto3.util.setEnumType(ParticipantInfo_State, "livekit.ParticipantInfo.State",
4472
4500
  no: 3,
4473
4501
  name: "DISCONNECTED"
4474
4502
  }]);
4503
+ /**
4504
+ * @generated from enum livekit.ParticipantInfo.Kind
4505
+ */
4506
+ var ParticipantInfo_Kind;
4507
+ (function (ParticipantInfo_Kind) {
4508
+ /**
4509
+ * standard participants, e.g. web clients
4510
+ *
4511
+ * @generated from enum value: STANDARD = 0;
4512
+ */
4513
+ ParticipantInfo_Kind[ParticipantInfo_Kind["STANDARD"] = 0] = "STANDARD";
4514
+ /**
4515
+ * only ingests streams
4516
+ *
4517
+ * @generated from enum value: INGRESS = 1;
4518
+ */
4519
+ ParticipantInfo_Kind[ParticipantInfo_Kind["INGRESS"] = 1] = "INGRESS";
4520
+ /**
4521
+ * only consumes streams
4522
+ *
4523
+ * @generated from enum value: EGRESS = 2;
4524
+ */
4525
+ ParticipantInfo_Kind[ParticipantInfo_Kind["EGRESS"] = 2] = "EGRESS";
4526
+ /**
4527
+ * SIP participants
4528
+ *
4529
+ * @generated from enum value: SIP = 3;
4530
+ */
4531
+ ParticipantInfo_Kind[ParticipantInfo_Kind["SIP"] = 3] = "SIP";
4532
+ /**
4533
+ * LiveKit agents
4534
+ *
4535
+ * @generated from enum value: AGENT = 4;
4536
+ */
4537
+ ParticipantInfo_Kind[ParticipantInfo_Kind["AGENT"] = 4] = "AGENT";
4538
+ })(ParticipantInfo_Kind || (ParticipantInfo_Kind = {}));
4539
+ // Retrieve enum metadata with: proto3.getEnumType(ParticipantInfo_Kind)
4540
+ proto3.util.setEnumType(ParticipantInfo_Kind, "livekit.ParticipantInfo.Kind", [{
4541
+ no: 0,
4542
+ name: "STANDARD"
4543
+ }, {
4544
+ no: 1,
4545
+ name: "INGRESS"
4546
+ }, {
4547
+ no: 2,
4548
+ name: "EGRESS"
4549
+ }, {
4550
+ no: 3,
4551
+ name: "SIP"
4552
+ }, {
4553
+ no: 4,
4554
+ name: "AGENT"
4555
+ }]);
4475
4556
  /**
4476
4557
  * @generated from message livekit.Encryption
4477
4558
  */
@@ -4779,6 +4860,11 @@ TrackInfo.fields = proto3.util.newFieldList(() => [{
4779
4860
  name: "stream",
4780
4861
  kind: "scalar",
4781
4862
  T: 9 /* ScalarType.STRING */
4863
+ }, {
4864
+ no: 18,
4865
+ name: "version",
4866
+ kind: "message",
4867
+ T: TimedVersion
4782
4868
  }]);
4783
4869
  /**
4784
4870
  * provide information about available spatial layers
@@ -4793,7 +4879,7 @@ class VideoLayer extends Message {
4793
4879
  *
4794
4880
  * @generated from field: livekit.VideoQuality quality = 1;
4795
4881
  */
4796
- this.quality = VideoQuality.LOW;
4882
+ this.quality = VideoQuality$1.LOW;
4797
4883
  /**
4798
4884
  * @generated from field: uint32 width = 2;
4799
4885
  */
@@ -4833,7 +4919,7 @@ VideoLayer.fields = proto3.util.newFieldList(() => [{
4833
4919
  no: 1,
4834
4920
  name: "quality",
4835
4921
  kind: "enum",
4836
- T: proto3.getEnumType(VideoQuality)
4922
+ T: proto3.getEnumType(VideoQuality$1)
4837
4923
  }, {
4838
4924
  no: 2,
4839
4925
  name: "width",
@@ -10036,7 +10122,14 @@ var RoomEvent;
10036
10122
  RoomEvent["Reconnected"] = "reconnected";
10037
10123
  /**
10038
10124
  * When disconnected from room. This fires when room.disconnect() is called or
10039
- * when an unrecoverable connection issue had occured
10125
+ * when an unrecoverable connection issue had occured.
10126
+ *
10127
+ * DisconnectReason can be used to determine why the participant was disconnected. Notable reasons are
10128
+ * - DUPLICATE_IDENTITY: another client with the same identity has joined the room
10129
+ * - PARTICIPANT_REMOVED: participant was removed by RemoveParticipant API
10130
+ * - ROOM_DELETED: the room has ended via DeleteRoom API
10131
+ *
10132
+ * args: ([[DisconnectReason]])
10040
10133
  */
10041
10134
  RoomEvent["Disconnected"] = "disconnected";
10042
10135
  /**
@@ -10045,10 +10138,6 @@ var RoomEvent;
10045
10138
  * args: ([[ConnectionState]])
10046
10139
  */
10047
10140
  RoomEvent["ConnectionStateChanged"] = "connectionStateChanged";
10048
- /**
10049
- * @deprecated StateChanged has been renamed to ConnectionStateChanged
10050
- */
10051
- RoomEvent["StateChanged"] = "connectionStateChanged";
10052
10141
  /**
10053
10142
  * When input or output devices on the machine have changed.
10054
10143
  */
@@ -10614,10 +10703,10 @@ function getMatch(exp, ua) {
10614
10703
  return match && match.length >= id && match[id] || '';
10615
10704
  }
10616
10705
 
10617
- var version$1 = "1.15.12";
10706
+ var version$1 = "2.0.1";
10618
10707
 
10619
10708
  const version = version$1;
10620
- const protocolVersion = 11;
10709
+ const protocolVersion = 12;
10621
10710
 
10622
10711
  /**
10623
10712
  * Timers that can be overridden with platform specific implementations
@@ -11783,7 +11872,7 @@ class UpdateTrackSettings extends Message {
11783
11872
  *
11784
11873
  * @generated from field: livekit.VideoQuality quality = 4;
11785
11874
  */
11786
- this.quality = VideoQuality.LOW;
11875
+ this.quality = VideoQuality$1.LOW;
11787
11876
  /**
11788
11877
  * for video, width to receive
11789
11878
  *
@@ -11844,7 +11933,7 @@ UpdateTrackSettings.fields = proto3.util.newFieldList(() => [{
11844
11933
  no: 4,
11845
11934
  name: "quality",
11846
11935
  kind: "enum",
11847
- T: proto3.getEnumType(VideoQuality)
11936
+ T: proto3.getEnumType(VideoQuality$1)
11848
11937
  }, {
11849
11938
  no: 5,
11850
11939
  name: "width",
@@ -11875,6 +11964,7 @@ class LeaveRequest extends Message {
11875
11964
  /**
11876
11965
  * sent when server initiates the disconnect due to server-restart
11877
11966
  * indicates clients should attempt full-reconnect sequence
11967
+ * NOTE: `can_reconnect` obsoleted by `action` starting in protocol version 13
11878
11968
  *
11879
11969
  * @generated from field: bool can_reconnect = 1;
11880
11970
  */
@@ -11883,6 +11973,10 @@ class LeaveRequest extends Message {
11883
11973
  * @generated from field: livekit.DisconnectReason reason = 2;
11884
11974
  */
11885
11975
  this.reason = DisconnectReason.UNKNOWN_REASON;
11976
+ /**
11977
+ * @generated from field: livekit.LeaveRequest.Action action = 3;
11978
+ */
11979
+ this.action = LeaveRequest_Action.DISCONNECT;
11886
11980
  proto3.util.initPartial(data, this);
11887
11981
  }
11888
11982
  static fromBinary(bytes, options) {
@@ -11910,6 +12004,53 @@ LeaveRequest.fields = proto3.util.newFieldList(() => [{
11910
12004
  name: "reason",
11911
12005
  kind: "enum",
11912
12006
  T: proto3.getEnumType(DisconnectReason)
12007
+ }, {
12008
+ no: 3,
12009
+ name: "action",
12010
+ kind: "enum",
12011
+ T: proto3.getEnumType(LeaveRequest_Action)
12012
+ }, {
12013
+ no: 4,
12014
+ name: "regions",
12015
+ kind: "message",
12016
+ T: RegionSettings
12017
+ }]);
12018
+ /**
12019
+ * indicates action clients should take on receiving this message
12020
+ *
12021
+ * @generated from enum livekit.LeaveRequest.Action
12022
+ */
12023
+ var LeaveRequest_Action;
12024
+ (function (LeaveRequest_Action) {
12025
+ /**
12026
+ * should disconnect
12027
+ *
12028
+ * @generated from enum value: DISCONNECT = 0;
12029
+ */
12030
+ LeaveRequest_Action[LeaveRequest_Action["DISCONNECT"] = 0] = "DISCONNECT";
12031
+ /**
12032
+ * should attempt a resume with `reconnect=1` in join URL
12033
+ *
12034
+ * @generated from enum value: RESUME = 1;
12035
+ */
12036
+ LeaveRequest_Action[LeaveRequest_Action["RESUME"] = 1] = "RESUME";
12037
+ /**
12038
+ * should attempt a reconnect, i. e. no `reconnect=1`
12039
+ *
12040
+ * @generated from enum value: RECONNECT = 2;
12041
+ */
12042
+ LeaveRequest_Action[LeaveRequest_Action["RECONNECT"] = 2] = "RECONNECT";
12043
+ })(LeaveRequest_Action || (LeaveRequest_Action = {}));
12044
+ // Retrieve enum metadata with: proto3.getEnumType(LeaveRequest_Action)
12045
+ proto3.util.setEnumType(LeaveRequest_Action, "livekit.LeaveRequest.Action", [{
12046
+ no: 0,
12047
+ name: "DISCONNECT"
12048
+ }, {
12049
+ no: 1,
12050
+ name: "RESUME"
12051
+ }, {
12052
+ no: 2,
12053
+ name: "RECONNECT"
11913
12054
  }]);
11914
12055
  /**
11915
12056
  * message to indicate published video track dimensions are changing
@@ -12292,7 +12433,7 @@ class SubscribedQuality extends Message {
12292
12433
  /**
12293
12434
  * @generated from field: livekit.VideoQuality quality = 1;
12294
12435
  */
12295
- this.quality = VideoQuality.LOW;
12436
+ this.quality = VideoQuality$1.LOW;
12296
12437
  /**
12297
12438
  * @generated from field: bool enabled = 2;
12298
12439
  */
@@ -12318,7 +12459,7 @@ SubscribedQuality.fields = proto3.util.newFieldList(() => [{
12318
12459
  no: 1,
12319
12460
  name: "quality",
12320
12461
  kind: "enum",
12321
- T: proto3.getEnumType(VideoQuality)
12462
+ T: proto3.getEnumType(VideoQuality$1)
12322
12463
  }, {
12323
12464
  no: 2,
12324
12465
  name: "enabled",
@@ -12592,6 +12733,10 @@ class SyncState extends Message {
12592
12733
  * @generated from field: repeated livekit.DataChannelInfo data_channels = 4;
12593
12734
  */
12594
12735
  this.dataChannels = [];
12736
+ /**
12737
+ * @generated from field: repeated string track_sids_disabled = 6;
12738
+ */
12739
+ this.trackSidsDisabled = [];
12595
12740
  proto3.util.initPartial(data, this);
12596
12741
  }
12597
12742
  static fromBinary(bytes, options) {
@@ -12636,6 +12781,12 @@ SyncState.fields = proto3.util.newFieldList(() => [{
12636
12781
  name: "offer",
12637
12782
  kind: "message",
12638
12783
  T: SessionDescription
12784
+ }, {
12785
+ no: 6,
12786
+ name: "track_sids_disabled",
12787
+ kind: "scalar",
12788
+ T: 9 /* ScalarType.STRING */,
12789
+ repeated: true
12639
12790
  }]);
12640
12791
  /**
12641
12792
  * @generated from message livekit.DataChannelInfo
@@ -12997,6 +13148,12 @@ const BACKGROUND_REACTION_DELAY = 5000;
12997
13148
  // keep old audio elements when detached, we would re-use them since on iOS
12998
13149
  // Safari tracks which audio elements have been "blessed" by the user.
12999
13150
  const recycledElements = [];
13151
+ var VideoQuality;
13152
+ (function (VideoQuality) {
13153
+ VideoQuality[VideoQuality["LOW"] = 0] = "LOW";
13154
+ VideoQuality[VideoQuality["MEDIUM"] = 1] = "MEDIUM";
13155
+ VideoQuality[VideoQuality["HIGH"] = 2] = "HIGH";
13156
+ })(VideoQuality || (VideoQuality = {}));
13000
13157
  class Track extends eventsExports.EventEmitter {
13001
13158
  constructor(mediaTrack, kind) {
13002
13159
  let loggerOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
@@ -13657,10 +13814,6 @@ function isFireFox() {
13657
13814
  var _a;
13658
13815
  return ((_a = getBrowser()) === null || _a === void 0 ? void 0 : _a.name) === 'Firefox';
13659
13816
  }
13660
- function isChromiumBased() {
13661
- var _a;
13662
- return ((_a = getBrowser()) === null || _a === void 0 ? void 0 : _a.name) === 'Chrome';
13663
- }
13664
13817
  function isSafari() {
13665
13818
  var _a;
13666
13819
  return ((_a = getBrowser()) === null || _a === void 0 ? void 0 : _a.name) === 'Safari';
@@ -14548,8 +14701,8 @@ class E2EEManager extends eventsExports.EventEmitter {
14548
14701
  room.on(RoomEvent.TrackPublished, (pub, participant) => this.setParticipantCryptorEnabled(pub.trackInfo.encryption !== Encryption_Type.NONE, participant.identity));
14549
14702
  room.on(RoomEvent.ConnectionStateChanged, state => {
14550
14703
  if (state === ConnectionState.Connected) {
14551
- room.participants.forEach(participant => {
14552
- participant.tracks.forEach(pub => {
14704
+ room.remoteParticipants.forEach(participant => {
14705
+ participant.trackPublications.forEach(pub => {
14553
14706
  this.setParticipantCryptorEnabled(pub.trackInfo.encryption !== Encryption_Type.NONE, participant.identity);
14554
14707
  });
14555
14708
  });
@@ -15025,6 +15178,8 @@ class SignalClient {
15025
15178
  }
15026
15179
  this.log.warn("websocket closed", Object.assign(Object.assign({}, this.logContext), {
15027
15180
  reason: ev.reason,
15181
+ code: ev.code,
15182
+ wasClean: ev.wasClean,
15028
15183
  state: this.state
15029
15184
  }));
15030
15185
  this.handleOnClose(ev.reason);
@@ -15441,9 +15596,6 @@ function createConnectionParams(token, info, opts) {
15441
15596
  if (info.browserVersion) {
15442
15597
  params.set('browser_version', info.browserVersion);
15443
15598
  }
15444
- if (opts.publishOnly !== undefined) {
15445
- params.set('publish', opts.publishOnly);
15446
- }
15447
15599
  if (opts.adaptiveStream) {
15448
15600
  params.set('adaptive_stream', '1');
15449
15601
  }
@@ -16145,8 +16297,7 @@ class PCTransport extends eventsExports.EventEmitter {
16145
16297
  return this._pc;
16146
16298
  }
16147
16299
  constructor(config) {
16148
- let mediaConstraints = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
16149
- let loggerOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
16300
+ let loggerOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
16150
16301
  var _a;
16151
16302
  super();
16152
16303
  this.log = livekitLogger;
@@ -16190,13 +16341,10 @@ class PCTransport extends eventsExports.EventEmitter {
16190
16341
  this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.PCTransport);
16191
16342
  this.loggerOptions = loggerOptions;
16192
16343
  this.config = config;
16193
- this.mediaConstraints = mediaConstraints;
16194
16344
  this._pc = this.createPC();
16195
16345
  }
16196
16346
  createPC() {
16197
- const pc = isChromiumBased() ?
16198
- // @ts-expect-error chrome allows additional media constraints to be passed into the RTCPeerConnection constructor
16199
- new RTCPeerConnection(this.config, this.mediaConstraints) : new RTCPeerConnection(this.config);
16347
+ const pc = new RTCPeerConnection(this.config);
16200
16348
  pc.onicecandidate = ev => {
16201
16349
  var _a;
16202
16350
  if (!ev.candidate) return;
@@ -16657,10 +16805,6 @@ function extractStereoAndNackAudioFromOffer(offer) {
16657
16805
 
16658
16806
  const defaultVideoCodec = 'vp8';
16659
16807
  const publishDefaults = {
16660
- /**
16661
- * @deprecated
16662
- */
16663
- audioBitrate: AudioPresets.music.maxBitrate,
16664
16808
  audioPreset: AudioPresets.music,
16665
16809
  dtx: true,
16666
16810
  red: true,
@@ -16685,7 +16829,7 @@ const roomOptionDefaults = {
16685
16829
  stopLocalTrackOnUnpublish: true,
16686
16830
  reconnectPolicy: new DefaultReconnectPolicy(),
16687
16831
  disconnectOnPageLeave: true,
16688
- expWebAudioMix: false
16832
+ webAudioMix: true
16689
16833
  };
16690
16834
  const roomConnectOptionDefaults = {
16691
16835
  autoSubscribe: true,
@@ -16743,13 +16887,8 @@ class PCTransportManager {
16743
16887
  this.loggerOptions = loggerOptions;
16744
16888
  this.isPublisherConnectionRequired = !subscriberPrimary;
16745
16889
  this.isSubscriberConnectionRequired = subscriberPrimary;
16746
- const googConstraints = {
16747
- optional: [{
16748
- googDscp: true
16749
- }]
16750
- };
16751
- this.publisher = new PCTransport(rtcConfig, googConstraints, loggerOptions);
16752
- this.subscriber = new PCTransport(rtcConfig, undefined, loggerOptions);
16890
+ this.publisher = new PCTransport(rtcConfig, loggerOptions);
16891
+ this.subscriber = new PCTransport(rtcConfig, loggerOptions);
16753
16892
  this.publisher.onConnectionStateChange = this.updateState;
16754
16893
  this.subscriber.onConnectionStateChange = this.updateState;
16755
16894
  this.publisher.onIceConnectionStateChange = this.updateState;
@@ -16989,6 +17128,9 @@ class RTCEngine extends eventsExports.EventEmitter {
16989
17128
  get isClosed() {
16990
17129
  return this._isClosed;
16991
17130
  }
17131
+ get pendingReconnect() {
17132
+ return !!this.reconnectTimeout;
17133
+ }
16992
17134
  constructor(options) {
16993
17135
  var _a;
16994
17136
  super();
@@ -17113,7 +17255,7 @@ class RTCEngine extends eventsExports.EventEmitter {
17113
17255
  // since the current engine may have inherited a regional url
17114
17256
  this.regionUrlProvider.updateToken(this.token);
17115
17257
  }
17116
- this.reconnectTimeout = CriticalTimers.setTimeout(() => this.attemptReconnect(disconnectReason), delay);
17258
+ this.reconnectTimeout = CriticalTimers.setTimeout(() => this.attemptReconnect(disconnectReason).finally(() => this.reconnectTimeout = undefined), delay);
17117
17259
  };
17118
17260
  this.waitForRestarted = () => {
17119
17261
  return new Promise((resolve, reject) => {
@@ -17984,10 +18126,14 @@ class RTCEngine extends eventsExports.EventEmitter {
17984
18126
  */
17985
18127
  const autoSubscribe = (_b = (_a = this.signalOpts) === null || _a === void 0 ? void 0 : _a.autoSubscribe) !== null && _b !== void 0 ? _b : true;
17986
18128
  const trackSids = new Array();
18129
+ const trackSidsDisabled = new Array();
17987
18130
  remoteTracks.forEach(track => {
17988
18131
  if (track.isDesired !== autoSubscribe) {
17989
18132
  trackSids.push(track.trackSid);
17990
18133
  }
18134
+ if (!track.isEnabled) {
18135
+ trackSidsDisabled.push(track.trackSid);
18136
+ }
17991
18137
  });
17992
18138
  this.client.sendSyncState(new SyncState({
17993
18139
  answer: previousAnswer ? toProtoSessionDescription({
@@ -18004,7 +18150,8 @@ class RTCEngine extends eventsExports.EventEmitter {
18004
18150
  participantTracks: []
18005
18151
  }),
18006
18152
  publishTracks: getTrackPublicationInfo(localTracks),
18007
- dataChannels: this.dataChannelsInfo()
18153
+ dataChannels: this.dataChannelsInfo(),
18154
+ trackSidsDisabled
18008
18155
  }));
18009
18156
  }
18010
18157
  /* @internal */
@@ -18958,7 +19105,8 @@ class LocalVideoTrack extends LocalTrack {
18958
19105
  }
18959
19106
  addSimulcastTrack(codec, encodings) {
18960
19107
  if (this.simulcastCodecs.has(codec)) {
18961
- throw new Error("".concat(codec, " already added"));
19108
+ this.log.error("".concat(codec, " already added, skipping adding simulcast codec"), this.logContext);
19109
+ return;
18962
19110
  }
18963
19111
  const simulcastCodecInfo = {
18964
19112
  codec,
@@ -19987,7 +20135,7 @@ class Participant extends eventsExports.EventEmitter {
19987
20135
  });
19988
20136
  }
19989
20137
  get isEncrypted() {
19990
- return this.tracks.size > 0 && Array.from(this.tracks.values()).every(tr => tr.isEncrypted);
20138
+ return this.trackPublications.size > 0 && Array.from(this.trackPublications.values()).every(tr => tr.isEncrypted);
19991
20139
  }
19992
20140
  get isAgent() {
19993
20141
  var _a, _b;
@@ -20010,21 +20158,19 @@ class Participant extends eventsExports.EventEmitter {
20010
20158
  this.identity = identity;
20011
20159
  this.name = name;
20012
20160
  this.metadata = metadata;
20013
- this.audioTracks = new Map();
20014
- this.videoTracks = new Map();
20015
- this.tracks = new Map();
20161
+ this.audioTrackPublications = new Map();
20162
+ this.videoTrackPublications = new Map();
20163
+ this.trackPublications = new Map();
20016
20164
  }
20017
- getTracks() {
20018
- return Array.from(this.tracks.values());
20165
+ getTrackPublications() {
20166
+ return Array.from(this.trackPublications.values());
20019
20167
  }
20020
20168
  /**
20021
20169
  * Finds the first track that matches the source filter, for example, getting
20022
20170
  * the user's camera track with getTrackBySource(Track.Source.Camera).
20023
- * @param source
20024
- * @returns
20025
20171
  */
20026
- getTrack(source) {
20027
- for (const [, pub] of this.tracks) {
20172
+ getTrackPublication(source) {
20173
+ for (const [, pub] of this.trackPublications) {
20028
20174
  if (pub.source === source) {
20029
20175
  return pub;
20030
20176
  }
@@ -20032,11 +20178,9 @@ class Participant extends eventsExports.EventEmitter {
20032
20178
  }
20033
20179
  /**
20034
20180
  * Finds the first track that matches the track's name.
20035
- * @param name
20036
- * @returns
20037
20181
  */
20038
- getTrackByName(name) {
20039
- for (const [, pub] of this.tracks) {
20182
+ getTrackPublicationByName(name) {
20183
+ for (const [, pub] of this.trackPublications) {
20040
20184
  if (pub.trackName === name) {
20041
20185
  return pub;
20042
20186
  }
@@ -20047,16 +20191,16 @@ class Participant extends eventsExports.EventEmitter {
20047
20191
  }
20048
20192
  get isCameraEnabled() {
20049
20193
  var _a;
20050
- const track = this.getTrack(Track.Source.Camera);
20194
+ const track = this.getTrackPublication(Track.Source.Camera);
20051
20195
  return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true);
20052
20196
  }
20053
20197
  get isMicrophoneEnabled() {
20054
20198
  var _a;
20055
- const track = this.getTrack(Track.Source.Microphone);
20199
+ const track = this.getTrackPublication(Track.Source.Microphone);
20056
20200
  return !((_a = track === null || track === void 0 ? void 0 : track.isMuted) !== null && _a !== void 0 ? _a : true);
20057
20201
  }
20058
20202
  get isScreenShareEnabled() {
20059
- const track = this.getTrack(Track.Source.ScreenShare);
20203
+ const track = this.getTrackPublication(Track.Source.ScreenShare);
20060
20204
  return !!track;
20061
20205
  }
20062
20206
  get isLocal() {
@@ -20150,7 +20294,7 @@ class Participant extends eventsExports.EventEmitter {
20150
20294
  */
20151
20295
  setAudioContext(ctx) {
20152
20296
  this.audioContext = ctx;
20153
- this.audioTracks.forEach(track => (track.track instanceof RemoteAudioTrack || track.track instanceof LocalAudioTrack) && track.track.setAudioContext(ctx));
20297
+ this.audioTrackPublications.forEach(track => (track.track instanceof RemoteAudioTrack || track.track instanceof LocalAudioTrack) && track.track.setAudioContext(ctx));
20154
20298
  }
20155
20299
  addTrackPublication(publication) {
20156
20300
  // forward publication driven events
@@ -20164,13 +20308,13 @@ class Participant extends eventsExports.EventEmitter {
20164
20308
  if (pub.track) {
20165
20309
  pub.track.sid = publication.trackSid;
20166
20310
  }
20167
- this.tracks.set(publication.trackSid, publication);
20311
+ this.trackPublications.set(publication.trackSid, publication);
20168
20312
  switch (publication.kind) {
20169
20313
  case Track.Kind.Audio:
20170
- this.audioTracks.set(publication.trackSid, publication);
20314
+ this.audioTrackPublications.set(publication.trackSid, publication);
20171
20315
  break;
20172
20316
  case Track.Kind.Video:
20173
- this.videoTracks.set(publication.trackSid, publication);
20317
+ this.videoTrackPublications.set(publication.trackSid, publication);
20174
20318
  break;
20175
20319
  }
20176
20320
  }
@@ -20189,1541 +20333,1545 @@ function trackPermissionToProto(perms) {
20189
20333
  });
20190
20334
  }
20191
20335
 
20192
- class RemoteTrackPublication extends TrackPublication {
20193
- constructor(kind, ti, autoSubscribe, loggerOptions) {
20194
- super(kind, ti.sid, ti.name, loggerOptions);
20195
- this.track = undefined;
20336
+ class LocalParticipant extends Participant {
20337
+ /** @internal */
20338
+ constructor(sid, identity, engine, options) {
20339
+ super(sid, identity, undefined, undefined, {
20340
+ loggerName: options.loggerName,
20341
+ loggerContextCb: () => this.engine.logContext
20342
+ });
20343
+ this.pendingPublishing = new Set();
20344
+ this.pendingPublishPromises = new Map();
20345
+ this.participantTrackPermissions = [];
20346
+ this.allParticipantsAllowedToSubscribe = true;
20347
+ this.encryptionType = Encryption_Type.NONE;
20348
+ this.handleReconnecting = () => {
20349
+ if (!this.reconnectFuture) {
20350
+ this.reconnectFuture = new Future();
20351
+ }
20352
+ };
20353
+ this.handleReconnected = () => {
20354
+ var _a, _b;
20355
+ (_b = (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.resolve) === null || _b === void 0 ? void 0 : _b.call(_a);
20356
+ this.reconnectFuture = undefined;
20357
+ this.updateTrackSubscriptionPermissions();
20358
+ };
20359
+ this.handleDisconnected = () => {
20360
+ var _a, _b;
20361
+ if (this.reconnectFuture) {
20362
+ this.reconnectFuture.promise.catch(e => this.log.warn(e.message, this.logContext));
20363
+ (_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');
20364
+ this.reconnectFuture = undefined;
20365
+ }
20366
+ };
20367
+ this.updateTrackSubscriptionPermissions = () => {
20368
+ this.log.debug('updating track subscription permissions', Object.assign(Object.assign({}, this.logContext), {
20369
+ allParticipantsAllowed: this.allParticipantsAllowedToSubscribe,
20370
+ participantTrackPermissions: this.participantTrackPermissions
20371
+ }));
20372
+ this.engine.client.sendUpdateSubscriptionPermissions(this.allParticipantsAllowedToSubscribe, this.participantTrackPermissions.map(p => trackPermissionToProto(p)));
20373
+ };
20196
20374
  /** @internal */
20197
- this.allowed = true;
20198
- this.disabled = false;
20199
- this.currentVideoQuality = VideoQuality.HIGH;
20200
- this.handleEnded = track => {
20201
- this.setTrack(undefined);
20202
- this.emit(TrackEvent.Ended, track);
20375
+ this.onTrackUnmuted = track => {
20376
+ this.onTrackMuted(track, track.isUpstreamPaused);
20203
20377
  };
20204
- this.handleVisibilityChange = visible => {
20205
- this.log.debug("adaptivestream video visibility ".concat(this.trackSid, ", visible=").concat(visible), this.logContext);
20206
- this.disabled = !visible;
20207
- this.emitTrackUpdate();
20378
+ // when the local track changes in mute status, we'll notify server as such
20379
+ /** @internal */
20380
+ this.onTrackMuted = (track, muted) => {
20381
+ if (muted === undefined) {
20382
+ muted = true;
20383
+ }
20384
+ if (!track.sid) {
20385
+ this.log.error('could not update mute status for unpublished track', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20386
+ return;
20387
+ }
20388
+ this.engine.updateMuteStatus(track.sid, muted);
20208
20389
  };
20209
- this.handleVideoDimensionsChange = dimensions => {
20210
- this.log.debug("adaptivestream video dimensions ".concat(dimensions.width, "x").concat(dimensions.height), this.logContext);
20211
- this.videoDimensions = dimensions;
20212
- this.emitTrackUpdate();
20390
+ this.onTrackUpstreamPaused = track => {
20391
+ this.log.debug('upstream paused', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20392
+ this.onTrackMuted(track, true);
20213
20393
  };
20214
- this.subscribed = autoSubscribe;
20215
- this.updateInfo(ti);
20216
- }
20217
- /**
20218
- * Subscribe or unsubscribe to this remote track
20219
- * @param subscribed true to subscribe to a track, false to unsubscribe
20220
- */
20221
- setSubscribed(subscribed) {
20222
- const prevStatus = this.subscriptionStatus;
20223
- const prevPermission = this.permissionStatus;
20224
- this.subscribed = subscribed;
20225
- // reset allowed status when desired subscription state changes
20226
- // server will notify client via signal message if it's not allowed
20227
- if (subscribed) {
20228
- this.allowed = true;
20229
- }
20230
- const sub = new UpdateSubscription({
20231
- trackSids: [this.trackSid],
20232
- subscribe: this.subscribed,
20233
- participantTracks: [new ParticipantTracks({
20234
- // sending an empty participant id since TrackPublication doesn't keep it
20235
- // this is filled in by the participant that receives this message
20236
- participantSid: '',
20237
- trackSids: [this.trackSid]
20238
- })]
20394
+ this.onTrackUpstreamResumed = track => {
20395
+ this.log.debug('upstream resumed', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20396
+ this.onTrackMuted(track, track.isMuted);
20397
+ };
20398
+ this.handleSubscribedQualityUpdate = update => __awaiter(this, void 0, void 0, function* () {
20399
+ var _a, e_1, _b, _c;
20400
+ var _d, _e;
20401
+ if (!((_d = this.roomOptions) === null || _d === void 0 ? void 0 : _d.dynacast)) {
20402
+ return;
20403
+ }
20404
+ const pub = this.videoTrackPublications.get(update.trackSid);
20405
+ if (!pub) {
20406
+ this.log.warn('received subscribed quality update for unknown track', Object.assign(Object.assign({}, this.logContext), {
20407
+ trackSid: update.trackSid
20408
+ }));
20409
+ return;
20410
+ }
20411
+ if (update.subscribedCodecs.length > 0) {
20412
+ if (!pub.videoTrack) {
20413
+ return;
20414
+ }
20415
+ const newCodecs = yield pub.videoTrack.setPublishingCodecs(update.subscribedCodecs);
20416
+ try {
20417
+ 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) {
20418
+ _c = newCodecs_1_1.value;
20419
+ _f = false;
20420
+ const codec = _c;
20421
+ if (isBackupCodec(codec)) {
20422
+ this.log.debug("publish ".concat(codec, " for ").concat(pub.videoTrack.sid), Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(pub)));
20423
+ yield this.publishAdditionalCodecForTrack(pub.videoTrack, codec, pub.options);
20424
+ }
20425
+ }
20426
+ } catch (e_1_1) {
20427
+ e_1 = {
20428
+ error: e_1_1
20429
+ };
20430
+ } finally {
20431
+ try {
20432
+ if (!_f && !_a && (_b = newCodecs_1.return)) yield _b.call(newCodecs_1);
20433
+ } finally {
20434
+ if (e_1) throw e_1.error;
20435
+ }
20436
+ }
20437
+ } else if (update.subscribedQualities.length > 0) {
20438
+ yield (_e = pub.videoTrack) === null || _e === void 0 ? void 0 : _e.setPublishingLayers(update.subscribedQualities);
20439
+ }
20239
20440
  });
20240
- this.emit(TrackEvent.UpdateSubscription, sub);
20241
- this.emitSubscriptionUpdateIfChanged(prevStatus);
20242
- this.emitPermissionUpdateIfChanged(prevPermission);
20243
- }
20244
- get subscriptionStatus() {
20245
- if (this.subscribed === false) {
20246
- return TrackPublication.SubscriptionStatus.Unsubscribed;
20247
- }
20248
- if (!super.isSubscribed) {
20249
- return TrackPublication.SubscriptionStatus.Desired;
20441
+ this.handleLocalTrackUnpublished = unpublished => {
20442
+ const track = this.trackPublications.get(unpublished.trackSid);
20443
+ if (!track) {
20444
+ this.log.warn('received unpublished event for unknown track', Object.assign(Object.assign({}, this.logContext), {
20445
+ trackSid: unpublished.trackSid
20446
+ }));
20447
+ return;
20448
+ }
20449
+ this.unpublishTrack(track.track);
20450
+ };
20451
+ this.handleTrackEnded = track => __awaiter(this, void 0, void 0, function* () {
20452
+ if (track.source === Track.Source.ScreenShare || track.source === Track.Source.ScreenShareAudio) {
20453
+ this.log.debug('unpublishing local track due to TrackEnded', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20454
+ this.unpublishTrack(track);
20455
+ } else if (track.isUserProvided) {
20456
+ yield track.mute();
20457
+ } else if (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) {
20458
+ try {
20459
+ if (isWeb()) {
20460
+ try {
20461
+ const currentPermissions = yield navigator === null || navigator === void 0 ? void 0 : navigator.permissions.query({
20462
+ // the permission query for camera and microphone currently not supported in Safari and Firefox
20463
+ // @ts-ignore
20464
+ name: track.source === Track.Source.Camera ? 'camera' : 'microphone'
20465
+ });
20466
+ if (currentPermissions && currentPermissions.state === 'denied') {
20467
+ this.log.warn("user has revoked access to ".concat(track.source), Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20468
+ // detect granted change after permissions were denied to try and resume then
20469
+ currentPermissions.onchange = () => {
20470
+ if (currentPermissions.state !== 'denied') {
20471
+ if (!track.isMuted) {
20472
+ track.restartTrack();
20473
+ }
20474
+ currentPermissions.onchange = null;
20475
+ }
20476
+ };
20477
+ throw new Error('GetUserMedia Permission denied');
20478
+ }
20479
+ } catch (e) {
20480
+ // permissions query fails for firefox, we continue and try to restart the track
20481
+ }
20482
+ }
20483
+ if (!track.isMuted) {
20484
+ this.log.debug('track ended, attempting to use a different device', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20485
+ yield track.restartTrack();
20486
+ }
20487
+ } catch (e) {
20488
+ this.log.warn("could not restart track, muting instead", Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20489
+ yield track.mute();
20490
+ }
20491
+ }
20492
+ });
20493
+ this.audioTrackPublications = new Map();
20494
+ this.videoTrackPublications = new Map();
20495
+ this.trackPublications = new Map();
20496
+ this.engine = engine;
20497
+ this.roomOptions = options;
20498
+ this.setupEngine(engine);
20499
+ this.activeDeviceMap = new Map();
20500
+ }
20501
+ get lastCameraError() {
20502
+ return this.cameraError;
20503
+ }
20504
+ get lastMicrophoneError() {
20505
+ return this.microphoneError;
20506
+ }
20507
+ get isE2EEEnabled() {
20508
+ return this.encryptionType !== Encryption_Type.NONE;
20509
+ }
20510
+ getTrackPublication(source) {
20511
+ const track = super.getTrackPublication(source);
20512
+ if (track) {
20513
+ return track;
20250
20514
  }
20251
- return TrackPublication.SubscriptionStatus.Subscribed;
20252
20515
  }
20253
- get permissionStatus() {
20254
- return this.allowed ? TrackPublication.PermissionStatus.Allowed : TrackPublication.PermissionStatus.NotAllowed;
20516
+ getTrackPublicationByName(name) {
20517
+ const track = super.getTrackPublicationByName(name);
20518
+ if (track) {
20519
+ return track;
20520
+ }
20255
20521
  }
20256
20522
  /**
20257
- * Returns true if track is subscribed, and ready for playback
20523
+ * @internal
20258
20524
  */
20259
- get isSubscribed() {
20260
- if (this.subscribed === false) {
20261
- return false;
20262
- }
20263
- return super.isSubscribed;
20264
- }
20265
- // returns client's desire to subscribe to a track, also true if autoSubscribe is enabled
20266
- get isDesired() {
20267
- return this.subscribed !== false;
20525
+ setupEngine(engine) {
20526
+ this.engine = engine;
20527
+ this.engine.on(EngineEvent.RemoteMute, (trackSid, muted) => {
20528
+ const pub = this.trackPublications.get(trackSid);
20529
+ if (!pub || !pub.track) {
20530
+ return;
20531
+ }
20532
+ if (muted) {
20533
+ pub.mute();
20534
+ } else {
20535
+ pub.unmute();
20536
+ }
20537
+ });
20538
+ 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);
20268
20539
  }
20269
- get isEnabled() {
20270
- return !this.disabled;
20540
+ /**
20541
+ * Sets and updates the metadata of the local participant.
20542
+ * The change does not take immediate effect.
20543
+ * If successful, a `ParticipantEvent.MetadataChanged` event will be emitted on the local participant.
20544
+ * Note: this requires `canUpdateOwnMetadata` permission.
20545
+ * @param metadata
20546
+ */
20547
+ setMetadata(metadata) {
20548
+ var _a;
20549
+ this.engine.client.sendUpdateLocalMetadata(metadata, (_a = this.name) !== null && _a !== void 0 ? _a : '');
20271
20550
  }
20272
20551
  /**
20273
- * disable server from sending down data for this track. this is useful when
20274
- * the participant is off screen, you may disable streaming down their video
20275
- * to reduce bandwidth requirements
20276
- * @param enabled
20552
+ * Sets and updates the name of the local participant.
20553
+ * The change does not take immediate effect.
20554
+ * If successful, a `ParticipantEvent.ParticipantNameChanged` event will be emitted on the local participant.
20555
+ * Note: this requires `canUpdateOwnMetadata` permission.
20556
+ * @param metadata
20277
20557
  */
20278
- setEnabled(enabled) {
20279
- if (!this.isManualOperationAllowed() || this.disabled === !enabled) {
20280
- return;
20281
- }
20282
- this.disabled = !enabled;
20283
- this.emitTrackUpdate();
20558
+ setName(name) {
20559
+ var _a;
20560
+ this.engine.client.sendUpdateLocalMetadata((_a = this.metadata) !== null && _a !== void 0 ? _a : '', name);
20284
20561
  }
20285
20562
  /**
20286
- * for tracks that support simulcasting, adjust subscribed quality
20563
+ * Enable or disable a participant's camera track.
20287
20564
  *
20288
- * This indicates the highest quality the client can accept. if network
20289
- * bandwidth does not allow, server will automatically reduce quality to
20290
- * optimize for uninterrupted video
20565
+ * If a track has already published, it'll mute or unmute the track.
20566
+ * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20291
20567
  */
20292
- setVideoQuality(quality) {
20293
- if (!this.isManualOperationAllowed() || this.currentVideoQuality === quality) {
20294
- return;
20295
- }
20296
- this.currentVideoQuality = quality;
20297
- this.videoDimensions = undefined;
20298
- this.emitTrackUpdate();
20299
- }
20300
- setVideoDimensions(dimensions) {
20301
- var _a, _b;
20302
- if (!this.isManualOperationAllowed()) {
20303
- return;
20304
- }
20305
- if (((_a = this.videoDimensions) === null || _a === void 0 ? void 0 : _a.width) === dimensions.width && ((_b = this.videoDimensions) === null || _b === void 0 ? void 0 : _b.height) === dimensions.height) {
20306
- return;
20307
- }
20308
- if (this.track instanceof RemoteVideoTrack) {
20309
- this.videoDimensions = dimensions;
20310
- }
20311
- this.currentVideoQuality = undefined;
20312
- this.emitTrackUpdate();
20568
+ setCameraEnabled(enabled, options, publishOptions) {
20569
+ return this.setTrackEnabled(Track.Source.Camera, enabled, options, publishOptions);
20313
20570
  }
20314
- setVideoFPS(fps) {
20315
- if (!this.isManualOperationAllowed()) {
20316
- return;
20317
- }
20318
- if (!(this.track instanceof RemoteVideoTrack)) {
20319
- return;
20320
- }
20321
- if (this.fps === fps) {
20322
- return;
20323
- }
20324
- this.fps = fps;
20325
- this.emitTrackUpdate();
20571
+ /**
20572
+ * Enable or disable a participant's microphone track.
20573
+ *
20574
+ * If a track has already published, it'll mute or unmute the track.
20575
+ * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20576
+ */
20577
+ setMicrophoneEnabled(enabled, options, publishOptions) {
20578
+ return this.setTrackEnabled(Track.Source.Microphone, enabled, options, publishOptions);
20326
20579
  }
20327
- get videoQuality() {
20328
- return this.currentVideoQuality;
20580
+ /**
20581
+ * Start or stop sharing a participant's screen
20582
+ * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20583
+ */
20584
+ setScreenShareEnabled(enabled, options, publishOptions) {
20585
+ return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options, publishOptions);
20329
20586
  }
20330
20587
  /** @internal */
20331
- setTrack(track) {
20332
- const prevStatus = this.subscriptionStatus;
20333
- const prevPermission = this.permissionStatus;
20334
- const prevTrack = this.track;
20335
- if (prevTrack === track) {
20336
- return;
20337
- }
20338
- if (prevTrack) {
20339
- // unregister listener
20340
- prevTrack.off(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
20341
- prevTrack.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
20342
- prevTrack.off(TrackEvent.Ended, this.handleEnded);
20343
- prevTrack.detach();
20344
- prevTrack.stopMonitor();
20345
- this.emit(TrackEvent.Unsubscribed, prevTrack);
20346
- }
20347
- super.setTrack(track);
20348
- if (track) {
20349
- track.sid = this.trackSid;
20350
- track.on(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
20351
- track.on(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
20352
- track.on(TrackEvent.Ended, this.handleEnded);
20353
- this.emit(TrackEvent.Subscribed, track);
20588
+ setPermissions(permissions) {
20589
+ const prevPermissions = this.permissions;
20590
+ const changed = super.setPermissions(permissions);
20591
+ if (changed && prevPermissions) {
20592
+ this.emit(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions);
20354
20593
  }
20355
- this.emitPermissionUpdateIfChanged(prevPermission);
20356
- this.emitSubscriptionUpdateIfChanged(prevStatus);
20357
- }
20358
- /** @internal */
20359
- setAllowed(allowed) {
20360
- const prevStatus = this.subscriptionStatus;
20361
- const prevPermission = this.permissionStatus;
20362
- this.allowed = allowed;
20363
- this.emitPermissionUpdateIfChanged(prevPermission);
20364
- this.emitSubscriptionUpdateIfChanged(prevStatus);
20365
- }
20366
- /** @internal */
20367
- setSubscriptionError(error) {
20368
- this.emit(TrackEvent.SubscriptionFailed, error);
20594
+ return changed;
20369
20595
  }
20370
20596
  /** @internal */
20371
- updateInfo(info) {
20372
- super.updateInfo(info);
20373
- const prevMetadataMuted = this.metadataMuted;
20374
- this.metadataMuted = info.muted;
20375
- if (this.track) {
20376
- this.track.setMuted(info.muted);
20377
- } else if (prevMetadataMuted !== info.muted) {
20378
- this.emit(info.muted ? TrackEvent.Muted : TrackEvent.Unmuted);
20379
- }
20380
- }
20381
- emitSubscriptionUpdateIfChanged(previousStatus) {
20382
- const currentStatus = this.subscriptionStatus;
20383
- if (previousStatus === currentStatus) {
20384
- return;
20385
- }
20386
- this.emit(TrackEvent.SubscriptionStatusChanged, currentStatus, previousStatus);
20597
+ setE2EEEnabled(enabled) {
20598
+ return __awaiter(this, void 0, void 0, function* () {
20599
+ this.encryptionType = enabled ? Encryption_Type.GCM : Encryption_Type.NONE;
20600
+ yield this.republishAllTracks(undefined, false);
20601
+ });
20387
20602
  }
20388
- emitPermissionUpdateIfChanged(previousPermissionStatus) {
20389
- const currentPermissionStatus = this.permissionStatus;
20390
- if (currentPermissionStatus !== previousPermissionStatus) {
20391
- this.emit(TrackEvent.SubscriptionPermissionChanged, this.permissionStatus, previousPermissionStatus);
20392
- }
20393
- }
20394
- isManualOperationAllowed() {
20395
- if (this.kind === Track.Kind.Video && this.isAdaptiveStream) {
20396
- this.log.warn('adaptive stream is enabled, cannot change video track settings', this.logContext);
20397
- return false;
20398
- }
20399
- if (!this.isDesired) {
20400
- this.log.warn('cannot update track settings when not subscribed', this.logContext);
20401
- return false;
20402
- }
20403
- return true;
20404
- }
20405
- get isAdaptiveStream() {
20406
- return this.track instanceof RemoteVideoTrack && this.track.isAdaptiveStream;
20407
- }
20408
- /* @internal */
20409
- emitTrackUpdate() {
20410
- const settings = new UpdateTrackSettings({
20411
- trackSids: [this.trackSid],
20412
- disabled: this.disabled,
20413
- fps: this.fps
20414
- });
20415
- if (this.videoDimensions) {
20416
- settings.width = Math.ceil(this.videoDimensions.width);
20417
- settings.height = Math.ceil(this.videoDimensions.height);
20418
- } else if (this.currentVideoQuality !== undefined) {
20419
- settings.quality = this.currentVideoQuality;
20420
- } else {
20421
- // defaults to high quality
20422
- settings.quality = VideoQuality.HIGH;
20423
- }
20424
- this.emit(TrackEvent.UpdateSettings, settings);
20425
- }
20426
- }
20427
-
20428
- class RemoteParticipant extends Participant {
20429
- /** @internal */
20430
- static fromParticipantInfo(signalClient, pi) {
20431
- return new RemoteParticipant(signalClient, pi.sid, pi.identity, pi.name, pi.metadata);
20432
- }
20433
- /** @internal */
20434
- constructor(signalClient, sid, identity, name, metadata, loggerOptions) {
20435
- super(sid, identity || '', name, metadata, loggerOptions);
20436
- this.signalClient = signalClient;
20437
- this.tracks = new Map();
20438
- this.audioTracks = new Map();
20439
- this.videoTracks = new Map();
20440
- this.volumeMap = new Map();
20441
- }
20442
- addTrackPublication(publication) {
20443
- super.addTrackPublication(publication);
20444
- // register action events
20445
- publication.on(TrackEvent.UpdateSettings, settings => {
20446
- this.log.debug('send update settings', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
20447
- this.signalClient.sendUpdateTrackSettings(settings);
20448
- });
20449
- publication.on(TrackEvent.UpdateSubscription, sub => {
20450
- sub.participantTracks.forEach(pt => {
20451
- pt.participantSid = this.sid;
20452
- });
20453
- this.signalClient.sendUpdateSubscription(sub);
20454
- });
20455
- publication.on(TrackEvent.SubscriptionPermissionChanged, status => {
20456
- this.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, publication, status);
20457
- });
20458
- publication.on(TrackEvent.SubscriptionStatusChanged, status => {
20459
- this.emit(ParticipantEvent.TrackSubscriptionStatusChanged, publication, status);
20460
- });
20461
- publication.on(TrackEvent.Subscribed, track => {
20462
- this.emit(ParticipantEvent.TrackSubscribed, track, publication);
20463
- });
20464
- publication.on(TrackEvent.Unsubscribed, previousTrack => {
20465
- this.emit(ParticipantEvent.TrackUnsubscribed, previousTrack, publication);
20466
- });
20467
- publication.on(TrackEvent.SubscriptionFailed, error => {
20468
- this.emit(ParticipantEvent.TrackSubscriptionFailed, publication.trackSid, error);
20469
- });
20470
- }
20471
- getTrack(source) {
20472
- const track = super.getTrack(source);
20473
- if (track) {
20474
- return track;
20475
- }
20476
- }
20477
- getTrackByName(name) {
20478
- const track = super.getTrackByName(name);
20479
- if (track) {
20603
+ setTrackEnabled(source, enabled, options, publishOptions) {
20604
+ var _a, _b;
20605
+ return __awaiter(this, void 0, void 0, function* () {
20606
+ this.log.debug('setTrackEnabled', Object.assign(Object.assign({}, this.logContext), {
20607
+ source,
20608
+ enabled
20609
+ }));
20610
+ let track = this.getTrackPublication(source);
20611
+ if (enabled) {
20612
+ if (track) {
20613
+ yield track.unmute();
20614
+ } else {
20615
+ let localTracks;
20616
+ if (this.pendingPublishing.has(source)) {
20617
+ this.log.info('skipping duplicate published source', Object.assign(Object.assign({}, this.logContext), {
20618
+ source
20619
+ }));
20620
+ // no-op it's already been requested
20621
+ return;
20622
+ }
20623
+ this.pendingPublishing.add(source);
20624
+ try {
20625
+ switch (source) {
20626
+ case Track.Source.Camera:
20627
+ localTracks = yield this.createTracks({
20628
+ video: (_a = options) !== null && _a !== void 0 ? _a : true
20629
+ });
20630
+ break;
20631
+ case Track.Source.Microphone:
20632
+ localTracks = yield this.createTracks({
20633
+ audio: (_b = options) !== null && _b !== void 0 ? _b : true
20634
+ });
20635
+ break;
20636
+ case Track.Source.ScreenShare:
20637
+ localTracks = yield this.createScreenTracks(Object.assign({}, options));
20638
+ break;
20639
+ default:
20640
+ throw new TrackInvalidError(source);
20641
+ }
20642
+ const publishPromises = [];
20643
+ for (const localTrack of localTracks) {
20644
+ this.log.info('publishing track', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(localTrack)));
20645
+ publishPromises.push(this.publishTrack(localTrack, publishOptions));
20646
+ }
20647
+ const publishedTracks = yield Promise.all(publishPromises);
20648
+ // for screen share publications including audio, this will only return the screen share publication, not the screen share audio one
20649
+ // revisit if we want to return an array of tracks instead for v2
20650
+ [track] = publishedTracks;
20651
+ } catch (e) {
20652
+ localTracks === null || localTracks === void 0 ? void 0 : localTracks.forEach(tr => {
20653
+ tr.stop();
20654
+ });
20655
+ if (e instanceof Error && !(e instanceof TrackInvalidError)) {
20656
+ this.emit(ParticipantEvent.MediaDevicesError, e);
20657
+ }
20658
+ throw e;
20659
+ } finally {
20660
+ this.pendingPublishing.delete(source);
20661
+ }
20662
+ }
20663
+ } else if (track && track.track) {
20664
+ // screenshare cannot be muted, unpublish instead
20665
+ if (source === Track.Source.ScreenShare) {
20666
+ track = yield this.unpublishTrack(track.track);
20667
+ const screenAudioTrack = this.getTrackPublication(Track.Source.ScreenShareAudio);
20668
+ if (screenAudioTrack && screenAudioTrack.track) {
20669
+ this.unpublishTrack(screenAudioTrack.track);
20670
+ }
20671
+ } else {
20672
+ yield track.mute();
20673
+ }
20674
+ }
20480
20675
  return track;
20481
- }
20676
+ });
20482
20677
  }
20483
20678
  /**
20484
- * sets the volume on the participant's audio track
20485
- * by default, this affects the microphone publication
20486
- * a different source can be passed in as a second argument
20487
- * if no track exists the volume will be applied when the microphone track is added
20679
+ * Publish both camera and microphone at the same time. This is useful for
20680
+ * displaying a single Permission Dialog box to the end user.
20488
20681
  */
20489
- setVolume(volume) {
20490
- let source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Track.Source.Microphone;
20491
- this.volumeMap.set(source, volume);
20492
- const audioPublication = this.getTrack(source);
20493
- if (audioPublication && audioPublication.track) {
20494
- audioPublication.track.setVolume(volume);
20495
- }
20682
+ enableCameraAndMicrophone() {
20683
+ return __awaiter(this, void 0, void 0, function* () {
20684
+ if (this.pendingPublishing.has(Track.Source.Camera) || this.pendingPublishing.has(Track.Source.Microphone)) {
20685
+ // no-op it's already been requested
20686
+ return;
20687
+ }
20688
+ this.pendingPublishing.add(Track.Source.Camera);
20689
+ this.pendingPublishing.add(Track.Source.Microphone);
20690
+ try {
20691
+ const tracks = yield this.createTracks({
20692
+ audio: true,
20693
+ video: true
20694
+ });
20695
+ yield Promise.all(tracks.map(track => this.publishTrack(track)));
20696
+ } finally {
20697
+ this.pendingPublishing.delete(Track.Source.Camera);
20698
+ this.pendingPublishing.delete(Track.Source.Microphone);
20699
+ }
20700
+ });
20496
20701
  }
20497
20702
  /**
20498
- * gets the volume on the participant's microphone track
20703
+ * Create local camera and/or microphone tracks
20704
+ * @param options
20705
+ * @returns
20499
20706
  */
20500
- getVolume() {
20501
- let source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Track.Source.Microphone;
20502
- const audioPublication = this.getTrack(source);
20503
- if (audioPublication && audioPublication.track) {
20504
- return audioPublication.track.getVolume();
20505
- }
20506
- return this.volumeMap.get(source);
20507
- }
20508
- /** @internal */
20509
- addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft) {
20510
- // find the track publication
20511
- // it's possible for the media track to arrive before participant info
20512
- let publication = this.getTrackPublication(sid);
20513
- // it's also possible that the browser didn't honor our original track id
20514
- // FireFox would use its own local uuid instead of server track id
20515
- if (!publication) {
20516
- if (!sid.startsWith('TR')) {
20517
- // find the first track that matches type
20518
- this.tracks.forEach(p => {
20519
- if (!publication && mediaTrack.kind === p.kind.toString()) {
20520
- publication = p;
20707
+ createTracks(options) {
20708
+ var _a, _b;
20709
+ return __awaiter(this, void 0, void 0, function* () {
20710
+ 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);
20711
+ const constraints = constraintsForOptions(opts);
20712
+ let stream;
20713
+ try {
20714
+ stream = yield navigator.mediaDevices.getUserMedia(constraints);
20715
+ } catch (err) {
20716
+ if (err instanceof Error) {
20717
+ if (constraints.audio) {
20718
+ this.microphoneError = err;
20521
20719
  }
20522
- });
20720
+ if (constraints.video) {
20721
+ this.cameraError = err;
20722
+ }
20723
+ }
20724
+ throw err;
20523
20725
  }
20524
- }
20525
- // when we couldn't locate the track, it's possible that the metadata hasn't
20526
- // yet arrived. Wait a bit longer for it to arrive, or fire an error
20527
- if (!publication) {
20528
- if (triesLeft === 0) {
20529
- this.log.error('could not find published track', Object.assign(Object.assign({}, this.logContext), {
20530
- trackSid: sid
20531
- }));
20532
- this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
20533
- return;
20726
+ if (constraints.audio) {
20727
+ this.microphoneError = undefined;
20728
+ this.emit(ParticipantEvent.AudioStreamAcquired);
20534
20729
  }
20535
- if (triesLeft === undefined) triesLeft = 20;
20536
- setTimeout(() => {
20537
- this.addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft - 1);
20538
- }, 150);
20539
- return;
20540
- }
20541
- if (mediaTrack.readyState === 'ended') {
20542
- this.log.error('unable to subscribe because MediaStreamTrack is ended. Do not call MediaStreamTrack.stop()', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
20543
- this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
20544
- return;
20545
- }
20546
- const isVideo = mediaTrack.kind === 'video';
20547
- let track;
20548
- if (isVideo) {
20549
- track = new RemoteVideoTrack(mediaTrack, sid, receiver, adaptiveStreamSettings);
20550
- } else {
20551
- track = new RemoteAudioTrack(mediaTrack, sid, receiver, this.audioContext, this.audioOutput);
20552
- }
20553
- // set track info
20554
- track.source = publication.source;
20555
- // keep publication's muted status
20556
- track.isMuted = publication.isMuted;
20557
- track.setMediaStream(mediaStream);
20558
- track.start();
20559
- publication.setTrack(track);
20560
- // set participant volumes on new audio tracks
20561
- if (this.volumeMap.has(publication.source) && track instanceof RemoteAudioTrack) {
20562
- track.setVolume(this.volumeMap.get(publication.source));
20563
- }
20564
- return publication;
20565
- }
20566
- /** @internal */
20567
- get hasMetadata() {
20568
- return !!this.participantInfo;
20569
- }
20570
- getTrackPublication(sid) {
20571
- return this.tracks.get(sid);
20572
- }
20573
- /** @internal */
20574
- updateInfo(info) {
20575
- if (!super.updateInfo(info)) {
20576
- return false;
20577
- }
20578
- // we are getting a list of all available tracks, reconcile in here
20579
- // and send out events for changes
20580
- // reconcile track publications, publish events only if metadata is already there
20581
- // i.e. changes since the local participant has joined
20582
- const validTracks = new Map();
20583
- const newTracks = new Map();
20584
- info.tracks.forEach(ti => {
20585
- var _a, _b;
20586
- let publication = this.getTrackPublication(ti.sid);
20587
- if (!publication) {
20588
- // new publication
20589
- const kind = Track.kindFromProto(ti.type);
20590
- if (!kind) {
20591
- return;
20730
+ if (constraints.video) {
20731
+ this.cameraError = undefined;
20732
+ }
20733
+ return stream.getTracks().map(mediaStreamTrack => {
20734
+ const isAudio = mediaStreamTrack.kind === 'audio';
20735
+ isAudio ? options.audio : options.video;
20736
+ let trackConstraints;
20737
+ const conOrBool = isAudio ? constraints.audio : constraints.video;
20738
+ if (typeof conOrBool !== 'boolean') {
20739
+ trackConstraints = conOrBool;
20592
20740
  }
20593
- publication = new RemoteTrackPublication(kind, ti, (_a = this.signalClient.connectOptions) === null || _a === void 0 ? void 0 : _a.autoSubscribe, {
20594
- loggerContextCb: () => this.logContext,
20595
- loggerName: (_b = this.loggerOptions) === null || _b === void 0 ? void 0 : _b.loggerName
20741
+ const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints, {
20742
+ loggerName: this.roomOptions.loggerName,
20743
+ loggerContextCb: () => this.logContext
20596
20744
  });
20597
- publication.updateInfo(ti);
20598
- newTracks.set(ti.sid, publication);
20599
- const existingTrackOfSource = Array.from(this.tracks.values()).find(publishedTrack => publishedTrack.source === (publication === null || publication === void 0 ? void 0 : publication.source));
20600
- if (existingTrackOfSource && publication.source !== Track.Source.Unknown) {
20601
- this.log.debug("received a second track publication for ".concat(this.identity, " with the same source: ").concat(publication.source), Object.assign(Object.assign({}, this.logContext), {
20602
- oldTrack: getLogContextFromTrack(existingTrackOfSource),
20603
- newTrack: getLogContextFromTrack(publication)
20604
- }));
20745
+ if (track.kind === Track.Kind.Video) {
20746
+ track.source = Track.Source.Camera;
20747
+ } else if (track.kind === Track.Kind.Audio) {
20748
+ track.source = Track.Source.Microphone;
20605
20749
  }
20606
- this.addTrackPublication(publication);
20607
- } else {
20608
- publication.updateInfo(ti);
20609
- }
20610
- validTracks.set(ti.sid, publication);
20611
- });
20612
- // detect removed tracks
20613
- this.tracks.forEach(publication => {
20614
- if (!validTracks.has(publication.trackSid)) {
20615
- this.log.trace('detected removed track on remote participant, unpublishing', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
20616
- this.unpublishTrack(publication.trackSid, true);
20617
- }
20618
- });
20619
- // always emit events for new publications, Room will not forward them unless it's ready
20620
- newTracks.forEach(publication => {
20621
- this.emit(ParticipantEvent.TrackPublished, publication);
20750
+ track.mediaStream = stream;
20751
+ return track;
20752
+ });
20622
20753
  });
20623
- return true;
20624
- }
20625
- /** @internal */
20626
- unpublishTrack(sid, sendUnpublish) {
20627
- const publication = this.tracks.get(sid);
20628
- if (!publication) {
20629
- return;
20630
- }
20631
- // also send unsubscribe, if track is actively subscribed
20632
- const {
20633
- track
20634
- } = publication;
20635
- if (track) {
20636
- track.stop();
20637
- publication.setTrack(undefined);
20638
- }
20639
- // remove track from maps only after unsubscribed has been fired
20640
- this.tracks.delete(sid);
20641
- // remove from the right type map
20642
- switch (publication.kind) {
20643
- case Track.Kind.Audio:
20644
- this.audioTracks.delete(sid);
20645
- break;
20646
- case Track.Kind.Video:
20647
- this.videoTracks.delete(sid);
20648
- break;
20649
- }
20650
- if (sendUnpublish) {
20651
- this.emit(ParticipantEvent.TrackUnpublished, publication);
20652
- }
20653
20754
  }
20654
20755
  /**
20655
- * @internal
20756
+ * Creates a screen capture tracks with getDisplayMedia().
20757
+ * A LocalVideoTrack is always created and returned.
20758
+ * If { audio: true }, and the browser supports audio capture, a LocalAudioTrack is also created.
20656
20759
  */
20657
- setAudioOutput(output) {
20760
+ createScreenTracks(options) {
20658
20761
  return __awaiter(this, void 0, void 0, function* () {
20659
- this.audioOutput = output;
20660
- const promises = [];
20661
- this.audioTracks.forEach(pub => {
20662
- var _a;
20663
- if (pub.track instanceof RemoteAudioTrack) {
20664
- promises.push(pub.track.setSinkId((_a = output.deviceId) !== null && _a !== void 0 ? _a : 'default'));
20665
- }
20666
- });
20667
- yield Promise.all(promises);
20668
- });
20669
- }
20670
- /** @internal */
20671
- emit(event) {
20672
- for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
20673
- args[_key - 1] = arguments[_key];
20674
- }
20675
- this.log.trace('participant event', Object.assign(Object.assign({}, this.logContext), {
20676
- event,
20677
- args
20678
- }));
20679
- return super.emit(event, ...args);
20680
- }
20681
- }
20682
-
20683
- class LocalParticipant extends Participant {
20684
- /** @internal */
20685
- constructor(sid, identity, engine, options) {
20686
- super(sid, identity, undefined, undefined, {
20687
- loggerName: options.loggerName,
20688
- loggerContextCb: () => this.engine.logContext
20689
- });
20690
- this.pendingPublishing = new Set();
20691
- this.pendingPublishPromises = new Map();
20692
- this.participantTrackPermissions = [];
20693
- this.allParticipantsAllowedToSubscribe = true;
20694
- this.encryptionType = Encryption_Type.NONE;
20695
- this.handleReconnecting = () => {
20696
- if (!this.reconnectFuture) {
20697
- this.reconnectFuture = new Future();
20762
+ if (options === undefined) {
20763
+ options = {};
20698
20764
  }
20699
- };
20700
- this.handleReconnected = () => {
20701
- var _a, _b;
20702
- (_b = (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.resolve) === null || _b === void 0 ? void 0 : _b.call(_a);
20703
- this.reconnectFuture = undefined;
20704
- this.updateTrackSubscriptionPermissions();
20705
- };
20706
- this.handleDisconnected = () => {
20707
- var _a, _b;
20708
- if (this.reconnectFuture) {
20709
- this.reconnectFuture.promise.catch(e => this.log.warn(e.message, this.logContext));
20710
- (_b = (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.reject) === null || _b === void 0 ? void 0 : _b.call(_a, 'Got disconnected during reconnection attempt');
20711
- this.reconnectFuture = undefined;
20765
+ if (navigator.mediaDevices.getDisplayMedia === undefined) {
20766
+ throw new DeviceUnsupportedError('getDisplayMedia not supported');
20712
20767
  }
20713
- };
20714
- this.updateTrackSubscriptionPermissions = () => {
20715
- this.log.debug('updating track subscription permissions', Object.assign(Object.assign({}, this.logContext), {
20716
- allParticipantsAllowed: this.allParticipantsAllowedToSubscribe,
20717
- participantTrackPermissions: this.participantTrackPermissions
20718
- }));
20719
- this.engine.client.sendUpdateSubscriptionPermissions(this.allParticipantsAllowedToSubscribe, this.participantTrackPermissions.map(p => trackPermissionToProto(p)));
20720
- };
20721
- /** @internal */
20722
- this.onTrackUnmuted = track => {
20723
- this.onTrackMuted(track, track.isUpstreamPaused);
20724
- };
20725
- // when the local track changes in mute status, we'll notify server as such
20726
- /** @internal */
20727
- this.onTrackMuted = (track, muted) => {
20728
- if (muted === undefined) {
20729
- muted = true;
20768
+ if (options.resolution === undefined && !isSafari17()) {
20769
+ // we need to constrain the dimensions, otherwise it could lead to low bitrate
20770
+ // due to encoding a huge video. Encoding such large surfaces is really expensive
20771
+ // unfortunately Safari 17 has a but and cannot be constrained by default
20772
+ options.resolution = ScreenSharePresets.h1080fps30.resolution;
20730
20773
  }
20731
- if (!track.sid) {
20732
- this.log.error('could not update mute status for unpublished track', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20733
- return;
20774
+ const constraints = screenCaptureToDisplayMediaStreamOptions(options);
20775
+ const stream = yield navigator.mediaDevices.getDisplayMedia(constraints);
20776
+ const tracks = stream.getVideoTracks();
20777
+ if (tracks.length === 0) {
20778
+ throw new TrackInvalidError('no video track found');
20734
20779
  }
20735
- this.engine.updateMuteStatus(track.sid, muted);
20736
- };
20737
- this.onTrackUpstreamPaused = track => {
20738
- this.log.debug('upstream paused', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20739
- this.onTrackMuted(track, true);
20740
- };
20741
- this.onTrackUpstreamResumed = track => {
20742
- this.log.debug('upstream resumed', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20743
- this.onTrackMuted(track, track.isMuted);
20744
- };
20745
- this.handleSubscribedQualityUpdate = update => __awaiter(this, void 0, void 0, function* () {
20746
- var _a, e_1, _b, _c;
20747
- var _d, _e;
20748
- if (!((_d = this.roomOptions) === null || _d === void 0 ? void 0 : _d.dynacast)) {
20749
- return;
20780
+ const screenVideo = new LocalVideoTrack(tracks[0], undefined, false, {
20781
+ loggerName: this.roomOptions.loggerName,
20782
+ loggerContextCb: () => this.logContext
20783
+ });
20784
+ screenVideo.source = Track.Source.ScreenShare;
20785
+ if (options.contentHint) {
20786
+ screenVideo.mediaStreamTrack.contentHint = options.contentHint;
20750
20787
  }
20751
- const pub = this.videoTracks.get(update.trackSid);
20752
- if (!pub) {
20753
- this.log.warn('received subscribed quality update for unknown track', Object.assign(Object.assign({}, this.logContext), {
20754
- trackSid: update.trackSid
20755
- }));
20756
- return;
20757
- }
20758
- if (update.subscribedCodecs.length > 0) {
20759
- if (!pub.videoTrack) {
20760
- return;
20761
- }
20762
- const newCodecs = yield pub.videoTrack.setPublishingCodecs(update.subscribedCodecs);
20763
- try {
20764
- for (var _f = true, newCodecs_1 = __asyncValues(newCodecs), newCodecs_1_1; newCodecs_1_1 = yield newCodecs_1.next(), _a = newCodecs_1_1.done, !_a; _f = true) {
20765
- _c = newCodecs_1_1.value;
20766
- _f = false;
20767
- const codec = _c;
20768
- if (isBackupCodec(codec)) {
20769
- this.log.debug("publish ".concat(codec, " for ").concat(pub.videoTrack.sid), Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(pub)));
20770
- yield this.publishAdditionalCodecForTrack(pub.videoTrack, codec, pub.options);
20771
- }
20772
- }
20773
- } catch (e_1_1) {
20774
- e_1 = {
20775
- error: e_1_1
20776
- };
20777
- } finally {
20778
- try {
20779
- if (!_f && !_a && (_b = newCodecs_1.return)) yield _b.call(newCodecs_1);
20780
- } finally {
20781
- if (e_1) throw e_1.error;
20782
- }
20783
- }
20784
- } else if (update.subscribedQualities.length > 0) {
20785
- yield (_e = pub.videoTrack) === null || _e === void 0 ? void 0 : _e.setPublishingLayers(update.subscribedQualities);
20786
- }
20787
- });
20788
- this.handleLocalTrackUnpublished = unpublished => {
20789
- const track = this.tracks.get(unpublished.trackSid);
20790
- if (!track) {
20791
- this.log.warn('received unpublished event for unknown track', Object.assign(Object.assign({}, this.logContext), {
20792
- trackSid: unpublished.trackSid
20793
- }));
20794
- return;
20795
- }
20796
- this.unpublishTrack(track.track);
20797
- };
20798
- this.handleTrackEnded = track => __awaiter(this, void 0, void 0, function* () {
20799
- if (track.source === Track.Source.ScreenShare || track.source === Track.Source.ScreenShareAudio) {
20800
- this.log.debug('unpublishing local track due to TrackEnded', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20801
- this.unpublishTrack(track);
20802
- } else if (track.isUserProvided) {
20803
- yield track.mute();
20804
- } else if (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) {
20805
- try {
20806
- if (isWeb()) {
20807
- try {
20808
- const currentPermissions = yield navigator === null || navigator === void 0 ? void 0 : navigator.permissions.query({
20809
- // the permission query for camera and microphone currently not supported in Safari and Firefox
20810
- // @ts-ignore
20811
- name: track.source === Track.Source.Camera ? 'camera' : 'microphone'
20812
- });
20813
- if (currentPermissions && currentPermissions.state === 'denied') {
20814
- this.log.warn("user has revoked access to ".concat(track.source), Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20815
- // detect granted change after permissions were denied to try and resume then
20816
- currentPermissions.onchange = () => {
20817
- if (currentPermissions.state !== 'denied') {
20818
- if (!track.isMuted) {
20819
- track.restartTrack();
20820
- }
20821
- currentPermissions.onchange = null;
20822
- }
20823
- };
20824
- throw new Error('GetUserMedia Permission denied');
20825
- }
20826
- } catch (e) {
20827
- // permissions query fails for firefox, we continue and try to restart the track
20828
- }
20829
- }
20830
- if (!track.isMuted) {
20831
- this.log.debug('track ended, attempting to use a different device', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20832
- yield track.restartTrack();
20833
- }
20834
- } catch (e) {
20835
- this.log.warn("could not restart track, muting instead", Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20836
- yield track.mute();
20837
- }
20788
+ const localTracks = [screenVideo];
20789
+ if (stream.getAudioTracks().length > 0) {
20790
+ this.emit(ParticipantEvent.AudioStreamAcquired);
20791
+ const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0], undefined, false, this.audioContext, {
20792
+ loggerName: this.roomOptions.loggerName,
20793
+ loggerContextCb: () => this.logContext
20794
+ });
20795
+ screenAudio.source = Track.Source.ScreenShareAudio;
20796
+ localTracks.push(screenAudio);
20838
20797
  }
20798
+ return localTracks;
20839
20799
  });
20840
- this.audioTracks = new Map();
20841
- this.videoTracks = new Map();
20842
- this.tracks = new Map();
20843
- this.engine = engine;
20844
- this.roomOptions = options;
20845
- this.setupEngine(engine);
20846
- this.activeDeviceMap = new Map();
20847
- }
20848
- get lastCameraError() {
20849
- return this.cameraError;
20850
- }
20851
- get lastMicrophoneError() {
20852
- return this.microphoneError;
20853
- }
20854
- get isE2EEEnabled() {
20855
- return this.encryptionType !== Encryption_Type.NONE;
20856
- }
20857
- getTrack(source) {
20858
- const track = super.getTrack(source);
20859
- if (track) {
20860
- return track;
20861
- }
20862
- }
20863
- getTrackByName(name) {
20864
- const track = super.getTrackByName(name);
20865
- if (track) {
20866
- return track;
20867
- }
20868
20800
  }
20869
20801
  /**
20870
- * @internal
20802
+ * Publish a new track to the room
20803
+ * @param track
20804
+ * @param options
20871
20805
  */
20872
- setupEngine(engine) {
20873
- this.engine = engine;
20874
- this.engine.on(EngineEvent.RemoteMute, (trackSid, muted) => {
20875
- const pub = this.tracks.get(trackSid);
20876
- if (!pub || !pub.track) {
20877
- return;
20806
+ publishTrack(track, options) {
20807
+ var _a, _b, _c, _d;
20808
+ return __awaiter(this, void 0, void 0, function* () {
20809
+ yield (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.promise;
20810
+ if (track instanceof LocalTrack && this.pendingPublishPromises.has(track)) {
20811
+ yield this.pendingPublishPromises.get(track);
20878
20812
  }
20879
- if (muted) {
20880
- pub.mute();
20813
+ let defaultConstraints;
20814
+ if (track instanceof MediaStreamTrack) {
20815
+ defaultConstraints = track.getConstraints();
20881
20816
  } else {
20882
- pub.unmute();
20817
+ // we want to access constraints directly as `track.mediaStreamTrack`
20818
+ // might be pointing to a non-device track (e.g. processed track) already
20819
+ defaultConstraints = track.constraints;
20820
+ let deviceKind = undefined;
20821
+ switch (track.source) {
20822
+ case Track.Source.Microphone:
20823
+ deviceKind = 'audioinput';
20824
+ break;
20825
+ case Track.Source.Camera:
20826
+ deviceKind = 'videoinput';
20827
+ }
20828
+ if (deviceKind && this.activeDeviceMap.has(deviceKind)) {
20829
+ defaultConstraints = Object.assign(Object.assign({}, defaultConstraints), {
20830
+ deviceId: this.activeDeviceMap.get(deviceKind)
20831
+ });
20832
+ }
20833
+ }
20834
+ // convert raw media track into audio or video track
20835
+ if (track instanceof MediaStreamTrack) {
20836
+ switch (track.kind) {
20837
+ case 'audio':
20838
+ track = new LocalAudioTrack(track, defaultConstraints, true, this.audioContext, {
20839
+ loggerName: this.roomOptions.loggerName,
20840
+ loggerContextCb: () => this.logContext
20841
+ });
20842
+ break;
20843
+ case 'video':
20844
+ track = new LocalVideoTrack(track, defaultConstraints, true, {
20845
+ loggerName: this.roomOptions.loggerName,
20846
+ loggerContextCb: () => this.logContext
20847
+ });
20848
+ break;
20849
+ default:
20850
+ throw new TrackInvalidError("unsupported MediaStreamTrack kind ".concat(track.kind));
20851
+ }
20852
+ } else {
20853
+ track.updateLoggerOptions({
20854
+ loggerName: this.roomOptions.loggerName,
20855
+ loggerContextCb: () => this.logContext
20856
+ });
20857
+ }
20858
+ if (track instanceof LocalAudioTrack) {
20859
+ track.setAudioContext(this.audioContext);
20860
+ }
20861
+ // is it already published? if so skip
20862
+ let existingPublication;
20863
+ this.trackPublications.forEach(publication => {
20864
+ if (!publication.track) {
20865
+ return;
20866
+ }
20867
+ if (publication.track === track) {
20868
+ existingPublication = publication;
20869
+ }
20870
+ });
20871
+ if (existingPublication) {
20872
+ this.log.warn('track has already been published, skipping', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(existingPublication)));
20873
+ return existingPublication;
20874
+ }
20875
+ const isStereoInput = 'channelCount' in track.mediaStreamTrack.getSettings() &&
20876
+ // @ts-ignore `channelCount` on getSettings() is currently only available for Safari, but is generally the best way to determine a stereo track https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings/channelCount
20877
+ track.mediaStreamTrack.getSettings().channelCount === 2 || track.mediaStreamTrack.getConstraints().channelCount === 2;
20878
+ const isStereo = (_b = options === null || options === void 0 ? void 0 : options.forceStereo) !== null && _b !== void 0 ? _b : isStereoInput;
20879
+ // disable dtx for stereo track if not enabled explicitly
20880
+ if (isStereo) {
20881
+ if (!options) {
20882
+ options = {};
20883
+ }
20884
+ if (options.dtx === undefined) {
20885
+ this.log.info("Opus DTX will be disabled for stereo tracks by default. Enable them explicitly to make it work.", Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20886
+ }
20887
+ if (options.red === undefined) {
20888
+ this.log.info("Opus RED will be disabled for stereo tracks by default. Enable them explicitly to make it work.");
20889
+ }
20890
+ (_c = options.dtx) !== null && _c !== void 0 ? _c : options.dtx = false;
20891
+ (_d = options.red) !== null && _d !== void 0 ? _d : options.red = false;
20892
+ }
20893
+ const opts = Object.assign(Object.assign({}, this.roomOptions.publishDefaults), options);
20894
+ // disable simulcast if e2ee is set on safari
20895
+ if (isSafari() && this.roomOptions.e2ee) {
20896
+ this.log.info("End-to-end encryption is set up, simulcast publishing will be disabled on Safari", Object.assign({}, this.logContext));
20897
+ opts.simulcast = false;
20898
+ }
20899
+ if (opts.source) {
20900
+ track.source = opts.source;
20901
+ }
20902
+ const publishPromise = this.publish(track, opts, isStereo);
20903
+ this.pendingPublishPromises.set(track, publishPromise);
20904
+ try {
20905
+ const publication = yield publishPromise;
20906
+ return publication;
20907
+ } catch (e) {
20908
+ throw e;
20909
+ } finally {
20910
+ this.pendingPublishPromises.delete(track);
20883
20911
  }
20884
20912
  });
20885
- this.engine.on(EngineEvent.Connected, this.handleReconnected).on(EngineEvent.SignalRestarted, this.handleReconnected).on(EngineEvent.SignalResumed, this.handleReconnected).on(EngineEvent.Restarting, this.handleReconnecting).on(EngineEvent.Resuming, this.handleReconnecting).on(EngineEvent.LocalTrackUnpublished, this.handleLocalTrackUnpublished).on(EngineEvent.SubscribedQualityUpdate, this.handleSubscribedQualityUpdate).on(EngineEvent.Disconnected, this.handleDisconnected);
20886
- }
20887
- /**
20888
- * Sets and updates the metadata of the local participant.
20889
- * The change does not take immediate effect.
20890
- * If successful, a `ParticipantEvent.MetadataChanged` event will be emitted on the local participant.
20891
- * Note: this requires `canUpdateOwnMetadata` permission.
20892
- * @param metadata
20893
- */
20894
- setMetadata(metadata) {
20895
- var _a;
20896
- this.engine.client.sendUpdateLocalMetadata(metadata, (_a = this.name) !== null && _a !== void 0 ? _a : '');
20897
- }
20898
- /**
20899
- * Sets and updates the name of the local participant.
20900
- * The change does not take immediate effect.
20901
- * If successful, a `ParticipantEvent.ParticipantNameChanged` event will be emitted on the local participant.
20902
- * Note: this requires `canUpdateOwnMetadata` permission.
20903
- * @param metadata
20904
- */
20905
- setName(name) {
20906
- var _a;
20907
- this.engine.client.sendUpdateLocalMetadata((_a = this.metadata) !== null && _a !== void 0 ? _a : '', name);
20908
- }
20909
- /**
20910
- * Enable or disable a participant's camera track.
20911
- *
20912
- * If a track has already published, it'll mute or unmute the track.
20913
- * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20914
- */
20915
- setCameraEnabled(enabled, options, publishOptions) {
20916
- return this.setTrackEnabled(Track.Source.Camera, enabled, options, publishOptions);
20917
- }
20918
- /**
20919
- * Enable or disable a participant's microphone track.
20920
- *
20921
- * If a track has already published, it'll mute or unmute the track.
20922
- * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20923
- */
20924
- setMicrophoneEnabled(enabled, options, publishOptions) {
20925
- return this.setTrackEnabled(Track.Source.Microphone, enabled, options, publishOptions);
20926
- }
20927
- /**
20928
- * Start or stop sharing a participant's screen
20929
- * Resolves with a `LocalTrackPublication` instance if successful and `undefined` otherwise
20930
- */
20931
- setScreenShareEnabled(enabled, options, publishOptions) {
20932
- return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options, publishOptions);
20933
- }
20934
- /** @internal */
20935
- setPermissions(permissions) {
20936
- const prevPermissions = this.permissions;
20937
- const changed = super.setPermissions(permissions);
20938
- if (changed && prevPermissions) {
20939
- this.emit(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions);
20940
- }
20941
- return changed;
20942
20913
  }
20943
- /** @internal */
20944
- setE2EEEnabled(enabled) {
20914
+ publish(track, opts, isStereo) {
20915
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
20945
20916
  return __awaiter(this, void 0, void 0, function* () {
20946
- this.encryptionType = enabled ? Encryption_Type.GCM : Encryption_Type.NONE;
20947
- yield this.republishAllTracks(undefined, false);
20948
- });
20949
- }
20950
- setTrackEnabled(source, enabled, options, publishOptions) {
20951
- var _a, _b;
20952
- return __awaiter(this, void 0, void 0, function* () {
20953
- this.log.debug('setTrackEnabled', Object.assign(Object.assign({}, this.logContext), {
20954
- source,
20955
- enabled
20956
- }));
20957
- let track = this.getTrack(source);
20958
- if (enabled) {
20959
- if (track) {
20960
- yield track.unmute();
20961
- } else {
20962
- let localTracks;
20963
- if (this.pendingPublishing.has(source)) {
20964
- this.log.info('skipping duplicate published source', Object.assign(Object.assign({}, this.logContext), {
20965
- source
20966
- }));
20967
- // no-op it's already been requested
20968
- return;
20969
- }
20970
- this.pendingPublishing.add(source);
20971
- try {
20972
- switch (source) {
20973
- case Track.Source.Camera:
20974
- localTracks = yield this.createTracks({
20975
- video: (_a = options) !== null && _a !== void 0 ? _a : true
20976
- });
20977
- break;
20978
- case Track.Source.Microphone:
20979
- localTracks = yield this.createTracks({
20980
- audio: (_b = options) !== null && _b !== void 0 ? _b : true
20981
- });
20982
- break;
20983
- case Track.Source.ScreenShare:
20984
- localTracks = yield this.createScreenTracks(Object.assign({}, options));
20985
- break;
20986
- default:
20987
- throw new TrackInvalidError(source);
20988
- }
20989
- const publishPromises = [];
20990
- for (const localTrack of localTracks) {
20991
- this.log.info('publishing track', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(localTrack)));
20992
- publishPromises.push(this.publishTrack(localTrack, publishOptions));
20917
+ const existingTrackOfSource = Array.from(this.trackPublications.values()).find(publishedTrack => track instanceof LocalTrack && publishedTrack.source === track.source);
20918
+ if (existingTrackOfSource && track.source !== Track.Source.Unknown) {
20919
+ this.log.info("publishing a second track with the same source: ".concat(track.source), Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
20920
+ }
20921
+ if (opts.stopMicTrackOnMute && track instanceof LocalAudioTrack) {
20922
+ track.stopOnMute = true;
20923
+ }
20924
+ if (track.source === Track.Source.ScreenShare && isFireFox()) {
20925
+ // Firefox does not work well with simulcasted screen share
20926
+ // we frequently get no data on layer 0 when enabled
20927
+ opts.simulcast = false;
20928
+ }
20929
+ // require full AV1/VP9 SVC support prior to using it
20930
+ if (opts.videoCodec === 'av1' && !supportsAV1()) {
20931
+ opts.videoCodec = undefined;
20932
+ }
20933
+ if (opts.videoCodec === 'vp9' && !supportsVP9()) {
20934
+ opts.videoCodec = undefined;
20935
+ }
20936
+ if (opts.videoCodec === undefined) {
20937
+ opts.videoCodec = defaultVideoCodec;
20938
+ }
20939
+ const videoCodec = opts.videoCodec;
20940
+ // handle track actions
20941
+ track.on(TrackEvent.Muted, this.onTrackMuted);
20942
+ track.on(TrackEvent.Unmuted, this.onTrackUnmuted);
20943
+ track.on(TrackEvent.Ended, this.handleTrackEnded);
20944
+ track.on(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
20945
+ track.on(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
20946
+ // create track publication from track
20947
+ const req = new AddTrackRequest({
20948
+ // get local track id for use during publishing
20949
+ cid: track.mediaStreamTrack.id,
20950
+ name: opts.name,
20951
+ type: Track.kindToProto(track.kind),
20952
+ muted: track.isMuted,
20953
+ source: Track.sourceToProto(track.source),
20954
+ disableDtx: !((_a = opts.dtx) !== null && _a !== void 0 ? _a : true),
20955
+ encryption: this.encryptionType,
20956
+ stereo: isStereo,
20957
+ disableRed: this.isE2EEEnabled || !((_b = opts.red) !== null && _b !== void 0 ? _b : true),
20958
+ stream: opts === null || opts === void 0 ? void 0 : opts.stream
20959
+ });
20960
+ // compute encodings and layers for video
20961
+ let encodings;
20962
+ if (track.kind === Track.Kind.Video) {
20963
+ let dims = {
20964
+ width: 0,
20965
+ height: 0
20966
+ };
20967
+ try {
20968
+ dims = yield track.waitForDimensions();
20969
+ } catch (e) {
20970
+ // use defaults, it's quite painful for congestion control without simulcast
20971
+ // so using default dims according to publish settings
20972
+ const defaultRes = (_d = (_c = this.roomOptions.videoCaptureDefaults) === null || _c === void 0 ? void 0 : _c.resolution) !== null && _d !== void 0 ? _d : VideoPresets.h720.resolution;
20973
+ dims = {
20974
+ width: defaultRes.width,
20975
+ height: defaultRes.height
20976
+ };
20977
+ // log failure
20978
+ this.log.error('could not determine track dimensions, using defaults', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
20979
+ dims
20980
+ }));
20981
+ }
20982
+ // width and height should be defined for video
20983
+ req.width = dims.width;
20984
+ req.height = dims.height;
20985
+ // for svc codecs, disable simulcast and use vp8 for backup codec
20986
+ if (track instanceof LocalVideoTrack) {
20987
+ if (isSVCCodec(videoCodec)) {
20988
+ // vp9 svc with screenshare has problem to encode, always use L1T3 here
20989
+ if (track.source === Track.Source.ScreenShare && videoCodec === 'vp9') {
20990
+ opts.scalabilityMode = 'L1T3';
20993
20991
  }
20994
- const publishedTracks = yield Promise.all(publishPromises);
20995
- // for screen share publications including audio, this will only return the screen share publication, not the screen share audio one
20996
- // revisit if we want to return an array of tracks instead for v2
20997
- [track] = publishedTracks;
20998
- } catch (e) {
20999
- localTracks === null || localTracks === void 0 ? void 0 : localTracks.forEach(tr => {
21000
- tr.stop();
21001
- });
21002
- if (e instanceof Error && !(e instanceof TrackInvalidError)) {
21003
- this.emit(ParticipantEvent.MediaDevicesError, e);
20992
+ // set scalabilityMode to 'L3T3_KEY' by default
20993
+ opts.scalabilityMode = (_e = opts.scalabilityMode) !== null && _e !== void 0 ? _e : 'L3T3_KEY';
20994
+ }
20995
+ req.simulcastCodecs = [new SimulcastCodec({
20996
+ codec: videoCodec,
20997
+ cid: track.mediaStreamTrack.id
20998
+ })];
20999
+ // set up backup
21000
+ if (opts.backupCodec === true) {
21001
+ opts.backupCodec = {
21002
+ codec: defaultVideoCodec
21003
+ };
21004
+ }
21005
+ if (opts.backupCodec && videoCodec !== opts.backupCodec.codec &&
21006
+ // TODO remove this once e2ee is supported for backup codecs
21007
+ req.encryption === Encryption_Type.NONE) {
21008
+ // multi-codec simulcast requires dynacast
21009
+ if (!this.roomOptions.dynacast) {
21010
+ this.roomOptions.dynacast = true;
21004
21011
  }
21005
- throw e;
21006
- } finally {
21007
- this.pendingPublishing.delete(source);
21012
+ req.simulcastCodecs.push(new SimulcastCodec({
21013
+ codec: opts.backupCodec.codec,
21014
+ cid: ''
21015
+ }));
21008
21016
  }
21009
21017
  }
21010
- } else if (track && track.track) {
21011
- // screenshare cannot be muted, unpublish instead
21012
- if (source === Track.Source.ScreenShare) {
21013
- track = yield this.unpublishTrack(track.track);
21014
- const screenAudioTrack = this.getTrack(Track.Source.ScreenShareAudio);
21015
- if (screenAudioTrack && screenAudioTrack.track) {
21016
- this.unpublishTrack(screenAudioTrack.track);
21018
+ encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts);
21019
+ req.layers = videoLayersFromEncodings(req.width, req.height, encodings, isSVCCodec(opts.videoCodec));
21020
+ } else if (track.kind === Track.Kind.Audio) {
21021
+ encodings = [{
21022
+ maxBitrate: (_f = opts.audioPreset) === null || _f === void 0 ? void 0 : _f.maxBitrate,
21023
+ priority: (_h = (_g = opts.audioPreset) === null || _g === void 0 ? void 0 : _g.priority) !== null && _h !== void 0 ? _h : 'high',
21024
+ networkPriority: (_k = (_j = opts.audioPreset) === null || _j === void 0 ? void 0 : _j.priority) !== null && _k !== void 0 ? _k : 'high'
21025
+ }];
21026
+ }
21027
+ if (!this.engine || this.engine.isClosed) {
21028
+ throw new UnexpectedConnectionState('cannot publish track when not connected');
21029
+ }
21030
+ const ti = yield this.engine.addTrack(req);
21031
+ // server might not support the codec the client has requested, in that case, fallback
21032
+ // to a supported codec
21033
+ let primaryCodecMime;
21034
+ ti.codecs.forEach(codec => {
21035
+ if (primaryCodecMime === undefined) {
21036
+ primaryCodecMime = codec.mimeType;
21037
+ }
21038
+ });
21039
+ if (primaryCodecMime && track.kind === Track.Kind.Video) {
21040
+ const updatedCodec = mimeTypeToVideoCodecString(primaryCodecMime);
21041
+ if (updatedCodec !== videoCodec) {
21042
+ this.log.debug('falling back to server selected codec', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
21043
+ codec: updatedCodec
21044
+ }));
21045
+ /* @ts-ignore */
21046
+ opts.videoCodec = updatedCodec;
21047
+ // recompute encodings since bitrates/etc could have changed
21048
+ encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts);
21049
+ }
21050
+ }
21051
+ const publication = new LocalTrackPublication(track.kind, ti, track, {
21052
+ loggerName: this.roomOptions.loggerName,
21053
+ loggerContextCb: () => this.logContext
21054
+ });
21055
+ // save options for when it needs to be republished again
21056
+ publication.options = opts;
21057
+ track.sid = ti.sid;
21058
+ if (!this.engine.pcManager) {
21059
+ throw new UnexpectedConnectionState('pcManager is not ready');
21060
+ }
21061
+ this.log.debug("publishing ".concat(track.kind, " with encodings"), Object.assign(Object.assign({}, this.logContext), {
21062
+ encodings,
21063
+ trackInfo: ti
21064
+ }));
21065
+ track.sender = yield this.engine.createSender(track, opts, encodings);
21066
+ if (encodings) {
21067
+ if (isFireFox() && track.kind === Track.Kind.Audio) {
21068
+ /* Refer to RFC https://datatracker.ietf.org/doc/html/rfc7587#section-6.1,
21069
+ livekit-server uses maxaveragebitrate=510000 in the answer sdp to permit client to
21070
+ publish high quality audio track. But firefox always uses this value as the actual
21071
+ bitrates, causing the audio bitrates to rise to 510Kbps in any stereo case unexpectedly.
21072
+ So the client need to modify maxaverragebitrates in answer sdp to user provided value to
21073
+ fix the issue.
21074
+ */
21075
+ let trackTransceiver = undefined;
21076
+ for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
21077
+ if (transceiver.sender === track.sender) {
21078
+ trackTransceiver = transceiver;
21079
+ break;
21080
+ }
21017
21081
  }
21018
- } else {
21019
- yield track.mute();
21082
+ if (trackTransceiver) {
21083
+ this.engine.pcManager.publisher.setTrackCodecBitrate({
21084
+ transceiver: trackTransceiver,
21085
+ codec: 'opus',
21086
+ maxbr: ((_l = encodings[0]) === null || _l === void 0 ? void 0 : _l.maxBitrate) ? encodings[0].maxBitrate / 1000 : 0
21087
+ });
21088
+ }
21089
+ } else if (track.codec && isSVCCodec(track.codec) && ((_m = encodings[0]) === null || _m === void 0 ? void 0 : _m.maxBitrate)) {
21090
+ this.engine.pcManager.publisher.setTrackCodecBitrate({
21091
+ cid: req.cid,
21092
+ codec: track.codec,
21093
+ maxbr: encodings[0].maxBitrate / 1000
21094
+ });
21020
21095
  }
21021
21096
  }
21022
- return track;
21097
+ yield this.engine.negotiate();
21098
+ if (track instanceof LocalVideoTrack) {
21099
+ track.startMonitor(this.engine.client);
21100
+ } else if (track instanceof LocalAudioTrack) {
21101
+ track.startMonitor();
21102
+ }
21103
+ this.addTrackPublication(publication);
21104
+ // send event for publication
21105
+ this.emit(ParticipantEvent.LocalTrackPublished, publication);
21106
+ return publication;
21023
21107
  });
21024
21108
  }
21025
- /**
21026
- * Publish both camera and microphone at the same time. This is useful for
21027
- * displaying a single Permission Dialog box to the end user.
21109
+ get isLocal() {
21110
+ return true;
21111
+ }
21112
+ /** @internal
21113
+ * publish additional codec to existing track
21028
21114
  */
21029
- enableCameraAndMicrophone() {
21115
+ publishAdditionalCodecForTrack(track, videoCodec, options) {
21116
+ var _a;
21030
21117
  return __awaiter(this, void 0, void 0, function* () {
21031
- if (this.pendingPublishing.has(Track.Source.Camera) || this.pendingPublishing.has(Track.Source.Microphone)) {
21032
- // no-op it's already been requested
21118
+ // TODO remove once e2ee is supported for backup tracks
21119
+ if (this.encryptionType !== Encryption_Type.NONE) {
21033
21120
  return;
21034
21121
  }
21035
- this.pendingPublishing.add(Track.Source.Camera);
21036
- this.pendingPublishing.add(Track.Source.Microphone);
21037
- try {
21038
- const tracks = yield this.createTracks({
21039
- audio: true,
21040
- video: true
21041
- });
21042
- yield Promise.all(tracks.map(track => this.publishTrack(track)));
21043
- } finally {
21044
- this.pendingPublishing.delete(Track.Source.Camera);
21045
- this.pendingPublishing.delete(Track.Source.Microphone);
21122
+ // is it not published? if so skip
21123
+ let existingPublication;
21124
+ this.trackPublications.forEach(publication => {
21125
+ if (!publication.track) {
21126
+ return;
21127
+ }
21128
+ if (publication.track === track) {
21129
+ existingPublication = publication;
21130
+ }
21131
+ });
21132
+ if (!existingPublication) {
21133
+ throw new TrackInvalidError('track is not published');
21134
+ }
21135
+ if (!(track instanceof LocalVideoTrack)) {
21136
+ throw new TrackInvalidError('track is not a video track');
21137
+ }
21138
+ const opts = Object.assign(Object.assign({}, (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.publishDefaults), options);
21139
+ const encodings = computeTrackBackupEncodings(track, videoCodec, opts);
21140
+ if (!encodings) {
21141
+ this.log.info("backup codec has been disabled, ignoring request to add additional codec for track", Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21142
+ return;
21143
+ }
21144
+ const simulcastTrack = track.addSimulcastTrack(videoCodec, encodings);
21145
+ if (!simulcastTrack) {
21146
+ return;
21147
+ }
21148
+ const req = new AddTrackRequest({
21149
+ cid: simulcastTrack.mediaStreamTrack.id,
21150
+ type: Track.kindToProto(track.kind),
21151
+ muted: track.isMuted,
21152
+ source: Track.sourceToProto(track.source),
21153
+ sid: track.sid,
21154
+ simulcastCodecs: [{
21155
+ codec: opts.videoCodec,
21156
+ cid: simulcastTrack.mediaStreamTrack.id
21157
+ }]
21158
+ });
21159
+ req.layers = videoLayersFromEncodings(req.width, req.height, encodings);
21160
+ if (!this.engine || this.engine.isClosed) {
21161
+ throw new UnexpectedConnectionState('cannot publish track when not connected');
21046
21162
  }
21163
+ const ti = yield this.engine.addTrack(req);
21164
+ yield this.engine.createSimulcastSender(track, simulcastTrack, opts, encodings);
21165
+ yield this.engine.negotiate();
21166
+ this.log.debug("published ".concat(videoCodec, " for track ").concat(track.sid), Object.assign(Object.assign({}, this.logContext), {
21167
+ encodings,
21168
+ trackInfo: ti
21169
+ }));
21047
21170
  });
21048
21171
  }
21049
- /**
21050
- * Create local camera and/or microphone tracks
21051
- * @param options
21052
- * @returns
21053
- */
21054
- createTracks(options) {
21172
+ unpublishTrack(track, stopOnUnpublish) {
21055
21173
  var _a, _b;
21056
21174
  return __awaiter(this, void 0, void 0, function* () {
21057
- const opts = mergeDefaultOptions(options, (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.audioCaptureDefaults, (_b = this.roomOptions) === null || _b === void 0 ? void 0 : _b.videoCaptureDefaults);
21058
- const constraints = constraintsForOptions(opts);
21059
- let stream;
21060
- try {
21061
- stream = yield navigator.mediaDevices.getUserMedia(constraints);
21062
- } catch (err) {
21063
- if (err instanceof Error) {
21064
- if (constraints.audio) {
21065
- this.microphoneError = err;
21175
+ // look through all published tracks to find the right ones
21176
+ const publication = this.getPublicationForTrack(track);
21177
+ const pubLogContext = publication ? getLogContextFromTrack(publication) : undefined;
21178
+ this.log.debug('unpublishing track', Object.assign(Object.assign({}, this.logContext), pubLogContext));
21179
+ if (!publication || !publication.track) {
21180
+ this.log.warn('track was not unpublished because no publication was found', Object.assign(Object.assign({}, this.logContext), pubLogContext));
21181
+ return undefined;
21182
+ }
21183
+ track = publication.track;
21184
+ track.off(TrackEvent.Muted, this.onTrackMuted);
21185
+ track.off(TrackEvent.Unmuted, this.onTrackUnmuted);
21186
+ track.off(TrackEvent.Ended, this.handleTrackEnded);
21187
+ track.off(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
21188
+ track.off(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
21189
+ if (stopOnUnpublish === undefined) {
21190
+ stopOnUnpublish = (_b = (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.stopLocalTrackOnUnpublish) !== null && _b !== void 0 ? _b : true;
21191
+ }
21192
+ if (stopOnUnpublish) {
21193
+ track.stop();
21194
+ }
21195
+ let negotiationNeeded = false;
21196
+ const trackSender = track.sender;
21197
+ track.sender = undefined;
21198
+ if (this.engine.pcManager && this.engine.pcManager.currentState < PCTransportState.FAILED && trackSender) {
21199
+ try {
21200
+ for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
21201
+ // if sender is not currently sending (after replaceTrack(null))
21202
+ // removeTrack would have no effect.
21203
+ // to ensure we end up successfully removing the track, manually set
21204
+ // the transceiver to inactive
21205
+ if (transceiver.sender === trackSender) {
21206
+ transceiver.direction = 'inactive';
21207
+ negotiationNeeded = true;
21208
+ }
21066
21209
  }
21067
- if (constraints.video) {
21068
- this.cameraError = err;
21210
+ if (this.engine.removeTrack(trackSender)) {
21211
+ negotiationNeeded = true;
21212
+ }
21213
+ if (track instanceof LocalVideoTrack) {
21214
+ for (const [, trackInfo] of track.simulcastCodecs) {
21215
+ if (trackInfo.sender) {
21216
+ if (this.engine.removeTrack(trackInfo.sender)) {
21217
+ negotiationNeeded = true;
21218
+ }
21219
+ trackInfo.sender = undefined;
21220
+ }
21221
+ }
21222
+ track.simulcastCodecs.clear();
21069
21223
  }
21224
+ } catch (e) {
21225
+ this.log.warn('failed to unpublish track', Object.assign(Object.assign(Object.assign({}, this.logContext), pubLogContext), {
21226
+ error: e
21227
+ }));
21070
21228
  }
21071
- throw err;
21072
21229
  }
21073
- if (constraints.audio) {
21074
- this.microphoneError = undefined;
21075
- this.emit(ParticipantEvent.AudioStreamAcquired);
21230
+ // remove from our maps
21231
+ this.trackPublications.delete(publication.trackSid);
21232
+ switch (publication.kind) {
21233
+ case Track.Kind.Audio:
21234
+ this.audioTrackPublications.delete(publication.trackSid);
21235
+ break;
21236
+ case Track.Kind.Video:
21237
+ this.videoTrackPublications.delete(publication.trackSid);
21238
+ break;
21076
21239
  }
21077
- if (constraints.video) {
21078
- this.cameraError = undefined;
21240
+ this.emit(ParticipantEvent.LocalTrackUnpublished, publication);
21241
+ publication.setTrack(undefined);
21242
+ if (negotiationNeeded) {
21243
+ yield this.engine.negotiate();
21079
21244
  }
21080
- return stream.getTracks().map(mediaStreamTrack => {
21081
- const isAudio = mediaStreamTrack.kind === 'audio';
21082
- isAudio ? options.audio : options.video;
21083
- let trackConstraints;
21084
- const conOrBool = isAudio ? constraints.audio : constraints.video;
21085
- if (typeof conOrBool !== 'boolean') {
21086
- trackConstraints = conOrBool;
21087
- }
21088
- const track = mediaTrackToLocalTrack(mediaStreamTrack, trackConstraints, {
21089
- loggerName: this.roomOptions.loggerName,
21090
- loggerContextCb: () => this.logContext
21091
- });
21092
- if (track.kind === Track.Kind.Video) {
21093
- track.source = Track.Source.Camera;
21094
- } else if (track.kind === Track.Kind.Audio) {
21095
- track.source = Track.Source.Microphone;
21245
+ return publication;
21246
+ });
21247
+ }
21248
+ unpublishTracks(tracks) {
21249
+ return __awaiter(this, void 0, void 0, function* () {
21250
+ const results = yield Promise.all(tracks.map(track => this.unpublishTrack(track)));
21251
+ return results.filter(track => track instanceof LocalTrackPublication);
21252
+ });
21253
+ }
21254
+ republishAllTracks(options) {
21255
+ let restartTracks = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
21256
+ return __awaiter(this, void 0, void 0, function* () {
21257
+ const localPubs = [];
21258
+ this.trackPublications.forEach(pub => {
21259
+ if (pub.track) {
21260
+ if (options) {
21261
+ pub.options = Object.assign(Object.assign({}, pub.options), options);
21262
+ }
21263
+ localPubs.push(pub);
21096
21264
  }
21097
- track.mediaStream = stream;
21098
- return track;
21099
21265
  });
21266
+ yield Promise.all(localPubs.map(pub => __awaiter(this, void 0, void 0, function* () {
21267
+ const track = pub.track;
21268
+ yield this.unpublishTrack(track, false);
21269
+ if (restartTracks && !track.isMuted && track.source !== Track.Source.ScreenShare && track.source !== Track.Source.ScreenShareAudio && (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) && !track.isUserProvided) {
21270
+ // generally we need to restart the track before publishing, often a full reconnect
21271
+ // is necessary because computer had gone to sleep.
21272
+ this.log.debug('restarting existing track', Object.assign(Object.assign({}, this.logContext), {
21273
+ track: pub.trackSid
21274
+ }));
21275
+ yield track.restartTrack();
21276
+ }
21277
+ yield this.publishTrack(track, pub.options);
21278
+ })));
21100
21279
  });
21101
21280
  }
21102
21281
  /**
21103
- * Creates a screen capture tracks with getDisplayMedia().
21104
- * A LocalVideoTrack is always created and returned.
21105
- * If { audio: true }, and the browser supports audio capture, a LocalAudioTrack is also created.
21282
+ * Publish a new data payload to the room. Data will be forwarded to each
21283
+ * participant in the room if the destination field in publishOptions is empty
21284
+ *
21285
+ * @param data Uint8Array of the payload. To send string data, use TextEncoder.encode
21286
+ * @param options optionally specify a `reliable`, `topic` and `destination`
21106
21287
  */
21107
- createScreenTracks(options) {
21288
+ publishData(data) {
21289
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
21108
21290
  return __awaiter(this, void 0, void 0, function* () {
21109
- if (options === undefined) {
21110
- options = {};
21111
- }
21112
- if (navigator.mediaDevices.getDisplayMedia === undefined) {
21113
- throw new DeviceUnsupportedError('getDisplayMedia not supported');
21114
- }
21115
- if (options.resolution === undefined && !isSafari17()) {
21116
- // we need to constrain the dimensions, otherwise it could lead to low bitrate
21117
- // due to encoding a huge video. Encoding such large surfaces is really expensive
21118
- // unfortunately Safari 17 has a but and cannot be constrained by default
21119
- options.resolution = ScreenSharePresets.h1080fps30.resolution;
21120
- }
21121
- const constraints = screenCaptureToDisplayMediaStreamOptions(options);
21122
- const stream = yield navigator.mediaDevices.getDisplayMedia(constraints);
21123
- const tracks = stream.getVideoTracks();
21124
- if (tracks.length === 0) {
21125
- throw new TrackInvalidError('no video track found');
21126
- }
21127
- const screenVideo = new LocalVideoTrack(tracks[0], undefined, false, {
21128
- loggerName: this.roomOptions.loggerName,
21129
- loggerContextCb: () => this.logContext
21291
+ const kind = options.reliable ? DataPacket_Kind.RELIABLE : DataPacket_Kind.LOSSY;
21292
+ const destinationIdentities = options.destinationIdentities;
21293
+ const topic = options.topic;
21294
+ const packet = new DataPacket({
21295
+ kind: kind,
21296
+ value: {
21297
+ case: 'user',
21298
+ value: new UserPacket({
21299
+ participantIdentity: this.identity,
21300
+ payload: data,
21301
+ destinationIdentities,
21302
+ topic
21303
+ })
21304
+ }
21130
21305
  });
21131
- screenVideo.source = Track.Source.ScreenShare;
21132
- if (options.contentHint) {
21133
- screenVideo.mediaStreamTrack.contentHint = options.contentHint;
21134
- }
21135
- const localTracks = [screenVideo];
21136
- if (stream.getAudioTracks().length > 0) {
21137
- this.emit(ParticipantEvent.AudioStreamAcquired);
21138
- const screenAudio = new LocalAudioTrack(stream.getAudioTracks()[0], undefined, false, this.audioContext, {
21139
- loggerName: this.roomOptions.loggerName,
21140
- loggerContextCb: () => this.logContext
21141
- });
21142
- screenAudio.source = Track.Source.ScreenShareAudio;
21143
- localTracks.push(screenAudio);
21144
- }
21145
- return localTracks;
21306
+ yield this.engine.sendDataPacket(packet, kind);
21146
21307
  });
21147
21308
  }
21148
21309
  /**
21149
- * Publish a new track to the room
21150
- * @param track
21151
- * @param options
21152
- */
21153
- publishTrack(track, options) {
21154
- var _a, _b, _c, _d;
21155
- return __awaiter(this, void 0, void 0, function* () {
21156
- yield (_a = this.reconnectFuture) === null || _a === void 0 ? void 0 : _a.promise;
21157
- if (track instanceof LocalTrack && this.pendingPublishPromises.has(track)) {
21158
- yield this.pendingPublishPromises.get(track);
21159
- }
21160
- let defaultConstraints;
21161
- if (track instanceof MediaStreamTrack) {
21162
- defaultConstraints = track.getConstraints();
21163
- } else {
21164
- // we want to access constraints directly as `track.mediaStreamTrack`
21165
- // might be pointing to a non-device track (e.g. processed track) already
21166
- defaultConstraints = track.constraints;
21167
- let deviceKind = undefined;
21168
- switch (track.source) {
21169
- case Track.Source.Microphone:
21170
- deviceKind = 'audioinput';
21171
- break;
21172
- case Track.Source.Camera:
21173
- deviceKind = 'videoinput';
21174
- }
21175
- if (deviceKind && this.activeDeviceMap.has(deviceKind)) {
21176
- defaultConstraints = Object.assign(Object.assign({}, defaultConstraints), {
21177
- deviceId: this.activeDeviceMap.get(deviceKind)
21178
- });
21179
- }
21180
- }
21181
- // convert raw media track into audio or video track
21182
- if (track instanceof MediaStreamTrack) {
21183
- switch (track.kind) {
21184
- case 'audio':
21185
- track = new LocalAudioTrack(track, defaultConstraints, true, this.audioContext, {
21186
- loggerName: this.roomOptions.loggerName,
21187
- loggerContextCb: () => this.logContext
21188
- });
21189
- break;
21190
- case 'video':
21191
- track = new LocalVideoTrack(track, defaultConstraints, true, {
21192
- loggerName: this.roomOptions.loggerName,
21193
- loggerContextCb: () => this.logContext
21194
- });
21195
- break;
21196
- default:
21197
- throw new TrackInvalidError("unsupported MediaStreamTrack kind ".concat(track.kind));
21198
- }
21199
- } else {
21200
- track.updateLoggerOptions({
21201
- loggerName: this.roomOptions.loggerName,
21202
- loggerContextCb: () => this.logContext
21203
- });
21204
- }
21205
- if (track instanceof LocalAudioTrack) {
21206
- track.setAudioContext(this.audioContext);
21207
- }
21208
- // is it already published? if so skip
21209
- let existingPublication;
21210
- this.tracks.forEach(publication => {
21211
- if (!publication.track) {
21212
- return;
21213
- }
21214
- if (publication.track === track) {
21215
- existingPublication = publication;
21216
- }
21217
- });
21218
- if (existingPublication) {
21219
- this.log.warn('track has already been published, skipping', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(existingPublication)));
21220
- return existingPublication;
21221
- }
21222
- const isStereoInput = 'channelCount' in track.mediaStreamTrack.getSettings() &&
21223
- // @ts-ignore `channelCount` on getSettings() is currently only available for Safari, but is generally the best way to determine a stereo track https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings/channelCount
21224
- track.mediaStreamTrack.getSettings().channelCount === 2 || track.mediaStreamTrack.getConstraints().channelCount === 2;
21225
- const isStereo = (_b = options === null || options === void 0 ? void 0 : options.forceStereo) !== null && _b !== void 0 ? _b : isStereoInput;
21226
- // disable dtx for stereo track if not enabled explicitly
21227
- if (isStereo) {
21228
- if (!options) {
21229
- options = {};
21230
- }
21231
- if (options.dtx === undefined) {
21232
- this.log.info("Opus DTX will be disabled for stereo tracks by default. Enable them explicitly to make it work.", Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21233
- }
21234
- if (options.red === undefined) {
21235
- this.log.info("Opus RED will be disabled for stereo tracks by default. Enable them explicitly to make it work.");
21236
- }
21237
- (_c = options.dtx) !== null && _c !== void 0 ? _c : options.dtx = false;
21238
- (_d = options.red) !== null && _d !== void 0 ? _d : options.red = false;
21239
- }
21240
- const opts = Object.assign(Object.assign({}, this.roomOptions.publishDefaults), options);
21241
- // disable simulcast if e2ee is set on safari
21242
- if (isSafari() && this.roomOptions.e2ee) {
21243
- this.log.info("End-to-end encryption is set up, simulcast publishing will be disabled on Safari", Object.assign({}, this.logContext));
21244
- opts.simulcast = false;
21245
- }
21246
- if (opts.source) {
21247
- track.source = opts.source;
21248
- }
21249
- const publishPromise = this.publish(track, opts, isStereo);
21250
- this.pendingPublishPromises.set(track, publishPromise);
21251
- try {
21252
- const publication = yield publishPromise;
21253
- return publication;
21254
- } catch (e) {
21255
- throw e;
21256
- } finally {
21257
- this.pendingPublishPromises.delete(track);
21258
- }
21259
- });
21310
+ * Control who can subscribe to LocalParticipant's published tracks.
21311
+ *
21312
+ * By default, all participants can subscribe. This allows fine-grained control over
21313
+ * who is able to subscribe at a participant and track level.
21314
+ *
21315
+ * Note: if access is given at a track-level (i.e. both [allParticipantsAllowed] and
21316
+ * [ParticipantTrackPermission.allTracksAllowed] are false), any newer published tracks
21317
+ * will not grant permissions to any participants and will require a subsequent
21318
+ * permissions update to allow subscription.
21319
+ *
21320
+ * @param allParticipantsAllowed Allows all participants to subscribe all tracks.
21321
+ * Takes precedence over [[participantTrackPermissions]] if set to true.
21322
+ * By default this is set to true.
21323
+ * @param participantTrackPermissions Full list of individual permissions per
21324
+ * participant/track. Any omitted participants will not receive any permissions.
21325
+ */
21326
+ setTrackSubscriptionPermissions(allParticipantsAllowed) {
21327
+ let participantTrackPermissions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
21328
+ this.participantTrackPermissions = participantTrackPermissions;
21329
+ this.allParticipantsAllowedToSubscribe = allParticipantsAllowed;
21330
+ if (!this.engine.client.isDisconnected) {
21331
+ this.updateTrackSubscriptionPermissions();
21332
+ }
21260
21333
  }
21261
- publish(track, opts, isStereo) {
21262
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
21263
- return __awaiter(this, void 0, void 0, function* () {
21264
- const existingTrackOfSource = Array.from(this.tracks.values()).find(publishedTrack => track instanceof LocalTrack && publishedTrack.source === track.source);
21265
- if (existingTrackOfSource && track.source !== Track.Source.Unknown) {
21266
- this.log.info("publishing a second track with the same source: ".concat(track.source), Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21267
- }
21268
- if (opts.stopMicTrackOnMute && track instanceof LocalAudioTrack) {
21269
- track.stopOnMute = true;
21270
- }
21271
- if (track.source === Track.Source.ScreenShare && isFireFox()) {
21272
- // Firefox does not work well with simulcasted screen share
21273
- // we frequently get no data on layer 0 when enabled
21274
- opts.simulcast = false;
21275
- }
21276
- // require full AV1/VP9 SVC support prior to using it
21277
- if (opts.videoCodec === 'av1' && !supportsAV1()) {
21278
- opts.videoCodec = undefined;
21279
- }
21280
- if (opts.videoCodec === 'vp9' && !supportsVP9()) {
21281
- opts.videoCodec = undefined;
21282
- }
21283
- if (opts.videoCodec === undefined) {
21284
- opts.videoCodec = defaultVideoCodec;
21285
- }
21286
- const videoCodec = opts.videoCodec;
21287
- // handle track actions
21288
- track.on(TrackEvent.Muted, this.onTrackMuted);
21289
- track.on(TrackEvent.Unmuted, this.onTrackUnmuted);
21290
- track.on(TrackEvent.Ended, this.handleTrackEnded);
21291
- track.on(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
21292
- track.on(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
21293
- // create track publication from track
21294
- const req = new AddTrackRequest({
21295
- // get local track id for use during publishing
21296
- cid: track.mediaStreamTrack.id,
21297
- name: opts.name,
21298
- type: Track.kindToProto(track.kind),
21299
- muted: track.isMuted,
21300
- source: Track.sourceToProto(track.source),
21301
- disableDtx: !((_a = opts.dtx) !== null && _a !== void 0 ? _a : true),
21302
- encryption: this.encryptionType,
21303
- stereo: isStereo,
21304
- disableRed: this.isE2EEEnabled || !((_b = opts.red) !== null && _b !== void 0 ? _b : true),
21305
- stream: opts === null || opts === void 0 ? void 0 : opts.stream
21306
- });
21307
- // compute encodings and layers for video
21308
- let encodings;
21309
- if (track.kind === Track.Kind.Video) {
21310
- let dims = {
21311
- width: 0,
21312
- height: 0
21313
- };
21314
- try {
21315
- dims = yield track.waitForDimensions();
21316
- } catch (e) {
21317
- // use defaults, it's quite painful for congestion control without simulcast
21318
- // so using default dims according to publish settings
21319
- const defaultRes = (_d = (_c = this.roomOptions.videoCaptureDefaults) === null || _c === void 0 ? void 0 : _c.resolution) !== null && _d !== void 0 ? _d : VideoPresets.h720.resolution;
21320
- dims = {
21321
- width: defaultRes.width,
21322
- height: defaultRes.height
21323
- };
21324
- // log failure
21325
- this.log.error('could not determine track dimensions, using defaults', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
21326
- dims
21327
- }));
21328
- }
21329
- // width and height should be defined for video
21330
- req.width = dims.width;
21331
- req.height = dims.height;
21332
- // for svc codecs, disable simulcast and use vp8 for backup codec
21333
- if (track instanceof LocalVideoTrack) {
21334
- if (isSVCCodec(videoCodec)) {
21335
- // vp9 svc with screenshare has problem to encode, always use L1T3 here
21336
- if (track.source === Track.Source.ScreenShare && videoCodec === 'vp9') {
21337
- opts.scalabilityMode = 'L1T3';
21338
- }
21339
- // set scalabilityMode to 'L3T3_KEY' by default
21340
- opts.scalabilityMode = (_e = opts.scalabilityMode) !== null && _e !== void 0 ? _e : 'L3T3_KEY';
21341
- }
21342
- req.simulcastCodecs = [new SimulcastCodec({
21343
- codec: videoCodec,
21344
- cid: track.mediaStreamTrack.id
21345
- })];
21346
- // set up backup
21347
- if (opts.backupCodec === true) {
21348
- opts.backupCodec = {
21349
- codec: defaultVideoCodec
21350
- };
21351
- }
21352
- if (opts.backupCodec && videoCodec !== opts.backupCodec.codec &&
21353
- // TODO remove this once e2ee is supported for backup codecs
21354
- req.encryption === Encryption_Type.NONE) {
21355
- // multi-codec simulcast requires dynacast
21356
- if (!this.roomOptions.dynacast) {
21357
- this.roomOptions.dynacast = true;
21358
- }
21359
- req.simulcastCodecs.push(new SimulcastCodec({
21360
- codec: opts.backupCodec.codec,
21361
- cid: ''
21362
- }));
21363
- }
21364
- }
21365
- encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts);
21366
- req.layers = videoLayersFromEncodings(req.width, req.height, encodings, isSVCCodec(opts.videoCodec));
21367
- } else if (track.kind === Track.Kind.Audio) {
21368
- encodings = [{
21369
- maxBitrate: (_g = (_f = opts.audioPreset) === null || _f === void 0 ? void 0 : _f.maxBitrate) !== null && _g !== void 0 ? _g : opts.audioBitrate,
21370
- priority: (_j = (_h = opts.audioPreset) === null || _h === void 0 ? void 0 : _h.priority) !== null && _j !== void 0 ? _j : 'high',
21371
- networkPriority: (_l = (_k = opts.audioPreset) === null || _k === void 0 ? void 0 : _k.priority) !== null && _l !== void 0 ? _l : 'high'
21372
- }];
21373
- }
21374
- if (!this.engine || this.engine.isClosed) {
21375
- throw new UnexpectedConnectionState('cannot publish track when not connected');
21376
- }
21377
- const ti = yield this.engine.addTrack(req);
21378
- // server might not support the codec the client has requested, in that case, fallback
21379
- // to a supported codec
21380
- let primaryCodecMime;
21381
- ti.codecs.forEach(codec => {
21382
- if (primaryCodecMime === undefined) {
21383
- primaryCodecMime = codec.mimeType;
21384
- }
21385
- });
21386
- if (primaryCodecMime && track.kind === Track.Kind.Video) {
21387
- const updatedCodec = mimeTypeToVideoCodecString(primaryCodecMime);
21388
- if (updatedCodec !== videoCodec) {
21389
- this.log.debug('falling back to server selected codec', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
21390
- codec: updatedCodec
21334
+ /** @internal */
21335
+ updateInfo(info) {
21336
+ if (info.sid !== this.sid) {
21337
+ // drop updates that specify a wrong sid.
21338
+ // the sid for local participant is only explicitly set on join and full reconnect
21339
+ return false;
21340
+ }
21341
+ if (!super.updateInfo(info)) {
21342
+ return false;
21343
+ }
21344
+ // reconcile track mute status.
21345
+ // if server's track mute status doesn't match actual, we'll have to update
21346
+ // the server's copy
21347
+ info.tracks.forEach(ti => {
21348
+ var _a, _b;
21349
+ const pub = this.trackPublications.get(ti.sid);
21350
+ if (pub) {
21351
+ const mutedOnServer = pub.isMuted || ((_b = (_a = pub.track) === null || _a === void 0 ? void 0 : _a.isUpstreamPaused) !== null && _b !== void 0 ? _b : false);
21352
+ if (mutedOnServer !== ti.muted) {
21353
+ this.log.debug('updating server mute state after reconcile', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(pub)), {
21354
+ mutedOnServer
21391
21355
  }));
21392
- /* @ts-ignore */
21393
- opts.videoCodec = updatedCodec;
21394
- // recompute encodings since bitrates/etc could have changed
21395
- encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts);
21356
+ this.engine.client.sendMuteTrack(ti.sid, mutedOnServer);
21396
21357
  }
21397
21358
  }
21398
- const publication = new LocalTrackPublication(track.kind, ti, track, {
21399
- loggerName: this.roomOptions.loggerName,
21400
- loggerContextCb: () => this.logContext
21401
- });
21402
- // save options for when it needs to be republished again
21403
- publication.options = opts;
21404
- track.sid = ti.sid;
21405
- if (!this.engine.pcManager) {
21406
- throw new UnexpectedConnectionState('pcManager is not ready');
21359
+ });
21360
+ return true;
21361
+ }
21362
+ getPublicationForTrack(track) {
21363
+ let publication;
21364
+ this.trackPublications.forEach(pub => {
21365
+ const localTrack = pub.track;
21366
+ if (!localTrack) {
21367
+ return;
21407
21368
  }
21408
- this.log.debug("publishing ".concat(track.kind, " with encodings"), Object.assign(Object.assign({}, this.logContext), {
21409
- encodings,
21410
- trackInfo: ti
21411
- }));
21412
- track.sender = yield this.engine.createSender(track, opts, encodings);
21413
- if (encodings) {
21414
- if (isFireFox() && track.kind === Track.Kind.Audio) {
21415
- /* Refer to RFC https://datatracker.ietf.org/doc/html/rfc7587#section-6.1,
21416
- livekit-server uses maxaveragebitrate=510000 in the answer sdp to permit client to
21417
- publish high quality audio track. But firefox always uses this value as the actual
21418
- bitrates, causing the audio bitrates to rise to 510Kbps in any stereo case unexpectedly.
21419
- So the client need to modify maxaverragebitrates in answer sdp to user provided value to
21420
- fix the issue.
21421
- */
21422
- let trackTransceiver = undefined;
21423
- for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
21424
- if (transceiver.sender === track.sender) {
21425
- trackTransceiver = transceiver;
21426
- break;
21427
- }
21428
- }
21429
- if (trackTransceiver) {
21430
- this.engine.pcManager.publisher.setTrackCodecBitrate({
21431
- transceiver: trackTransceiver,
21432
- codec: 'opus',
21433
- maxbr: ((_m = encodings[0]) === null || _m === void 0 ? void 0 : _m.maxBitrate) ? encodings[0].maxBitrate / 1000 : 0
21434
- });
21369
+ // this looks overly complicated due to this object tree
21370
+ if (track instanceof MediaStreamTrack) {
21371
+ if (localTrack instanceof LocalAudioTrack || localTrack instanceof LocalVideoTrack) {
21372
+ if (localTrack.mediaStreamTrack === track) {
21373
+ publication = pub;
21435
21374
  }
21436
- } else if (track.codec && isSVCCodec(track.codec) && ((_o = encodings[0]) === null || _o === void 0 ? void 0 : _o.maxBitrate)) {
21437
- this.engine.pcManager.publisher.setTrackCodecBitrate({
21438
- cid: req.cid,
21439
- codec: track.codec,
21440
- maxbr: encodings[0].maxBitrate / 1000
21441
- });
21442
21375
  }
21376
+ } else if (track === localTrack) {
21377
+ publication = pub;
21443
21378
  }
21444
- yield this.engine.negotiate();
21445
- if (track instanceof LocalVideoTrack) {
21446
- track.startMonitor(this.engine.client);
21447
- } else if (track instanceof LocalAudioTrack) {
21448
- track.startMonitor();
21449
- }
21450
- this.addTrackPublication(publication);
21451
- // send event for publication
21452
- this.emit(ParticipantEvent.LocalTrackPublished, publication);
21453
- return publication;
21454
21379
  });
21380
+ return publication;
21381
+ }
21382
+ }
21383
+
21384
+ class RemoteTrackPublication extends TrackPublication {
21385
+ constructor(kind, ti, autoSubscribe, loggerOptions) {
21386
+ super(kind, ti.sid, ti.name, loggerOptions);
21387
+ this.track = undefined;
21388
+ /** @internal */
21389
+ this.allowed = true;
21390
+ this.disabled = false;
21391
+ this.currentVideoQuality = VideoQuality.HIGH;
21392
+ this.handleEnded = track => {
21393
+ this.setTrack(undefined);
21394
+ this.emit(TrackEvent.Ended, track);
21395
+ };
21396
+ this.handleVisibilityChange = visible => {
21397
+ this.log.debug("adaptivestream video visibility ".concat(this.trackSid, ", visible=").concat(visible), this.logContext);
21398
+ this.disabled = !visible;
21399
+ this.emitTrackUpdate();
21400
+ };
21401
+ this.handleVideoDimensionsChange = dimensions => {
21402
+ this.log.debug("adaptivestream video dimensions ".concat(dimensions.width, "x").concat(dimensions.height), this.logContext);
21403
+ this.videoDimensions = dimensions;
21404
+ this.emitTrackUpdate();
21405
+ };
21406
+ this.subscribed = autoSubscribe;
21407
+ this.updateInfo(ti);
21408
+ }
21409
+ /**
21410
+ * Subscribe or unsubscribe to this remote track
21411
+ * @param subscribed true to subscribe to a track, false to unsubscribe
21412
+ */
21413
+ setSubscribed(subscribed) {
21414
+ const prevStatus = this.subscriptionStatus;
21415
+ const prevPermission = this.permissionStatus;
21416
+ this.subscribed = subscribed;
21417
+ // reset allowed status when desired subscription state changes
21418
+ // server will notify client via signal message if it's not allowed
21419
+ if (subscribed) {
21420
+ this.allowed = true;
21421
+ }
21422
+ const sub = new UpdateSubscription({
21423
+ trackSids: [this.trackSid],
21424
+ subscribe: this.subscribed,
21425
+ participantTracks: [new ParticipantTracks({
21426
+ // sending an empty participant id since TrackPublication doesn't keep it
21427
+ // this is filled in by the participant that receives this message
21428
+ participantSid: '',
21429
+ trackSids: [this.trackSid]
21430
+ })]
21431
+ });
21432
+ this.emit(TrackEvent.UpdateSubscription, sub);
21433
+ this.emitSubscriptionUpdateIfChanged(prevStatus);
21434
+ this.emitPermissionUpdateIfChanged(prevPermission);
21435
+ }
21436
+ get subscriptionStatus() {
21437
+ if (this.subscribed === false) {
21438
+ return TrackPublication.SubscriptionStatus.Unsubscribed;
21439
+ }
21440
+ if (!super.isSubscribed) {
21441
+ return TrackPublication.SubscriptionStatus.Desired;
21442
+ }
21443
+ return TrackPublication.SubscriptionStatus.Subscribed;
21444
+ }
21445
+ get permissionStatus() {
21446
+ return this.allowed ? TrackPublication.PermissionStatus.Allowed : TrackPublication.PermissionStatus.NotAllowed;
21447
+ }
21448
+ /**
21449
+ * Returns true if track is subscribed, and ready for playback
21450
+ */
21451
+ get isSubscribed() {
21452
+ if (this.subscribed === false) {
21453
+ return false;
21454
+ }
21455
+ return super.isSubscribed;
21456
+ }
21457
+ // returns client's desire to subscribe to a track, also true if autoSubscribe is enabled
21458
+ get isDesired() {
21459
+ return this.subscribed !== false;
21460
+ }
21461
+ get isEnabled() {
21462
+ return !this.disabled;
21463
+ }
21464
+ /**
21465
+ * disable server from sending down data for this track. this is useful when
21466
+ * the participant is off screen, you may disable streaming down their video
21467
+ * to reduce bandwidth requirements
21468
+ * @param enabled
21469
+ */
21470
+ setEnabled(enabled) {
21471
+ if (!this.isManualOperationAllowed() || this.disabled === !enabled) {
21472
+ return;
21473
+ }
21474
+ this.disabled = !enabled;
21475
+ this.emitTrackUpdate();
21476
+ }
21477
+ /**
21478
+ * for tracks that support simulcasting, adjust subscribed quality
21479
+ *
21480
+ * This indicates the highest quality the client can accept. if network
21481
+ * bandwidth does not allow, server will automatically reduce quality to
21482
+ * optimize for uninterrupted video
21483
+ */
21484
+ setVideoQuality(quality) {
21485
+ if (!this.isManualOperationAllowed() || this.currentVideoQuality === quality) {
21486
+ return;
21487
+ }
21488
+ this.currentVideoQuality = quality;
21489
+ this.videoDimensions = undefined;
21490
+ this.emitTrackUpdate();
21491
+ }
21492
+ setVideoDimensions(dimensions) {
21493
+ var _a, _b;
21494
+ if (!this.isManualOperationAllowed()) {
21495
+ return;
21496
+ }
21497
+ 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) {
21498
+ return;
21499
+ }
21500
+ if (this.track instanceof RemoteVideoTrack) {
21501
+ this.videoDimensions = dimensions;
21502
+ }
21503
+ this.currentVideoQuality = undefined;
21504
+ this.emitTrackUpdate();
21505
+ }
21506
+ setVideoFPS(fps) {
21507
+ if (!this.isManualOperationAllowed()) {
21508
+ return;
21509
+ }
21510
+ if (!(this.track instanceof RemoteVideoTrack)) {
21511
+ return;
21512
+ }
21513
+ if (this.fps === fps) {
21514
+ return;
21515
+ }
21516
+ this.fps = fps;
21517
+ this.emitTrackUpdate();
21518
+ }
21519
+ get videoQuality() {
21520
+ return this.currentVideoQuality;
21521
+ }
21522
+ /** @internal */
21523
+ setTrack(track) {
21524
+ const prevStatus = this.subscriptionStatus;
21525
+ const prevPermission = this.permissionStatus;
21526
+ const prevTrack = this.track;
21527
+ if (prevTrack === track) {
21528
+ return;
21529
+ }
21530
+ if (prevTrack) {
21531
+ // unregister listener
21532
+ prevTrack.off(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
21533
+ prevTrack.off(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
21534
+ prevTrack.off(TrackEvent.Ended, this.handleEnded);
21535
+ prevTrack.detach();
21536
+ prevTrack.stopMonitor();
21537
+ this.emit(TrackEvent.Unsubscribed, prevTrack);
21538
+ }
21539
+ super.setTrack(track);
21540
+ if (track) {
21541
+ track.sid = this.trackSid;
21542
+ track.on(TrackEvent.VideoDimensionsChanged, this.handleVideoDimensionsChange);
21543
+ track.on(TrackEvent.VisibilityChanged, this.handleVisibilityChange);
21544
+ track.on(TrackEvent.Ended, this.handleEnded);
21545
+ this.emit(TrackEvent.Subscribed, track);
21546
+ }
21547
+ this.emitPermissionUpdateIfChanged(prevPermission);
21548
+ this.emitSubscriptionUpdateIfChanged(prevStatus);
21549
+ }
21550
+ /** @internal */
21551
+ setAllowed(allowed) {
21552
+ const prevStatus = this.subscriptionStatus;
21553
+ const prevPermission = this.permissionStatus;
21554
+ this.allowed = allowed;
21555
+ this.emitPermissionUpdateIfChanged(prevPermission);
21556
+ this.emitSubscriptionUpdateIfChanged(prevStatus);
21557
+ }
21558
+ /** @internal */
21559
+ setSubscriptionError(error) {
21560
+ this.emit(TrackEvent.SubscriptionFailed, error);
21561
+ }
21562
+ /** @internal */
21563
+ updateInfo(info) {
21564
+ super.updateInfo(info);
21565
+ const prevMetadataMuted = this.metadataMuted;
21566
+ this.metadataMuted = info.muted;
21567
+ if (this.track) {
21568
+ this.track.setMuted(info.muted);
21569
+ } else if (prevMetadataMuted !== info.muted) {
21570
+ this.emit(info.muted ? TrackEvent.Muted : TrackEvent.Unmuted);
21571
+ }
21455
21572
  }
21456
- get isLocal() {
21573
+ emitSubscriptionUpdateIfChanged(previousStatus) {
21574
+ const currentStatus = this.subscriptionStatus;
21575
+ if (previousStatus === currentStatus) {
21576
+ return;
21577
+ }
21578
+ this.emit(TrackEvent.SubscriptionStatusChanged, currentStatus, previousStatus);
21579
+ }
21580
+ emitPermissionUpdateIfChanged(previousPermissionStatus) {
21581
+ const currentPermissionStatus = this.permissionStatus;
21582
+ if (currentPermissionStatus !== previousPermissionStatus) {
21583
+ this.emit(TrackEvent.SubscriptionPermissionChanged, this.permissionStatus, previousPermissionStatus);
21584
+ }
21585
+ }
21586
+ isManualOperationAllowed() {
21587
+ if (this.kind === Track.Kind.Video && this.isAdaptiveStream) {
21588
+ this.log.warn('adaptive stream is enabled, cannot change video track settings', this.logContext);
21589
+ return false;
21590
+ }
21591
+ if (!this.isDesired) {
21592
+ this.log.warn('cannot update track settings when not subscribed', this.logContext);
21593
+ return false;
21594
+ }
21457
21595
  return true;
21458
21596
  }
21459
- /** @internal
21460
- * publish additional codec to existing track
21461
- */
21462
- publishAdditionalCodecForTrack(track, videoCodec, options) {
21463
- var _a;
21464
- return __awaiter(this, void 0, void 0, function* () {
21465
- // TODO remove once e2ee is supported for backup tracks
21466
- if (this.encryptionType !== Encryption_Type.NONE) {
21467
- return;
21468
- }
21469
- // is it not published? if so skip
21470
- let existingPublication;
21471
- this.tracks.forEach(publication => {
21472
- if (!publication.track) {
21473
- return;
21474
- }
21475
- if (publication.track === track) {
21476
- existingPublication = publication;
21477
- }
21478
- });
21479
- if (!existingPublication) {
21480
- throw new TrackInvalidError('track is not published');
21481
- }
21482
- if (!(track instanceof LocalVideoTrack)) {
21483
- throw new TrackInvalidError('track is not a video track');
21484
- }
21485
- const opts = Object.assign(Object.assign({}, (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.publishDefaults), options);
21486
- const encodings = computeTrackBackupEncodings(track, videoCodec, opts);
21487
- if (!encodings) {
21488
- this.log.info("backup codec has been disabled, ignoring request to add additional codec for track", Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)));
21489
- return;
21490
- }
21491
- const simulcastTrack = track.addSimulcastTrack(videoCodec, encodings);
21492
- const req = new AddTrackRequest({
21493
- cid: simulcastTrack.mediaStreamTrack.id,
21494
- type: Track.kindToProto(track.kind),
21495
- muted: track.isMuted,
21496
- source: Track.sourceToProto(track.source),
21497
- sid: track.sid,
21498
- simulcastCodecs: [{
21499
- codec: opts.videoCodec,
21500
- cid: simulcastTrack.mediaStreamTrack.id
21501
- }]
21502
- });
21503
- req.layers = videoLayersFromEncodings(req.width, req.height, encodings);
21504
- if (!this.engine || this.engine.isClosed) {
21505
- throw new UnexpectedConnectionState('cannot publish track when not connected');
21506
- }
21507
- const ti = yield this.engine.addTrack(req);
21508
- yield this.engine.createSimulcastSender(track, simulcastTrack, opts, encodings);
21509
- yield this.engine.negotiate();
21510
- this.log.debug("published ".concat(videoCodec, " for track ").concat(track.sid), Object.assign(Object.assign({}, this.logContext), {
21511
- encodings,
21512
- trackInfo: ti
21513
- }));
21514
- });
21597
+ get isAdaptiveStream() {
21598
+ return this.track instanceof RemoteVideoTrack && this.track.isAdaptiveStream;
21515
21599
  }
21516
- unpublishTrack(track, stopOnUnpublish) {
21517
- var _a, _b;
21518
- return __awaiter(this, void 0, void 0, function* () {
21519
- // look through all published tracks to find the right ones
21520
- const publication = this.getPublicationForTrack(track);
21521
- const pubLogContext = publication ? getLogContextFromTrack(publication) : undefined;
21522
- this.log.debug('unpublishing track', Object.assign(Object.assign({}, this.logContext), pubLogContext));
21523
- if (!publication || !publication.track) {
21524
- this.log.warn('track was not unpublished because no publication was found', Object.assign(Object.assign({}, this.logContext), pubLogContext));
21525
- return undefined;
21526
- }
21527
- track = publication.track;
21528
- track.off(TrackEvent.Muted, this.onTrackMuted);
21529
- track.off(TrackEvent.Unmuted, this.onTrackUnmuted);
21530
- track.off(TrackEvent.Ended, this.handleTrackEnded);
21531
- track.off(TrackEvent.UpstreamPaused, this.onTrackUpstreamPaused);
21532
- track.off(TrackEvent.UpstreamResumed, this.onTrackUpstreamResumed);
21533
- if (stopOnUnpublish === undefined) {
21534
- stopOnUnpublish = (_b = (_a = this.roomOptions) === null || _a === void 0 ? void 0 : _a.stopLocalTrackOnUnpublish) !== null && _b !== void 0 ? _b : true;
21535
- }
21536
- if (stopOnUnpublish) {
21537
- track.stop();
21538
- }
21539
- let negotiationNeeded = false;
21540
- const trackSender = track.sender;
21541
- track.sender = undefined;
21542
- if (this.engine.pcManager && this.engine.pcManager.currentState < PCTransportState.FAILED && trackSender) {
21543
- try {
21544
- for (const transceiver of this.engine.pcManager.publisher.getTransceivers()) {
21545
- // if sender is not currently sending (after replaceTrack(null))
21546
- // removeTrack would have no effect.
21547
- // to ensure we end up successfully removing the track, manually set
21548
- // the transceiver to inactive
21549
- if (transceiver.sender === trackSender) {
21550
- transceiver.direction = 'inactive';
21551
- negotiationNeeded = true;
21552
- }
21553
- }
21554
- if (this.engine.removeTrack(trackSender)) {
21555
- negotiationNeeded = true;
21556
- }
21557
- if (track instanceof LocalVideoTrack) {
21558
- for (const [, trackInfo] of track.simulcastCodecs) {
21559
- if (trackInfo.sender) {
21560
- if (this.engine.removeTrack(trackInfo.sender)) {
21561
- negotiationNeeded = true;
21562
- }
21563
- trackInfo.sender = undefined;
21564
- }
21565
- }
21566
- track.simulcastCodecs.clear();
21567
- }
21568
- } catch (e) {
21569
- this.log.warn('failed to unpublish track', Object.assign(Object.assign(Object.assign({}, this.logContext), pubLogContext), {
21570
- error: e
21571
- }));
21572
- }
21573
- }
21574
- // remove from our maps
21575
- this.tracks.delete(publication.trackSid);
21576
- switch (publication.kind) {
21577
- case Track.Kind.Audio:
21578
- this.audioTracks.delete(publication.trackSid);
21579
- break;
21580
- case Track.Kind.Video:
21581
- this.videoTracks.delete(publication.trackSid);
21582
- break;
21583
- }
21584
- this.emit(ParticipantEvent.LocalTrackUnpublished, publication);
21585
- publication.setTrack(undefined);
21586
- if (negotiationNeeded) {
21587
- yield this.engine.negotiate();
21588
- }
21589
- return publication;
21600
+ /* @internal */
21601
+ emitTrackUpdate() {
21602
+ const settings = new UpdateTrackSettings({
21603
+ trackSids: [this.trackSid],
21604
+ disabled: this.disabled,
21605
+ fps: this.fps
21590
21606
  });
21607
+ if (this.videoDimensions) {
21608
+ settings.width = Math.ceil(this.videoDimensions.width);
21609
+ settings.height = Math.ceil(this.videoDimensions.height);
21610
+ } else if (this.currentVideoQuality !== undefined) {
21611
+ settings.quality = this.currentVideoQuality;
21612
+ } else {
21613
+ // defaults to high quality
21614
+ settings.quality = VideoQuality.HIGH;
21615
+ }
21616
+ this.emit(TrackEvent.UpdateSettings, settings);
21591
21617
  }
21592
- unpublishTracks(tracks) {
21593
- return __awaiter(this, void 0, void 0, function* () {
21594
- const results = yield Promise.all(tracks.map(track => this.unpublishTrack(track)));
21595
- return results.filter(track => track instanceof LocalTrackPublication);
21596
- });
21618
+ }
21619
+
21620
+ class RemoteParticipant extends Participant {
21621
+ /** @internal */
21622
+ static fromParticipantInfo(signalClient, pi) {
21623
+ return new RemoteParticipant(signalClient, pi.sid, pi.identity, pi.name, pi.metadata);
21597
21624
  }
21598
- republishAllTracks(options) {
21599
- let restartTracks = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
21600
- return __awaiter(this, void 0, void 0, function* () {
21601
- const localPubs = [];
21602
- this.tracks.forEach(pub => {
21603
- if (pub.track) {
21604
- if (options) {
21605
- pub.options = Object.assign(Object.assign({}, pub.options), options);
21606
- }
21607
- localPubs.push(pub);
21608
- }
21625
+ /** @internal */
21626
+ constructor(signalClient, sid, identity, name, metadata, loggerOptions) {
21627
+ super(sid, identity || '', name, metadata, loggerOptions);
21628
+ this.signalClient = signalClient;
21629
+ this.trackPublications = new Map();
21630
+ this.audioTrackPublications = new Map();
21631
+ this.videoTrackPublications = new Map();
21632
+ this.volumeMap = new Map();
21633
+ }
21634
+ addTrackPublication(publication) {
21635
+ super.addTrackPublication(publication);
21636
+ // register action events
21637
+ publication.on(TrackEvent.UpdateSettings, settings => {
21638
+ this.log.debug('send update settings', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
21639
+ this.signalClient.sendUpdateTrackSettings(settings);
21640
+ });
21641
+ publication.on(TrackEvent.UpdateSubscription, sub => {
21642
+ sub.participantTracks.forEach(pt => {
21643
+ pt.participantSid = this.sid;
21609
21644
  });
21610
- yield Promise.all(localPubs.map(pub => __awaiter(this, void 0, void 0, function* () {
21611
- const track = pub.track;
21612
- yield this.unpublishTrack(track, false);
21613
- if (restartTracks && !track.isMuted && track.source !== Track.Source.ScreenShare && track.source !== Track.Source.ScreenShareAudio && (track instanceof LocalAudioTrack || track instanceof LocalVideoTrack) && !track.isUserProvided) {
21614
- // generally we need to restart the track before publishing, often a full reconnect
21615
- // is necessary because computer had gone to sleep.
21616
- this.log.debug('restarting existing track', Object.assign(Object.assign({}, this.logContext), {
21617
- track: pub.trackSid
21618
- }));
21619
- yield track.restartTrack();
21620
- }
21621
- yield this.publishTrack(track, pub.options);
21622
- })));
21645
+ this.signalClient.sendUpdateSubscription(sub);
21646
+ });
21647
+ publication.on(TrackEvent.SubscriptionPermissionChanged, status => {
21648
+ this.emit(ParticipantEvent.TrackSubscriptionPermissionChanged, publication, status);
21649
+ });
21650
+ publication.on(TrackEvent.SubscriptionStatusChanged, status => {
21651
+ this.emit(ParticipantEvent.TrackSubscriptionStatusChanged, publication, status);
21652
+ });
21653
+ publication.on(TrackEvent.Subscribed, track => {
21654
+ this.emit(ParticipantEvent.TrackSubscribed, track, publication);
21655
+ });
21656
+ publication.on(TrackEvent.Unsubscribed, previousTrack => {
21657
+ this.emit(ParticipantEvent.TrackUnsubscribed, previousTrack, publication);
21658
+ });
21659
+ publication.on(TrackEvent.SubscriptionFailed, error => {
21660
+ this.emit(ParticipantEvent.TrackSubscriptionFailed, publication.trackSid, error);
21623
21661
  });
21624
21662
  }
21625
- publishData(data, kind) {
21626
- let publishOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
21627
- return __awaiter(this, void 0, void 0, function* () {
21628
- const destination = Array.isArray(publishOptions) ? publishOptions : publishOptions === null || publishOptions === void 0 ? void 0 : publishOptions.destination;
21629
- const destinationSids = [];
21630
- const topic = !Array.isArray(publishOptions) ? publishOptions.topic : undefined;
21631
- if (destination !== undefined) {
21632
- destination.forEach(val => {
21633
- if (val instanceof RemoteParticipant) {
21634
- destinationSids.push(val.sid);
21635
- } else {
21636
- destinationSids.push(val);
21663
+ getTrackPublication(source) {
21664
+ const track = super.getTrackPublication(source);
21665
+ if (track) {
21666
+ return track;
21667
+ }
21668
+ }
21669
+ getTrackPublicationByName(name) {
21670
+ const track = super.getTrackPublicationByName(name);
21671
+ if (track) {
21672
+ return track;
21673
+ }
21674
+ }
21675
+ /**
21676
+ * sets the volume on the participant's audio track
21677
+ * by default, this affects the microphone publication
21678
+ * a different source can be passed in as a second argument
21679
+ * if no track exists the volume will be applied when the microphone track is added
21680
+ */
21681
+ setVolume(volume) {
21682
+ let source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Track.Source.Microphone;
21683
+ this.volumeMap.set(source, volume);
21684
+ const audioPublication = this.getTrackPublication(source);
21685
+ if (audioPublication && audioPublication.track) {
21686
+ audioPublication.track.setVolume(volume);
21687
+ }
21688
+ }
21689
+ /**
21690
+ * gets the volume on the participant's microphone track
21691
+ */
21692
+ getVolume() {
21693
+ let source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Track.Source.Microphone;
21694
+ const audioPublication = this.getTrackPublication(source);
21695
+ if (audioPublication && audioPublication.track) {
21696
+ return audioPublication.track.getVolume();
21697
+ }
21698
+ return this.volumeMap.get(source);
21699
+ }
21700
+ /** @internal */
21701
+ addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft) {
21702
+ // find the track publication
21703
+ // it's possible for the media track to arrive before participant info
21704
+ let publication = this.getTrackPublicationBySid(sid);
21705
+ // it's also possible that the browser didn't honor our original track id
21706
+ // FireFox would use its own local uuid instead of server track id
21707
+ if (!publication) {
21708
+ if (!sid.startsWith('TR')) {
21709
+ // find the first track that matches type
21710
+ this.trackPublications.forEach(p => {
21711
+ if (!publication && mediaTrack.kind === p.kind.toString()) {
21712
+ publication = p;
21637
21713
  }
21638
21714
  });
21639
21715
  }
21640
- const packet = new DataPacket({
21641
- kind,
21642
- value: {
21643
- case: 'user',
21644
- value: new UserPacket({
21645
- participantSid: this.sid,
21646
- payload: data,
21647
- destinationSids: destinationSids,
21648
- topic
21649
- })
21650
- }
21651
- });
21652
- yield this.engine.sendDataPacket(packet, kind);
21653
- });
21716
+ }
21717
+ // when we couldn't locate the track, it's possible that the metadata hasn't
21718
+ // yet arrived. Wait a bit longer for it to arrive, or fire an error
21719
+ if (!publication) {
21720
+ if (triesLeft === 0) {
21721
+ this.log.error('could not find published track', Object.assign(Object.assign({}, this.logContext), {
21722
+ trackSid: sid
21723
+ }));
21724
+ this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
21725
+ return;
21726
+ }
21727
+ if (triesLeft === undefined) triesLeft = 20;
21728
+ setTimeout(() => {
21729
+ this.addSubscribedMediaTrack(mediaTrack, sid, mediaStream, receiver, adaptiveStreamSettings, triesLeft - 1);
21730
+ }, 150);
21731
+ return;
21732
+ }
21733
+ if (mediaTrack.readyState === 'ended') {
21734
+ this.log.error('unable to subscribe because MediaStreamTrack is ended. Do not call MediaStreamTrack.stop()', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
21735
+ this.emit(ParticipantEvent.TrackSubscriptionFailed, sid);
21736
+ return;
21737
+ }
21738
+ const isVideo = mediaTrack.kind === 'video';
21739
+ let track;
21740
+ if (isVideo) {
21741
+ track = new RemoteVideoTrack(mediaTrack, sid, receiver, adaptiveStreamSettings);
21742
+ } else {
21743
+ track = new RemoteAudioTrack(mediaTrack, sid, receiver, this.audioContext, this.audioOutput);
21744
+ }
21745
+ // set track info
21746
+ track.source = publication.source;
21747
+ // keep publication's muted status
21748
+ track.isMuted = publication.isMuted;
21749
+ track.setMediaStream(mediaStream);
21750
+ track.start();
21751
+ publication.setTrack(track);
21752
+ // set participant volumes on new audio tracks
21753
+ if (this.volumeMap.has(publication.source) && track instanceof RemoteAudioTrack) {
21754
+ track.setVolume(this.volumeMap.get(publication.source));
21755
+ }
21756
+ return publication;
21757
+ }
21758
+ /** @internal */
21759
+ get hasMetadata() {
21760
+ return !!this.participantInfo;
21654
21761
  }
21655
21762
  /**
21656
- * Control who can subscribe to LocalParticipant's published tracks.
21657
- *
21658
- * By default, all participants can subscribe. This allows fine-grained control over
21659
- * who is able to subscribe at a participant and track level.
21660
- *
21661
- * Note: if access is given at a track-level (i.e. both [allParticipantsAllowed] and
21662
- * [ParticipantTrackPermission.allTracksAllowed] are false), any newer published tracks
21663
- * will not grant permissions to any participants and will require a subsequent
21664
- * permissions update to allow subscription.
21665
- *
21666
- * @param allParticipantsAllowed Allows all participants to subscribe all tracks.
21667
- * Takes precedence over [[participantTrackPermissions]] if set to true.
21668
- * By default this is set to true.
21669
- * @param participantTrackPermissions Full list of individual permissions per
21670
- * participant/track. Any omitted participants will not receive any permissions.
21763
+ * @internal
21671
21764
  */
21672
- setTrackSubscriptionPermissions(allParticipantsAllowed) {
21673
- let participantTrackPermissions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
21674
- this.participantTrackPermissions = participantTrackPermissions;
21675
- this.allParticipantsAllowedToSubscribe = allParticipantsAllowed;
21676
- if (!this.engine.client.isDisconnected) {
21677
- this.updateTrackSubscriptionPermissions();
21678
- }
21765
+ getTrackPublicationBySid(sid) {
21766
+ return this.trackPublications.get(sid);
21679
21767
  }
21680
21768
  /** @internal */
21681
21769
  updateInfo(info) {
21682
- if (info.sid !== this.sid) {
21683
- // drop updates that specify a wrong sid.
21684
- // the sid for local participant is only explicitly set on join and full reconnect
21685
- return false;
21686
- }
21687
21770
  if (!super.updateInfo(info)) {
21688
21771
  return false;
21689
21772
  }
21690
- // reconcile track mute status.
21691
- // if server's track mute status doesn't match actual, we'll have to update
21692
- // the server's copy
21773
+ // we are getting a list of all available tracks, reconcile in here
21774
+ // and send out events for changes
21775
+ // reconcile track publications, publish events only if metadata is already there
21776
+ // i.e. changes since the local participant has joined
21777
+ const validTracks = new Map();
21778
+ const newTracks = new Map();
21693
21779
  info.tracks.forEach(ti => {
21694
21780
  var _a, _b;
21695
- const pub = this.tracks.get(ti.sid);
21696
- if (pub) {
21697
- const mutedOnServer = pub.isMuted || ((_b = (_a = pub.track) === null || _a === void 0 ? void 0 : _a.isUpstreamPaused) !== null && _b !== void 0 ? _b : false);
21698
- if (mutedOnServer !== ti.muted) {
21699
- this.log.debug('updating server mute state after reconcile', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(pub)), {
21700
- mutedOnServer
21781
+ let publication = this.getTrackPublicationBySid(ti.sid);
21782
+ if (!publication) {
21783
+ // new publication
21784
+ const kind = Track.kindFromProto(ti.type);
21785
+ if (!kind) {
21786
+ return;
21787
+ }
21788
+ publication = new RemoteTrackPublication(kind, ti, (_a = this.signalClient.connectOptions) === null || _a === void 0 ? void 0 : _a.autoSubscribe, {
21789
+ loggerContextCb: () => this.logContext,
21790
+ loggerName: (_b = this.loggerOptions) === null || _b === void 0 ? void 0 : _b.loggerName
21791
+ });
21792
+ publication.updateInfo(ti);
21793
+ newTracks.set(ti.sid, publication);
21794
+ const existingTrackOfSource = Array.from(this.trackPublications.values()).find(publishedTrack => publishedTrack.source === (publication === null || publication === void 0 ? void 0 : publication.source));
21795
+ if (existingTrackOfSource && publication.source !== Track.Source.Unknown) {
21796
+ 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), {
21797
+ oldTrack: getLogContextFromTrack(existingTrackOfSource),
21798
+ newTrack: getLogContextFromTrack(publication)
21701
21799
  }));
21702
- this.engine.client.sendMuteTrack(ti.sid, mutedOnServer);
21703
21800
  }
21801
+ this.addTrackPublication(publication);
21802
+ } else {
21803
+ publication.updateInfo(ti);
21804
+ }
21805
+ validTracks.set(ti.sid, publication);
21806
+ });
21807
+ // detect removed tracks
21808
+ this.trackPublications.forEach(publication => {
21809
+ if (!validTracks.has(publication.trackSid)) {
21810
+ this.log.trace('detected removed track on remote participant, unpublishing', Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(publication)));
21811
+ this.unpublishTrack(publication.trackSid, true);
21704
21812
  }
21705
21813
  });
21814
+ // always emit events for new publications, Room will not forward them unless it's ready
21815
+ newTracks.forEach(publication => {
21816
+ this.emit(ParticipantEvent.TrackPublished, publication);
21817
+ });
21706
21818
  return true;
21707
21819
  }
21708
- getPublicationForTrack(track) {
21709
- let publication;
21710
- this.tracks.forEach(pub => {
21711
- const localTrack = pub.track;
21712
- if (!localTrack) {
21713
- return;
21714
- }
21715
- // this looks overly complicated due to this object tree
21716
- if (track instanceof MediaStreamTrack) {
21717
- if (localTrack instanceof LocalAudioTrack || localTrack instanceof LocalVideoTrack) {
21718
- if (localTrack.mediaStreamTrack === track) {
21719
- publication = pub;
21720
- }
21820
+ /** @internal */
21821
+ unpublishTrack(sid, sendUnpublish) {
21822
+ const publication = this.trackPublications.get(sid);
21823
+ if (!publication) {
21824
+ return;
21825
+ }
21826
+ // also send unsubscribe, if track is actively subscribed
21827
+ const {
21828
+ track
21829
+ } = publication;
21830
+ if (track) {
21831
+ track.stop();
21832
+ publication.setTrack(undefined);
21833
+ }
21834
+ // remove track from maps only after unsubscribed has been fired
21835
+ this.trackPublications.delete(sid);
21836
+ // remove from the right type map
21837
+ switch (publication.kind) {
21838
+ case Track.Kind.Audio:
21839
+ this.audioTrackPublications.delete(sid);
21840
+ break;
21841
+ case Track.Kind.Video:
21842
+ this.videoTrackPublications.delete(sid);
21843
+ break;
21844
+ }
21845
+ if (sendUnpublish) {
21846
+ this.emit(ParticipantEvent.TrackUnpublished, publication);
21847
+ }
21848
+ }
21849
+ /**
21850
+ * @internal
21851
+ */
21852
+ setAudioOutput(output) {
21853
+ return __awaiter(this, void 0, void 0, function* () {
21854
+ this.audioOutput = output;
21855
+ const promises = [];
21856
+ this.audioTrackPublications.forEach(pub => {
21857
+ var _a;
21858
+ if (pub.track instanceof RemoteAudioTrack) {
21859
+ promises.push(pub.track.setSinkId((_a = output.deviceId) !== null && _a !== void 0 ? _a : 'default'));
21721
21860
  }
21722
- } else if (track === localTrack) {
21723
- publication = pub;
21724
- }
21861
+ });
21862
+ yield Promise.all(promises);
21725
21863
  });
21726
- return publication;
21864
+ }
21865
+ /** @internal */
21866
+ emit(event) {
21867
+ for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
21868
+ args[_key - 1] = arguments[_key];
21869
+ }
21870
+ this.log.trace('participant event', Object.assign(Object.assign({}, this.logContext), {
21871
+ event,
21872
+ args
21873
+ }));
21874
+ return super.emit(event, ...args);
21727
21875
  }
21728
21876
  }
21729
21877
 
@@ -21735,8 +21883,6 @@ var ConnectionState;
21735
21883
  ConnectionState["Reconnecting"] = "reconnecting";
21736
21884
  })(ConnectionState || (ConnectionState = {}));
21737
21885
  const connectionReconcileFrequency = 2 * 1000;
21738
- /** @deprecated RoomState has been renamed to [[ConnectionState]] */
21739
- const RoomState = ConnectionState;
21740
21886
  /**
21741
21887
  * In LiveKit, a room is the logical grouping for a list of participants.
21742
21888
  * Participants in a room can publish tracks, and subscribe to others' tracks.
@@ -21767,6 +21913,7 @@ class Room extends eventsExports.EventEmitter {
21767
21913
  this.isVideoPlaybackBlocked = false;
21768
21914
  this.log = livekitLogger;
21769
21915
  this.bufferedEvents = [];
21916
+ this.isResuming = false;
21770
21917
  this.connect = (url, token, opts) => __awaiter(this, void 0, void 0, function* () {
21771
21918
  var _c;
21772
21919
  // In case a disconnect called happened right before the connect call, make sure the disconnect is completed first by awaiting its lock
@@ -21853,7 +22000,6 @@ class Room extends eventsExports.EventEmitter {
21853
22000
  var _e, _f, _g;
21854
22001
  const joinResponse = yield engine.join(url, token, {
21855
22002
  autoSubscribe: connectOptions.autoSubscribe,
21856
- publishOnly: connectOptions.publishOnly,
21857
22003
  adaptiveStream: typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
21858
22004
  maxRetries: connectOptions.maxRetries,
21859
22005
  e2eeEnabled: !!this.e2eeManager,
@@ -21898,8 +22044,8 @@ class Room extends eventsExports.EventEmitter {
21898
22044
  }
21899
22045
  };
21900
22046
  this.attemptConnection = (url, token, opts, abortController) => __awaiter(this, void 0, void 0, function* () {
21901
- var _h, _j;
21902
- if (this.state === ConnectionState.Reconnecting) {
22047
+ var _h, _j, _k;
22048
+ if (this.state === ConnectionState.Reconnecting || this.isResuming || ((_h = this.engine) === null || _h === void 0 ? void 0 : _h.pendingReconnect)) {
21903
22049
  this.log.info('Reconnection attempt replaced by new connection attempt', this.logContext);
21904
22050
  // make sure we close and recreate the existing engine in order to get rid of any potentially ongoing reconnection attempts
21905
22051
  this.recreateEngine();
@@ -21907,7 +22053,7 @@ class Room extends eventsExports.EventEmitter {
21907
22053
  // create engine if previously disconnected
21908
22054
  this.maybeCreateEngine();
21909
22055
  }
21910
- if ((_h = this.regionUrlProvider) === null || _h === void 0 ? void 0 : _h.isCloud()) {
22056
+ if ((_j = this.regionUrlProvider) === null || _j === void 0 ? void 0 : _j.isCloud()) {
21911
22057
  this.engine.setRegionUrlProvider(this.regionUrlProvider);
21912
22058
  }
21913
22059
  this.acquireAudioContext();
@@ -21960,7 +22106,7 @@ class Room extends eventsExports.EventEmitter {
21960
22106
  }
21961
22107
  if (isWeb()) {
21962
22108
  document.addEventListener('freeze', this.onPageLeave);
21963
- (_j = navigator.mediaDevices) === null || _j === void 0 ? void 0 : _j.addEventListener('devicechange', this.handleDeviceChange);
22109
+ (_k = navigator.mediaDevices) === null || _k === void 0 ? void 0 : _k.addEventListener('devicechange', this.handleDeviceChange);
21964
22110
  }
21965
22111
  this.setAndEmitConnectionState(ConnectionState.Connected);
21966
22112
  this.emit(RoomEvent.Connected);
@@ -21972,7 +22118,7 @@ class Room extends eventsExports.EventEmitter {
21972
22118
  this.disconnect = function () {
21973
22119
  let stopTracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
21974
22120
  return __awaiter(_this, void 0, void 0, function* () {
21975
- var _k, _l, _m, _o;
22121
+ var _l, _m, _o, _p;
21976
22122
  const unlock = yield this.disconnectLock.lock();
21977
22123
  try {
21978
22124
  if (this.state === ConnectionState.Disconnected) {
@@ -21980,16 +22126,16 @@ class Room extends eventsExports.EventEmitter {
21980
22126
  return;
21981
22127
  }
21982
22128
  this.log.info('disconnect from room', Object.assign({}, this.logContext));
21983
- if (this.state === ConnectionState.Connecting || this.state === ConnectionState.Reconnecting) {
22129
+ if (this.state === ConnectionState.Connecting || this.state === ConnectionState.Reconnecting || this.isResuming) {
21984
22130
  // try aborting pending connection attempt
21985
22131
  this.log.warn('abort connection attempt', this.logContext);
21986
- (_k = this.abortController) === null || _k === void 0 ? void 0 : _k.abort();
22132
+ (_l = this.abortController) === null || _l === void 0 ? void 0 : _l.abort();
21987
22133
  // in case the abort controller didn't manage to cancel the connection attempt, reject the connect promise explicitly
21988
- (_m = (_l = this.connectFuture) === null || _l === void 0 ? void 0 : _l.reject) === null || _m === void 0 ? void 0 : _m.call(_l, new ConnectionError('Client initiated disconnect'));
22134
+ (_o = (_m = this.connectFuture) === null || _m === void 0 ? void 0 : _m.reject) === null || _o === void 0 ? void 0 : _o.call(_m, new ConnectionError('Client initiated disconnect'));
21989
22135
  this.connectFuture = undefined;
21990
22136
  }
21991
22137
  // send leave
21992
- if (!((_o = this.engine) === null || _o === void 0 ? void 0 : _o.client.isDisconnected)) {
22138
+ if (!((_p = this.engine) === null || _p === void 0 ? void 0 : _p.client.isDisconnected)) {
21993
22139
  yield this.engine.client.sendLeave();
21994
22140
  }
21995
22141
  // close engine (also closes client)
@@ -22056,8 +22202,8 @@ class Room extends eventsExports.EventEmitter {
22056
22202
  }
22057
22203
  elements.push(dummyAudioEl);
22058
22204
  }
22059
- this.participants.forEach(p => {
22060
- p.audioTracks.forEach(t => {
22205
+ this.remoteParticipants.forEach(p => {
22206
+ p.audioTrackPublications.forEach(t => {
22061
22207
  if (t.track) {
22062
22208
  t.track.attachedElements.forEach(e => {
22063
22209
  elements.push(e);
@@ -22078,8 +22224,8 @@ class Room extends eventsExports.EventEmitter {
22078
22224
  });
22079
22225
  this.startVideo = () => __awaiter(this, void 0, void 0, function* () {
22080
22226
  const elements = [];
22081
- for (const p of this.participants.values()) {
22082
- p.videoTracks.forEach(tr => {
22227
+ for (const p of this.remoteParticipants.values()) {
22228
+ p.videoTrackPublications.forEach(tr => {
22083
22229
  var _a;
22084
22230
  (_a = tr.track) === null || _a === void 0 ? void 0 : _a.attachedElements.forEach(el => {
22085
22231
  if (!elements.includes(el)) {
@@ -22100,9 +22246,11 @@ class Room extends eventsExports.EventEmitter {
22100
22246
  });
22101
22247
  this.handleRestarting = () => {
22102
22248
  this.clearConnectionReconcile();
22249
+ // in case we went from resuming to full-reconnect, make sure to reflect it on the isResuming flag
22250
+ this.isResuming = false;
22103
22251
  // also unwind existing participants & existing subscriptions
22104
- for (const p of this.participants.values()) {
22105
- this.handleParticipantDisconnected(p.sid, p);
22252
+ for (const p of this.remoteParticipants.values()) {
22253
+ this.handleParticipantDisconnected(p.identity, p);
22106
22254
  }
22107
22255
  if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
22108
22256
  this.emit(RoomEvent.Reconnecting);
@@ -22127,7 +22275,7 @@ class Room extends eventsExports.EventEmitter {
22127
22275
  this.log.debug("fully reconnected to server", Object.assign(Object.assign({}, this.logContext), {
22128
22276
  region: joinResponse.serverRegion
22129
22277
  }));
22130
- } catch (_p) {
22278
+ } catch (_q) {
22131
22279
  // reconnection failed, handleDisconnect is being invoked already, just return here
22132
22280
  return;
22133
22281
  }
@@ -22139,28 +22287,23 @@ class Room extends eventsExports.EventEmitter {
22139
22287
  this.handleParticipantUpdates = participantInfos => {
22140
22288
  // handle changes to participant state, and send events
22141
22289
  participantInfos.forEach(info => {
22290
+ var _a;
22142
22291
  if (info.identity === this.localParticipant.identity) {
22143
22292
  this.localParticipant.updateInfo(info);
22144
22293
  return;
22145
22294
  }
22146
- // ensure identity <=> sid mapping
22147
- const sid = this.identityToSid.get(info.identity);
22148
- if (sid && sid !== info.sid) {
22149
- // sid had changed, need to remove previous participant
22150
- this.handleParticipantDisconnected(sid, this.participants.get(sid));
22295
+ // LiveKit server doesn't send identity info prior to version 1.5.2 in disconnect updates
22296
+ // so we try to map an empty identity to an already known sID manually
22297
+ if (info.identity === '') {
22298
+ info.identity = (_a = this.sidToIdentity.get(info.sid)) !== null && _a !== void 0 ? _a : '';
22151
22299
  }
22152
- let remoteParticipant = this.participants.get(info.sid);
22153
- const isNewParticipant = !remoteParticipant;
22300
+ let remoteParticipant = this.remoteParticipants.get(info.identity);
22154
22301
  // when it's disconnected, send updates
22155
22302
  if (info.state === ParticipantInfo_State.DISCONNECTED) {
22156
- this.handleParticipantDisconnected(info.sid, remoteParticipant);
22303
+ this.handleParticipantDisconnected(info.identity, remoteParticipant);
22157
22304
  } else {
22158
22305
  // create participant if doesn't exist
22159
- remoteParticipant = this.getOrCreateParticipant(info.sid, info);
22160
- if (!isNewParticipant) {
22161
- // just update, no events
22162
- remoteParticipant.updateInfo(info);
22163
- }
22306
+ remoteParticipant = this.getOrCreateParticipant(info.identity, info);
22164
22307
  }
22165
22308
  });
22166
22309
  };
@@ -22175,7 +22318,7 @@ class Room extends eventsExports.EventEmitter {
22175
22318
  this.localParticipant.setIsSpeaking(true);
22176
22319
  activeSpeakers.push(this.localParticipant);
22177
22320
  } else {
22178
- const p = this.participants.get(speaker.sid);
22321
+ const p = this.getRemoteParticipantBySid(speaker.sid);
22179
22322
  if (p) {
22180
22323
  p.audioLevel = speaker.level;
22181
22324
  p.setIsSpeaking(true);
@@ -22187,7 +22330,7 @@ class Room extends eventsExports.EventEmitter {
22187
22330
  this.localParticipant.audioLevel = 0;
22188
22331
  this.localParticipant.setIsSpeaking(false);
22189
22332
  }
22190
- this.participants.forEach(p => {
22333
+ this.remoteParticipants.forEach(p => {
22191
22334
  if (!seenSids[p.sid]) {
22192
22335
  p.audioLevel = 0;
22193
22336
  p.setIsSpeaking(false);
@@ -22203,7 +22346,7 @@ class Room extends eventsExports.EventEmitter {
22203
22346
  lastSpeakers.set(p.sid, p);
22204
22347
  });
22205
22348
  speakerUpdates.forEach(speaker => {
22206
- let p = this.participants.get(speaker.sid);
22349
+ let p = this.getRemoteParticipantBySid(speaker.sid);
22207
22350
  if (speaker.sid === this.localParticipant.sid) {
22208
22351
  p = this.localParticipant;
22209
22352
  }
@@ -22225,11 +22368,11 @@ class Room extends eventsExports.EventEmitter {
22225
22368
  };
22226
22369
  this.handleStreamStateUpdate = streamStateUpdate => {
22227
22370
  streamStateUpdate.streamStates.forEach(streamState => {
22228
- const participant = this.participants.get(streamState.participantSid);
22371
+ const participant = this.getRemoteParticipantBySid(streamState.participantSid);
22229
22372
  if (!participant) {
22230
22373
  return;
22231
22374
  }
22232
- const pub = participant.getTrackPublication(streamState.trackSid);
22375
+ const pub = participant.getTrackPublicationBySid(streamState.trackSid);
22233
22376
  if (!pub || !pub.track) {
22234
22377
  return;
22235
22378
  }
@@ -22239,22 +22382,22 @@ class Room extends eventsExports.EventEmitter {
22239
22382
  });
22240
22383
  };
22241
22384
  this.handleSubscriptionPermissionUpdate = update => {
22242
- const participant = this.participants.get(update.participantSid);
22385
+ const participant = this.getRemoteParticipantBySid(update.participantSid);
22243
22386
  if (!participant) {
22244
22387
  return;
22245
22388
  }
22246
- const pub = participant.getTrackPublication(update.trackSid);
22389
+ const pub = participant.getTrackPublicationBySid(update.trackSid);
22247
22390
  if (!pub) {
22248
22391
  return;
22249
22392
  }
22250
22393
  pub.setAllowed(update.allowed);
22251
22394
  };
22252
22395
  this.handleSubscriptionError = update => {
22253
- const participant = Array.from(this.participants.values()).find(p => p.tracks.has(update.trackSid));
22396
+ const participant = Array.from(this.remoteParticipants.values()).find(p => p.trackPublications.has(update.trackSid));
22254
22397
  if (!participant) {
22255
22398
  return;
22256
22399
  }
22257
- const pub = participant.getTrackPublication(update.trackSid);
22400
+ const pub = participant.getTrackPublicationBySid(update.trackSid);
22258
22401
  if (!pub) {
22259
22402
  return;
22260
22403
  }
@@ -22262,7 +22405,7 @@ class Room extends eventsExports.EventEmitter {
22262
22405
  };
22263
22406
  this.handleDataPacket = (userPacket, kind) => {
22264
22407
  // find the participant
22265
- const participant = this.participants.get(userPacket.participantSid);
22408
+ const participant = this.remoteParticipants.get(userPacket.participantIdentity);
22266
22409
  this.emit(RoomEvent.DataReceived, userPacket.payload, participant, kind, userPacket.topic);
22267
22410
  // also emit on the participant
22268
22411
  participant === null || participant === void 0 ? void 0 : participant.emit(ParticipantEvent.DataReceived, userPacket.payload, kind);
@@ -22315,7 +22458,7 @@ class Room extends eventsExports.EventEmitter {
22315
22458
  this.localParticipant.setConnectionQuality(info.quality);
22316
22459
  return;
22317
22460
  }
22318
- const participant = this.participants.get(info.participantSid);
22461
+ const participant = this.getRemoteParticipantBySid(info.participantSid);
22319
22462
  if (participant) {
22320
22463
  participant.setConnectionQuality(info.quality);
22321
22464
  }
@@ -22334,7 +22477,7 @@ class Room extends eventsExports.EventEmitter {
22334
22477
  this.emit(RoomEvent.TrackUnmuted, pub, this.localParticipant);
22335
22478
  };
22336
22479
  this.onLocalTrackPublished = pub => __awaiter(this, void 0, void 0, function* () {
22337
- var _q;
22480
+ var _r;
22338
22481
  this.emit(RoomEvent.LocalTrackPublished, pub, this.localParticipant);
22339
22482
  if (pub.track instanceof LocalAudioTrack) {
22340
22483
  const trackIsSilent = yield pub.track.checkForSilence();
@@ -22342,7 +22485,7 @@ class Room extends eventsExports.EventEmitter {
22342
22485
  this.emit(RoomEvent.LocalAudioSilenceDetected, pub);
22343
22486
  }
22344
22487
  }
22345
- const deviceId = yield (_q = pub.track) === null || _q === void 0 ? void 0 : _q.getDeviceId();
22488
+ const deviceId = yield (_r = pub.track) === null || _r === void 0 ? void 0 : _r.getDeviceId();
22346
22489
  const deviceKind = sourceToKind(pub.source);
22347
22490
  if (deviceKind && deviceId && deviceId !== this.localParticipant.activeDeviceMap.get(deviceKind)) {
22348
22491
  this.localParticipant.activeDeviceMap.set(deviceKind, deviceId);
@@ -22362,8 +22505,8 @@ class Room extends eventsExports.EventEmitter {
22362
22505
  this.emit(RoomEvent.ParticipantPermissionsChanged, prevPermissions, this.localParticipant);
22363
22506
  };
22364
22507
  this.setMaxListeners(100);
22365
- this.participants = new Map();
22366
- this.identityToSid = new Map();
22508
+ this.remoteParticipants = new Map();
22509
+ this.sidToIdentity = new Map();
22367
22510
  this.options = Object.assign(Object.assign({}, roomOptionDefaults), options);
22368
22511
  this.log = getLogger((_a = this.options.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Room);
22369
22512
  this.options.audioCaptureDefaults = Object.assign(Object.assign({}, audioDefaults), options === null || options === void 0 ? void 0 : options.audioCaptureDefaults);
@@ -22415,9 +22558,10 @@ class Room extends eventsExports.EventEmitter {
22415
22558
  }
22416
22559
  }
22417
22560
  get logContext() {
22561
+ var _a;
22418
22562
  return {
22419
22563
  room: this.name,
22420
- roomSid: this.sid,
22564
+ roomSid: (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.sid,
22421
22565
  identity: this.localParticipant.identity
22422
22566
  };
22423
22567
  }
@@ -22428,10 +22572,32 @@ class Room extends eventsExports.EventEmitter {
22428
22572
  var _a, _b;
22429
22573
  return (_b = (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.activeRecording) !== null && _b !== void 0 ? _b : false;
22430
22574
  }
22431
- /** server assigned unique room id */
22432
- get sid() {
22433
- var _a, _b;
22434
- return (_b = (_a = this.roomInfo) === null || _a === void 0 ? void 0 : _a.sid) !== null && _b !== void 0 ? _b : '';
22575
+ /**
22576
+ * server assigned unique room id.
22577
+ * returns once a sid has been issued by the server.
22578
+ */
22579
+ getSid() {
22580
+ return __awaiter(this, void 0, void 0, function* () {
22581
+ if (this.state === ConnectionState.Disconnected) {
22582
+ return '';
22583
+ }
22584
+ if (this.roomInfo && this.roomInfo.sid !== '') {
22585
+ return this.roomInfo.sid;
22586
+ }
22587
+ return new Promise((resolve, reject) => {
22588
+ const handleRoomUpdate = roomInfo => {
22589
+ if (roomInfo.sid !== '') {
22590
+ this.engine.off(EngineEvent.RoomUpdate, handleRoomUpdate);
22591
+ resolve(roomInfo.sid);
22592
+ }
22593
+ };
22594
+ this.engine.on(EngineEvent.RoomUpdate, handleRoomUpdate);
22595
+ this.once(RoomEvent.Disconnected, () => {
22596
+ this.engine.off(EngineEvent.RoomUpdate, handleRoomUpdate);
22597
+ reject('Room disconnected before room server id was available');
22598
+ });
22599
+ });
22600
+ });
22435
22601
  }
22436
22602
  /** user assigned name, derived from JWT token */
22437
22603
  get name() {
@@ -22462,18 +22628,17 @@ class Room extends eventsExports.EventEmitter {
22462
22628
  this.handleDisconnect(this.options.stopLocalTrackOnUnpublish, reason);
22463
22629
  }).on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate).on(EngineEvent.DataPacketReceived, this.handleDataPacket).on(EngineEvent.Resuming, () => {
22464
22630
  this.clearConnectionReconcile();
22465
- if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
22466
- this.emit(RoomEvent.Reconnecting);
22467
- }
22631
+ this.isResuming = true;
22632
+ this.log.info('Resuming signal connection', this.logContext);
22468
22633
  }).on(EngineEvent.Resumed, () => {
22469
- this.setAndEmitConnectionState(ConnectionState.Connected);
22470
- this.emit(RoomEvent.Reconnected);
22471
22634
  this.registerConnectionReconcile();
22635
+ this.isResuming = false;
22636
+ this.log.info('Resumed signal connection', this.logContext);
22472
22637
  this.updateSubscriptions();
22473
22638
  this.emitBufferedEvents();
22474
22639
  }).on(EngineEvent.SignalResumed, () => {
22475
22640
  this.bufferedEvents = [];
22476
- if (this.state === ConnectionState.Reconnecting) {
22641
+ if (this.state === ConnectionState.Reconnecting || this.isResuming) {
22477
22642
  this.sendSyncState();
22478
22643
  }
22479
22644
  }).on(EngineEvent.Restarting, this.handleRestarting).on(EngineEvent.SignalRestarted, this.handleSignalRestarted).on(EngineEvent.DCBufferStatusChanged, (status, kind) => {
@@ -22547,10 +22712,7 @@ class Room extends eventsExports.EventEmitter {
22547
22712
  if (this.localParticipant.identity === identity) {
22548
22713
  return this.localParticipant;
22549
22714
  }
22550
- const sid = this.identityToSid.get(identity);
22551
- if (sid) {
22552
- return this.participants.get(sid);
22553
- }
22715
+ return this.remoteParticipants.get(identity);
22554
22716
  }
22555
22717
  clearConnectionFutures() {
22556
22718
  this.connectFuture = undefined;
@@ -22681,15 +22843,6 @@ class Room extends eventsExports.EventEmitter {
22681
22843
  get canPlaybackVideo() {
22682
22844
  return !this.isVideoPlaybackBlocked;
22683
22845
  }
22684
- /**
22685
- * Returns the active audio output device used in this room.
22686
- * @return the previously successfully set audio output device ID or an empty string if the default device is used.
22687
- * @deprecated use `getActiveDevice('audiooutput')` instead
22688
- */
22689
- getActiveAudioOutputDevice() {
22690
- var _a, _b;
22691
- return (_b = (_a = this.options.audioOutput) === null || _a === void 0 ? void 0 : _a.deviceId) !== null && _b !== void 0 ? _b : '';
22692
- }
22693
22846
  getActiveDevice(kind) {
22694
22847
  return this.localParticipant.activeDeviceMap.get(kind);
22695
22848
  }
@@ -22717,7 +22870,7 @@ class Room extends eventsExports.EventEmitter {
22717
22870
  const prevDeviceId = this.options.audioCaptureDefaults.deviceId;
22718
22871
  this.options.audioCaptureDefaults.deviceId = deviceConstraint;
22719
22872
  deviceHasChanged = prevDeviceId !== deviceConstraint;
22720
- const tracks = Array.from(this.localParticipant.audioTracks.values()).filter(track => track.source === Track.Source.Microphone);
22873
+ const tracks = Array.from(this.localParticipant.audioTrackPublications.values()).filter(track => track.source === Track.Source.Microphone);
22721
22874
  try {
22722
22875
  success = (yield Promise.all(tracks.map(t => {
22723
22876
  var _a;
@@ -22731,7 +22884,7 @@ class Room extends eventsExports.EventEmitter {
22731
22884
  const prevDeviceId = this.options.videoCaptureDefaults.deviceId;
22732
22885
  this.options.videoCaptureDefaults.deviceId = deviceConstraint;
22733
22886
  deviceHasChanged = prevDeviceId !== deviceConstraint;
22734
- const tracks = Array.from(this.localParticipant.videoTracks.values()).filter(track => track.source === Track.Source.Camera);
22887
+ const tracks = Array.from(this.localParticipant.videoTrackPublications.values()).filter(track => track.source === Track.Source.Camera);
22735
22888
  try {
22736
22889
  success = (yield Promise.all(tracks.map(t => {
22737
22890
  var _a;
@@ -22742,7 +22895,7 @@ class Room extends eventsExports.EventEmitter {
22742
22895
  throw e;
22743
22896
  }
22744
22897
  } else if (kind === 'audiooutput') {
22745
- if (!supportsSetSinkId() && !this.options.expWebAudioMix || this.options.expWebAudioMix && this.audioContext && !('setSinkId' in this.audioContext)) {
22898
+ if (!supportsSetSinkId() && !this.options.webAudioMix || this.options.webAudioMix && this.audioContext && !('setSinkId' in this.audioContext)) {
22746
22899
  throw new Error('cannot switch audio output, setSinkId not supported');
22747
22900
  }
22748
22901
  (_a = (_c = this.options).audioOutput) !== null && _a !== void 0 ? _a : _c.audioOutput = {};
@@ -22750,11 +22903,11 @@ class Room extends eventsExports.EventEmitter {
22750
22903
  this.options.audioOutput.deviceId = deviceId;
22751
22904
  deviceHasChanged = prevDeviceId !== deviceConstraint;
22752
22905
  try {
22753
- if (this.options.expWebAudioMix) {
22906
+ if (this.options.webAudioMix) {
22754
22907
  // @ts-expect-error setSinkId is not yet in the typescript type of AudioContext
22755
22908
  (_b = this.audioContext) === null || _b === void 0 ? void 0 : _b.setSinkId(deviceId);
22756
22909
  } else {
22757
- yield Promise.all(Array.from(this.participants.values()).map(p => p.setAudioOutput({
22910
+ yield Promise.all(Array.from(this.remoteParticipants.values()).map(p => p.setAudioOutput({
22758
22911
  deviceId
22759
22912
  })));
22760
22913
  }
@@ -22778,9 +22931,11 @@ class Room extends eventsExports.EventEmitter {
22778
22931
  (_a = this.engine) === null || _a === void 0 ? void 0 : _a.close();
22779
22932
  /* @ts-ignore */
22780
22933
  this.engine = undefined;
22934
+ this.isResuming = false;
22781
22935
  // clear out existing remote participants, since they may have attached
22782
22936
  // the old engine
22783
- this.participants.clear();
22937
+ this.remoteParticipants.clear();
22938
+ this.sidToIdentity.clear();
22784
22939
  this.bufferedEvents = [];
22785
22940
  this.maybeCreateEngine();
22786
22941
  }
@@ -22810,19 +22965,19 @@ class Room extends eventsExports.EventEmitter {
22810
22965
  return;
22811
22966
  }
22812
22967
  const parts = unpackStreamId(stream.id);
22813
- const participantId = parts[0];
22968
+ const participantSid = parts[0];
22814
22969
  let streamId = parts[1];
22815
22970
  let trackId = mediaTrack.id;
22816
22971
  // firefox will get streamId (pID|trackId) instead of (pID|streamId) as it doesn't support sync tracks by stream
22817
22972
  // and generates its own track id instead of infer from sdp track id.
22818
22973
  if (streamId && streamId.startsWith('TR')) trackId = streamId;
22819
- if (participantId === this.localParticipant.sid) {
22974
+ if (participantSid === this.localParticipant.sid) {
22820
22975
  this.log.warn('tried to create RemoteParticipant for local participant', this.logContext);
22821
22976
  return;
22822
22977
  }
22823
- const participant = this.participants.get(participantId);
22978
+ const participant = Array.from(this.remoteParticipants.values()).find(p => p.sid === participantSid);
22824
22979
  if (!participant) {
22825
- this.log.error("Tried to add a track for a participant, that's not present. Sid: ".concat(participantId), this.logContext);
22980
+ this.log.error("Tried to add a track for a participant, that's not present. Sid: ".concat(participantSid), this.logContext);
22826
22981
  return;
22827
22982
  }
22828
22983
  let adaptiveStreamSettings;
@@ -22840,18 +22995,19 @@ class Room extends eventsExports.EventEmitter {
22840
22995
  let reason = arguments.length > 1 ? arguments[1] : undefined;
22841
22996
  var _a;
22842
22997
  this.clearConnectionReconcile();
22998
+ this.isResuming = false;
22843
22999
  this.bufferedEvents = [];
22844
23000
  if (this.state === ConnectionState.Disconnected) {
22845
23001
  return;
22846
23002
  }
22847
23003
  this.regionUrl = undefined;
22848
23004
  try {
22849
- this.participants.forEach(p => {
22850
- p.tracks.forEach(pub => {
23005
+ this.remoteParticipants.forEach(p => {
23006
+ p.trackPublications.forEach(pub => {
22851
23007
  p.unpublishTrack(pub.trackSid);
22852
23008
  });
22853
23009
  });
22854
- this.localParticipant.tracks.forEach(pub => {
23010
+ this.localParticipant.trackPublications.forEach(pub => {
22855
23011
  var _a, _b;
22856
23012
  if (pub.track) {
22857
23013
  this.localParticipant.unpublishTrack(pub.track, shouldStopTracks);
@@ -22862,12 +23018,13 @@ class Room extends eventsExports.EventEmitter {
22862
23018
  }
22863
23019
  });
22864
23020
  this.localParticipant.off(ParticipantEvent.ParticipantMetadataChanged, this.onLocalParticipantMetadataChanged).off(ParticipantEvent.ParticipantNameChanged, this.onLocalParticipantNameChanged).off(ParticipantEvent.TrackMuted, this.onLocalTrackMuted).off(ParticipantEvent.TrackUnmuted, this.onLocalTrackUnmuted).off(ParticipantEvent.LocalTrackPublished, this.onLocalTrackPublished).off(ParticipantEvent.LocalTrackUnpublished, this.onLocalTrackUnpublished).off(ParticipantEvent.ConnectionQualityChanged, this.onLocalConnectionQualityChanged).off(ParticipantEvent.MediaDevicesError, this.onMediaDevicesError).off(ParticipantEvent.AudioStreamAcquired, this.startAudio).off(ParticipantEvent.ParticipantPermissionsChanged, this.onLocalParticipantPermissionsChanged);
22865
- this.localParticipant.tracks.clear();
22866
- this.localParticipant.videoTracks.clear();
22867
- this.localParticipant.audioTracks.clear();
22868
- this.participants.clear();
23021
+ this.localParticipant.trackPublications.clear();
23022
+ this.localParticipant.videoTrackPublications.clear();
23023
+ this.localParticipant.audioTrackPublications.clear();
23024
+ this.remoteParticipants.clear();
23025
+ this.sidToIdentity.clear();
22869
23026
  this.activeSpeakers = [];
22870
- if (this.audioContext && typeof this.options.expWebAudioMix === 'boolean') {
23027
+ if (this.audioContext && typeof this.options.webAudioMix === 'boolean') {
22871
23028
  this.audioContext.close();
22872
23029
  this.audioContext = undefined;
22873
23030
  }
@@ -22882,14 +23039,13 @@ class Room extends eventsExports.EventEmitter {
22882
23039
  this.emit(RoomEvent.Disconnected, reason);
22883
23040
  }
22884
23041
  }
22885
- handleParticipantDisconnected(sid, participant) {
23042
+ handleParticipantDisconnected(identity, participant) {
22886
23043
  // remove and send event
22887
- this.participants.delete(sid);
23044
+ this.remoteParticipants.delete(identity);
22888
23045
  if (!participant) {
22889
23046
  return;
22890
23047
  }
22891
- this.identityToSid.delete(participant.identity);
22892
- participant.tracks.forEach(publication => {
23048
+ participant.trackPublications.forEach(publication => {
22893
23049
  participant.unpublishTrack(publication.trackSid, true);
22894
23050
  });
22895
23051
  this.emit(RoomEvent.ParticipantDisconnected, participant);
@@ -22897,9 +23053,9 @@ class Room extends eventsExports.EventEmitter {
22897
23053
  acquireAudioContext() {
22898
23054
  var _a, _b;
22899
23055
  return __awaiter(this, void 0, void 0, function* () {
22900
- if (typeof this.options.expWebAudioMix !== 'boolean' && this.options.expWebAudioMix.audioContext) {
23056
+ if (typeof this.options.webAudioMix !== 'boolean' && this.options.webAudioMix.audioContext) {
22901
23057
  // override audio context with custom audio context if supplied by user
22902
- this.audioContext = this.options.expWebAudioMix.audioContext;
23058
+ this.audioContext = this.options.webAudioMix.audioContext;
22903
23059
  } else if (!this.audioContext || this.audioContext.state === 'closed') {
22904
23060
  // by using an AudioContext, it reduces lag on audio elements
22905
23061
  // https://stackoverflow.com/questions/9811429/html5-audio-tag-on-safari-has-a-delay/54119854#54119854
@@ -22916,8 +23072,8 @@ class Room extends eventsExports.EventEmitter {
22916
23072
  }));
22917
23073
  }
22918
23074
  }
22919
- if (this.options.expWebAudioMix) {
22920
- this.participants.forEach(participant => participant.setAudioContext(this.audioContext));
23075
+ if (this.options.webAudioMix) {
23076
+ this.remoteParticipants.forEach(participant => participant.setAudioContext(this.audioContext));
22921
23077
  }
22922
23078
  this.localParticipant.setAudioContext(this.audioContext);
22923
23079
  const newContextIsRunning = ((_b = this.audioContext) === null || _b === void 0 ? void 0 : _b.state) === 'running';
@@ -22927,18 +23083,18 @@ class Room extends eventsExports.EventEmitter {
22927
23083
  }
22928
23084
  });
22929
23085
  }
22930
- createParticipant(id, info) {
23086
+ createParticipant(identity, info) {
22931
23087
  var _a;
22932
23088
  let participant;
22933
23089
  if (info) {
22934
23090
  participant = RemoteParticipant.fromParticipantInfo(this.engine.client, info);
22935
23091
  } else {
22936
- participant = new RemoteParticipant(this.engine.client, id, '', undefined, undefined, {
23092
+ participant = new RemoteParticipant(this.engine.client, '', identity, undefined, undefined, {
22937
23093
  loggerContextCb: () => this.logContext,
22938
23094
  loggerName: this.options.loggerName
22939
23095
  });
22940
23096
  }
22941
- if (this.options.expWebAudioMix) {
23097
+ if (this.options.webAudioMix) {
22942
23098
  participant.setAudioContext(this.audioContext);
22943
23099
  }
22944
23100
  if ((_a = this.options.audioOutput) === null || _a === void 0 ? void 0 : _a.deviceId) {
@@ -22946,13 +23102,20 @@ class Room extends eventsExports.EventEmitter {
22946
23102
  }
22947
23103
  return participant;
22948
23104
  }
22949
- getOrCreateParticipant(id, info) {
22950
- if (this.participants.has(id)) {
22951
- return this.participants.get(id);
23105
+ getOrCreateParticipant(identity, info) {
23106
+ if (this.remoteParticipants.has(identity)) {
23107
+ const existingParticipant = this.remoteParticipants.get(identity);
23108
+ if (info) {
23109
+ const wasUpdated = existingParticipant.updateInfo(info);
23110
+ if (wasUpdated) {
23111
+ this.sidToIdentity.set(info.sid, info.identity);
23112
+ }
23113
+ }
23114
+ return existingParticipant;
22952
23115
  }
22953
- const participant = this.createParticipant(id, info);
22954
- this.participants.set(id, participant);
22955
- this.identityToSid.set(info.identity, info.sid);
23116
+ const participant = this.createParticipant(identity, info);
23117
+ this.remoteParticipants.set(identity, participant);
23118
+ this.sidToIdentity.set(info.sid, info.identity);
22956
23119
  // if we have valid info and the participant wasn't in the map before, we can assume the participant is new
22957
23120
  // firing here to make sure that `ParticipantConnected` fires before the initial track events
22958
23121
  this.emitWhenConnected(RoomEvent.ParticipantConnected, participant);
@@ -23003,11 +23166,11 @@ class Room extends eventsExports.EventEmitter {
23003
23166
  return participant;
23004
23167
  }
23005
23168
  sendSyncState() {
23006
- const remoteTracks = Array.from(this.participants.values()).reduce((acc, participant) => {
23007
- acc.push(...participant.getTracks()); // FIXME would be nice to have this return RemoteTrackPublications directly instead of the type cast
23169
+ const remoteTracks = Array.from(this.remoteParticipants.values()).reduce((acc, participant) => {
23170
+ acc.push(...participant.getTrackPublications()); // FIXME would be nice to have this return RemoteTrackPublications directly instead of the type cast
23008
23171
  return acc;
23009
23172
  }, []);
23010
- const localTracks = this.localParticipant.getTracks(); // FIXME would be nice to have this return LocalTrackPublications directly instead of the type cast
23173
+ const localTracks = this.localParticipant.getTrackPublications(); // FIXME would be nice to have this return LocalTrackPublications directly instead of the type cast
23011
23174
  this.engine.sendSyncState(remoteTracks, localTracks);
23012
23175
  }
23013
23176
  /**
@@ -23015,14 +23178,20 @@ class Room extends eventsExports.EventEmitter {
23015
23178
  * subscription settings.
23016
23179
  */
23017
23180
  updateSubscriptions() {
23018
- for (const p of this.participants.values()) {
23019
- for (const pub of p.videoTracks.values()) {
23181
+ for (const p of this.remoteParticipants.values()) {
23182
+ for (const pub of p.videoTrackPublications.values()) {
23020
23183
  if (pub.isSubscribed && pub instanceof RemoteTrackPublication) {
23021
23184
  pub.emitTrackUpdate();
23022
23185
  }
23023
23186
  }
23024
23187
  }
23025
23188
  }
23189
+ getRemoteParticipantBySid(sid) {
23190
+ const identity = this.sidToIdentity.get(sid);
23191
+ if (identity) {
23192
+ return this.remoteParticipants.get(identity);
23193
+ }
23194
+ }
23026
23195
  registerConnectionReconcile() {
23027
23196
  this.clearConnectionReconcile();
23028
23197
  let consecutiveFailures = 0;
@@ -23076,11 +23245,11 @@ class Room extends eventsExports.EventEmitter {
23076
23245
  for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
23077
23246
  args[_key - 1] = arguments[_key];
23078
23247
  }
23079
- if (this.state === ConnectionState.Connected) {
23080
- return this.emit(event, ...args);
23081
- } else if (this.state === ConnectionState.Reconnecting) {
23248
+ if (this.state === ConnectionState.Reconnecting || this.isResuming || !this.engine || this.engine.pendingReconnect) {
23082
23249
  // in case the room is reconnecting, buffer the events by firing them later after emitting RoomEvent.Reconnected
23083
23250
  this.bufferedEvents.push([event, args]);
23251
+ } else if (this.state === ConnectionState.Connected) {
23252
+ return this.emit(event, ...args);
23084
23253
  }
23085
23254
  return false;
23086
23255
  }
@@ -23859,5 +24028,5 @@ function isFacingModeValue(item) {
23859
24028
  return item === undefined || allowedValues.includes(item);
23860
24029
  }
23861
24030
 
23862
- export { AudioPresets, BaseKeyProvider, ConnectionCheck, ConnectionError, ConnectionQuality, ConnectionState, CriticalTimers, CryptorEvent, DataPacket_Kind, DefaultReconnectPolicy, DeviceUnsupportedError, DisconnectReason, EncryptionEvent, EngineEvent, ExternalE2EEKeyProvider, KeyHandlerEvent, KeyProviderEvent, LivekitError, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, MediaDeviceFailure, NegotiationError, Participant, ParticipantEvent, PublishDataError, RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, RoomEvent, RoomState, ScreenSharePresets, Track, TrackEvent, TrackInvalidError, TrackPublication, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, attachToElement, createAudioAnalyser, createE2EEKey, createKeyMaterialFromBuffer, createKeyMaterialFromString, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, deriveKeys, detachTrack, facingModeFromDeviceLabel, facingModeFromLocalTrack, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, getLogger, importKey, isBackupCodec, isBrowserSupported, isE2EESupported, isInsertableStreamSupported, isScriptTransformSupported, isVideoFrame, needsRbspUnescaping, parseRbsp, protocolVersion, ratchet, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, version, videoCodecs, writeRbsp };
24031
+ export { AudioPresets, BaseKeyProvider, ConnectionCheck, ConnectionError, ConnectionQuality, ConnectionState, CriticalTimers, CryptorEvent, DataPacket_Kind, DefaultReconnectPolicy, DeviceUnsupportedError, DisconnectReason, EncryptionEvent, EngineEvent, ExternalE2EEKeyProvider, KeyHandlerEvent, KeyProviderEvent, LivekitError, LocalAudioTrack, LocalParticipant, LocalTrack, LocalTrackPublication, LocalVideoTrack, LogLevel, MediaDeviceFailure, NegotiationError, Participant, ParticipantEvent, PublishDataError, RemoteAudioTrack, RemoteParticipant, RemoteTrack, RemoteTrackPublication, RemoteVideoTrack, Room, RoomEvent, ScreenSharePresets, Track, TrackEvent, TrackInvalidError, TrackPublication, UnexpectedConnectionState, UnsupportedServer, VideoPreset, VideoPresets, VideoPresets43, VideoQuality, attachToElement, createAudioAnalyser, createE2EEKey, createKeyMaterialFromBuffer, createKeyMaterialFromString, createLocalAudioTrack, createLocalScreenTracks, createLocalTracks, createLocalVideoTrack, deriveKeys, detachTrack, facingModeFromDeviceLabel, facingModeFromLocalTrack, getEmptyAudioStreamTrack, getEmptyVideoStreamTrack, getLogger, importKey, isBackupCodec, isBrowserSupported, isE2EESupported, isInsertableStreamSupported, isScriptTransformSupported, isVideoFrame, needsRbspUnescaping, parseRbsp, protocolVersion, ratchet, setLogExtension, setLogLevel, supportsAV1, supportsAdaptiveStream, supportsDynacast, supportsVP9, version, videoCodecs, writeRbsp };
23863
24032
  //# sourceMappingURL=livekit-client.esm.mjs.map