livekit-client 1.15.12 → 2.0.1

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