livekit-client 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/livekit-client.esm.mjs +2212 -2100
  2. package/dist/livekit-client.esm.mjs.map +1 -1
  3. package/dist/livekit-client.umd.js +1 -1
  4. package/dist/livekit-client.umd.js.map +1 -1
  5. package/dist/src/index.d.ts +1 -1
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/room/PCTransport.d.ts.map +1 -1
  8. package/dist/src/room/RTCEngine.d.ts +4 -4
  9. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  10. package/dist/src/room/Room.d.ts +5 -2
  11. package/dist/src/room/Room.d.ts.map +1 -1
  12. package/dist/src/room/events.d.ts +20 -1
  13. package/dist/src/room/events.d.ts.map +1 -1
  14. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  15. package/dist/src/room/participant/Participant.d.ts +2 -1
  16. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  17. package/dist/src/room/track/RemoteTrack.d.ts +1 -0
  18. package/dist/src/room/track/RemoteTrack.d.ts.map +1 -1
  19. package/dist/src/room/track/Track.d.ts +4 -0
  20. package/dist/src/room/track/Track.d.ts.map +1 -1
  21. package/dist/src/room/track/TrackPublication.d.ts +3 -1
  22. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  23. package/dist/src/room/types.d.ts +8 -0
  24. package/dist/src/room/types.d.ts.map +1 -1
  25. package/dist/src/room/utils.d.ts +4 -2
  26. package/dist/src/room/utils.d.ts.map +1 -1
  27. package/dist/src/utils/browserParser.d.ts +1 -0
  28. package/dist/src/utils/browserParser.d.ts.map +1 -1
  29. package/dist/ts4.2/src/index.d.ts +1 -1
  30. package/dist/ts4.2/src/room/RTCEngine.d.ts +4 -4
  31. package/dist/ts4.2/src/room/Room.d.ts +5 -2
  32. package/dist/ts4.2/src/room/events.d.ts +20 -1
  33. package/dist/ts4.2/src/room/participant/Participant.d.ts +2 -1
  34. package/dist/ts4.2/src/room/track/RemoteTrack.d.ts +1 -0
  35. package/dist/ts4.2/src/room/track/Track.d.ts +4 -0
  36. package/dist/ts4.2/src/room/track/TrackPublication.d.ts +3 -1
  37. package/dist/ts4.2/src/room/types.d.ts +8 -0
  38. package/dist/ts4.2/src/room/utils.d.ts +4 -2
  39. package/dist/ts4.2/src/utils/browserParser.d.ts +1 -0
  40. package/package.json +8 -8
  41. package/src/index.ts +1 -1
  42. package/src/room/PCTransport.ts +0 -2
  43. package/src/room/RTCEngine.ts +11 -61
  44. package/src/room/Room.ts +27 -1
  45. package/src/room/events.ts +23 -0
  46. package/src/room/participant/LocalParticipant.ts +3 -5
  47. package/src/room/participant/Participant.ts +5 -1
  48. package/src/room/track/RemoteTrack.ts +13 -0
  49. package/src/room/track/Track.ts +9 -0
  50. package/src/room/track/TrackPublication.ts +3 -1
  51. package/src/room/types.ts +9 -0
  52. package/src/room/utils.ts +46 -29
  53. package/src/utils/browserParser.test.ts +4 -0
  54. package/src/utils/browserParser.ts +11 -1
@@ -175,6 +175,7 @@ function normalizeEnumValue(value) {
175
175
  class Message {
176
176
  /**
177
177
  * Compare with a message of the same type.
178
+ * Note that this function disregards extensions and unknown fields.
178
179
  */
179
180
  equals(other) {
180
181
  return this.getType().runtime.util.equals(this.getType(), this, other);
@@ -2185,7 +2186,7 @@ function readScalar$1(type, json, longType, nullAsZeroValue) {
2185
2186
  if (json.trim().length === json.length) int32 = Number(json);
2186
2187
  }
2187
2188
  if (int32 === undefined) break;
2188
- if (type == ScalarType.UINT32) assertUInt32(int32);else assertInt32(int32);
2189
+ if (type == ScalarType.UINT32 || type == ScalarType.FIXED32) assertUInt32(int32);else assertInt32(int32);
2189
2190
  return int32;
2190
2191
  // int64, fixed64, uint64: JSON value will be a decimal string. Either numbers or strings are accepted.
2191
2192
  case ScalarType.INT64:
@@ -2997,6 +2998,9 @@ function makeUtilCommon() {
2997
2998
  }
2998
2999
  any[member.localName] = copy;
2999
3000
  }
3001
+ for (const uf of type.runtime.bin.listUnknownFields(message)) {
3002
+ type.runtime.bin.onUnknownField(any, uf.no, uf.wireType, uf.data);
3003
+ }
3000
3004
  return target;
3001
3005
  }
3002
3006
  };
@@ -3317,10 +3321,8 @@ function normalizeFieldInfos(fieldInfos, packedByDefault) {
3317
3321
  f.req = (_e = field.req) !== null && _e !== void 0 ? _e : false;
3318
3322
  f.opt = (_f = field.opt) !== null && _f !== void 0 ? _f : false;
3319
3323
  if (field.packed === undefined) {
3320
- if (packedByDefault) {
3324
+ {
3321
3325
  f.packed = field.kind == "enum" || field.kind == "scalar" && field.T != ScalarType.BYTES && field.T != ScalarType.STRING;
3322
- } else {
3323
- f.packed = false;
3324
3326
  }
3325
3327
  }
3326
3328
  // We do not surface options at this time
@@ -3355,7 +3357,7 @@ function normalizeFieldInfos(fieldInfos, packedByDefault) {
3355
3357
  * Provides functionality for messages defined with the proto3 syntax.
3356
3358
  */
3357
3359
  const proto3 = makeProtoRuntime("proto3", fields => {
3358
- return new InternalFieldList(fields, source => normalizeFieldInfos(source, true));
3360
+ return new InternalFieldList(fields, source => normalizeFieldInfos(source));
3359
3361
  },
3360
3362
  // TODO merge with proto2 and initExtensionField, also see initPartial, equals, clone
3361
3363
  target => {
@@ -3947,6 +3949,12 @@ const TrackInfo = /*@__PURE__*/proto3.makeMessageType("livekit.TrackInfo", () =>
3947
3949
  name: "version",
3948
3950
  kind: "message",
3949
3951
  T: TimedVersion
3952
+ }, {
3953
+ no: 19,
3954
+ name: "audio_features",
3955
+ kind: "enum",
3956
+ T: proto3.getEnumType(AudioTrackFeature),
3957
+ repeated: true
3950
3958
  }]);
3951
3959
 
3952
3960
  /**
@@ -4020,6 +4028,12 @@ const DataPacket = /*@__PURE__*/proto3.makeMessageType("livekit.DataPacket", ()
4020
4028
  kind: "message",
4021
4029
  T: SipDTMF,
4022
4030
  oneof: "value"
4031
+ }, {
4032
+ no: 7,
4033
+ name: "transcription",
4034
+ kind: "message",
4035
+ T: Transcription,
4036
+ oneof: "value"
4023
4037
  }]);
4024
4038
 
4025
4039
  /**
@@ -4100,6 +4114,24 @@ const UserPacket = /*@__PURE__*/proto3.makeMessageType("livekit.UserPacket", ()
4100
4114
  kind: "scalar",
4101
4115
  T: 9 /* ScalarType.STRING */,
4102
4116
  opt: true
4117
+ }, {
4118
+ no: 8,
4119
+ name: "id",
4120
+ kind: "scalar",
4121
+ T: 9 /* ScalarType.STRING */,
4122
+ opt: true
4123
+ }, {
4124
+ no: 9,
4125
+ name: "start_time",
4126
+ kind: "scalar",
4127
+ T: 4 /* ScalarType.UINT64 */,
4128
+ opt: true
4129
+ }, {
4130
+ no: 10,
4131
+ name: "end_time",
4132
+ kind: "scalar",
4133
+ T: 4 /* ScalarType.UINT64 */,
4134
+ opt: true
4103
4135
  }]);
4104
4136
 
4105
4137
  /**
@@ -4117,6 +4149,62 @@ const SipDTMF = /*@__PURE__*/proto3.makeMessageType("livekit.SipDTMF", () => [{
4117
4149
  T: 9 /* ScalarType.STRING */
4118
4150
  }]);
4119
4151
 
4152
+ /**
4153
+ * @generated from message livekit.Transcription
4154
+ */
4155
+ const Transcription = /*@__PURE__*/proto3.makeMessageType("livekit.Transcription", () => [{
4156
+ no: 2,
4157
+ name: "participant_identity",
4158
+ kind: "scalar",
4159
+ T: 9 /* ScalarType.STRING */
4160
+ }, {
4161
+ no: 3,
4162
+ name: "track_id",
4163
+ kind: "scalar",
4164
+ T: 9 /* ScalarType.STRING */
4165
+ }, {
4166
+ no: 4,
4167
+ name: "segments",
4168
+ kind: "message",
4169
+ T: TranscriptionSegment,
4170
+ repeated: true
4171
+ }]);
4172
+
4173
+ /**
4174
+ * @generated from message livekit.TranscriptionSegment
4175
+ */
4176
+ const TranscriptionSegment = /*@__PURE__*/proto3.makeMessageType("livekit.TranscriptionSegment", () => [{
4177
+ no: 1,
4178
+ name: "id",
4179
+ kind: "scalar",
4180
+ T: 9 /* ScalarType.STRING */
4181
+ }, {
4182
+ no: 2,
4183
+ name: "text",
4184
+ kind: "scalar",
4185
+ T: 9 /* ScalarType.STRING */
4186
+ }, {
4187
+ no: 3,
4188
+ name: "start_time",
4189
+ kind: "scalar",
4190
+ T: 4 /* ScalarType.UINT64 */
4191
+ }, {
4192
+ no: 4,
4193
+ name: "end_time",
4194
+ kind: "scalar",
4195
+ T: 4 /* ScalarType.UINT64 */
4196
+ }, {
4197
+ no: 5,
4198
+ name: "final",
4199
+ kind: "scalar",
4200
+ T: 8 /* ScalarType.BOOL */
4201
+ }, {
4202
+ no: 6,
4203
+ name: "language",
4204
+ kind: "scalar",
4205
+ T: 9 /* ScalarType.STRING */
4206
+ }]);
4207
+
4120
4208
  /**
4121
4209
  * @generated from message livekit.ParticipantTracks
4122
4210
  */
@@ -5048,6 +5136,7 @@ const LeaveRequest_Action = /*@__PURE__*/proto3.makeEnum("livekit.LeaveRequest.A
5048
5136
  * message to indicate published video track dimensions are changing
5049
5137
  *
5050
5138
  * @generated from message livekit.UpdateVideoLayers
5139
+ * @deprecated
5051
5140
  */
5052
5141
  const UpdateVideoLayers = /*@__PURE__*/proto3.makeMessageType("livekit.UpdateVideoLayers", () => [{
5053
5142
  no: 1,
@@ -9945,6 +10034,11 @@ var RoomEvent;
9945
10034
  * args: (payload: Uint8Array, participant: [[Participant]], kind: [[DataPacket_Kind]], topic?: string)
9946
10035
  */
9947
10036
  RoomEvent["DataReceived"] = "dataReceived";
10037
+ /**
10038
+ * Transcription received from a participant's track.
10039
+ * @beta
10040
+ */
10041
+ RoomEvent["TranscriptionReceived"] = "transcriptionReceived";
9948
10042
  /**
9949
10043
  * Connection quality was changed for a Participant. It'll receive updates
9950
10044
  * from the local participant, as well as any [[RemoteParticipant]]s that we are
@@ -10126,6 +10220,11 @@ var ParticipantEvent;
10126
10220
  * args: (payload: Uint8Array, kind: [[DataPacket_Kind]])
10127
10221
  */
10128
10222
  ParticipantEvent["DataReceived"] = "dataReceived";
10223
+ /**
10224
+ * Transcription received from this participant as data source.
10225
+ * @beta
10226
+ */
10227
+ ParticipantEvent["TranscriptionReceived"] = "transcriptionReceived";
10129
10228
  /**
10130
10229
  * Has speaking status changed for the current participant
10131
10230
  *
@@ -10195,6 +10294,7 @@ var EngineEvent;
10195
10294
  EngineEvent["MediaTrackAdded"] = "mediaTrackAdded";
10196
10295
  EngineEvent["ActiveSpeakersUpdate"] = "activeSpeakersUpdate";
10197
10296
  EngineEvent["DataPacketReceived"] = "dataPacketReceived";
10297
+ EngineEvent["TranscriptionReceived"] = "transcriptionReceived";
10198
10298
  EngineEvent["RTPVideoMapUpdate"] = "rtpVideoMapUpdate";
10199
10299
  EngineEvent["DCBufferStatusChanged"] = "dcBufferStatusChanged";
10200
10300
  EngineEvent["ParticipantUpdate"] = "participantUpdate";
@@ -10277,6 +10377,14 @@ var TrackEvent;
10277
10377
  * @internal
10278
10378
  */
10279
10379
  TrackEvent["AudioTrackFeatureUpdate"] = "audioTrackFeatureUpdate";
10380
+ /**
10381
+ * @beta
10382
+ */
10383
+ TrackEvent["TranscriptionReceived"] = "transcriptionReceived";
10384
+ /**
10385
+ * @experimental
10386
+ */
10387
+ TrackEvent["TimeSyncUpdate"] = "timeSyncUpdate";
10280
10388
  })(TrackEvent || (TrackEvent = {}));
10281
10389
 
10282
10390
  function r(r, e, n) {
@@ -10353,7 +10461,8 @@ const browsersList = [{
10353
10461
  const browser = {
10354
10462
  name: 'Firefox',
10355
10463
  version: getMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i, ua),
10356
- os: ua.toLowerCase().includes('fxios') ? 'iOS' : undefined
10464
+ os: ua.toLowerCase().includes('fxios') ? 'iOS' : undefined,
10465
+ osVersion: getOSVersion(ua)
10357
10466
  };
10358
10467
  return browser;
10359
10468
  }
@@ -10363,7 +10472,8 @@ const browsersList = [{
10363
10472
  const browser = {
10364
10473
  name: 'Chrome',
10365
10474
  version: getMatch(/(?:chrome|chromium|crios|crmo)\/(\d+(\.?_?\d+)+)/i, ua),
10366
- os: ua.toLowerCase().includes('crios') ? 'iOS' : undefined
10475
+ os: ua.toLowerCase().includes('crios') ? 'iOS' : undefined,
10476
+ osVersion: getOSVersion(ua)
10367
10477
  };
10368
10478
  return browser;
10369
10479
  }
@@ -10374,7 +10484,8 @@ const browsersList = [{
10374
10484
  const browser = {
10375
10485
  name: 'Safari',
10376
10486
  version: getMatch(commonVersionIdentifier, ua),
10377
- os: ua.includes('mobile/') ? 'iOS' : 'macOS'
10487
+ os: ua.includes('mobile/') ? 'iOS' : 'macOS',
10488
+ osVersion: getOSVersion(ua)
10378
10489
  };
10379
10490
  return browser;
10380
10491
  }
@@ -10384,8 +10495,11 @@ function getMatch(exp, ua) {
10384
10495
  const match = ua.match(exp);
10385
10496
  return match && match.length >= id && match[id] || '';
10386
10497
  }
10498
+ function getOSVersion(ua) {
10499
+ return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
10500
+ }
10387
10501
 
10388
- var version$1 = "2.1.0";
10502
+ var version$1 = "2.1.2";
10389
10503
 
10390
10504
  const version = version$1;
10391
10505
  const protocolVersion = 12;
@@ -10686,6 +10800,9 @@ class Track extends eventsExports.EventEmitter {
10686
10800
  if (this.monitorInterval) {
10687
10801
  clearInterval(this.monitorInterval);
10688
10802
  }
10803
+ if (this.timeSyncHandle) {
10804
+ cancelAnimationFrame(this.timeSyncHandle);
10805
+ }
10689
10806
  }
10690
10807
  /** @internal */
10691
10808
  updateLoggerOptions(loggerOptions) {
@@ -11175,29 +11292,6 @@ function supportsSetSinkId(elm) {
11175
11292
  }
11176
11293
  return 'setSinkId' in elm;
11177
11294
  }
11178
- const setCodecPreferencesVersions = {
11179
- Chrome: '100',
11180
- Safari: '15',
11181
- Firefox: '100'
11182
- };
11183
- function supportsSetCodecPreferences(transceiver) {
11184
- if (!isWeb()) {
11185
- return false;
11186
- }
11187
- if (!('setCodecPreferences' in transceiver)) {
11188
- return false;
11189
- }
11190
- const browser = getBrowser();
11191
- if (!(browser === null || browser === void 0 ? void 0 : browser.name) || !browser.version) {
11192
- // version is required
11193
- return false;
11194
- }
11195
- const v = setCodecPreferencesVersions[browser.name];
11196
- if (v) {
11197
- return compareVersions(browser.version, v) >= 0;
11198
- }
11199
- return false;
11200
- }
11201
11295
  function isBrowserSupported() {
11202
11296
  if (typeof RTCPeerConnection === 'undefined') {
11203
11297
  return false;
@@ -11217,8 +11311,27 @@ function isSafari17() {
11217
11311
  return (b === null || b === void 0 ? void 0 : b.name) === 'Safari' && b.version.startsWith('17.');
11218
11312
  }
11219
11313
  function isMobile() {
11314
+ var _a, _b;
11220
11315
  if (!isWeb()) return false;
11221
- return /Tablet|iPad|Mobile|Android|BlackBerry/.test(navigator.userAgent);
11316
+ return (
11317
+ // @ts-expect-error `userAgentData` is not yet part of typescript
11318
+ (_b = (_a = navigator.userAgentData) === null || _a === void 0 ? void 0 : _a.mobile) !== null && _b !== void 0 ? _b : /Tablet|iPad|Mobile|Android|BlackBerry/.test(navigator.userAgent)
11319
+ );
11320
+ }
11321
+ function isE2EESimulcastSupported() {
11322
+ const browser = getBrowser();
11323
+ const supportedSafariVersion = '17.2'; // see https://bugs.webkit.org/show_bug.cgi?id=257803
11324
+ if (browser) {
11325
+ if (browser.name !== 'Safari' && browser.os !== 'iOS') {
11326
+ return true;
11327
+ } else if (browser.os === 'iOS' && browser.osVersion && compareVersions(supportedSafariVersion, browser.osVersion) >= 0) {
11328
+ return true;
11329
+ } else if (browser.name === 'Safari' && compareVersions(supportedSafariVersion, browser.version) >= 0) {
11330
+ return true;
11331
+ } else {
11332
+ return false;
11333
+ }
11334
+ }
11222
11335
  }
11223
11336
  function isWeb() {
11224
11337
  return typeof document !== 'undefined';
@@ -11494,6 +11607,26 @@ function toHttpUrl(url) {
11494
11607
  }
11495
11608
  return url;
11496
11609
  }
11610
+ function extractTranscriptionSegments(transcription) {
11611
+ return transcription.segments.map(_ref => {
11612
+ let {
11613
+ id,
11614
+ text,
11615
+ language,
11616
+ startTime,
11617
+ endTime,
11618
+ final
11619
+ } = _ref;
11620
+ return {
11621
+ id,
11622
+ text,
11623
+ startTime: Number.parseInt(startTime.toString()),
11624
+ endTime: Number.parseInt(endTime.toString()),
11625
+ final,
11626
+ language
11627
+ };
11628
+ });
11629
+ }
11497
11630
 
11498
11631
  const defaultId = 'default';
11499
11632
  class DeviceManager {
@@ -14141,8 +14274,6 @@ class PCTransport extends eventsExports.EventEmitter {
14141
14274
  yield this.pc.setLocalDescription(sd);
14142
14275
  }
14143
14276
  } catch (e) {
14144
- // this error cannot always be caught.
14145
- // If the local description has a setCodecPreferences error, this error will be uncaught
14146
14277
  let msg = 'unknown error';
14147
14278
  if (e instanceof Error) {
14148
14279
  msg = e.message;
@@ -14568,2330 +14699,2290 @@ class PCTransportManager {
14568
14699
  }
14569
14700
  }
14570
14701
 
14571
- const lossyDataChannel = '_lossy';
14572
- const reliableDataChannel = '_reliable';
14573
- const minReconnectWait = 2 * 1000;
14574
- const leaveReconnect = 'leave-reconnect';
14575
- var PCState;
14576
- (function (PCState) {
14577
- PCState[PCState["New"] = 0] = "New";
14578
- PCState[PCState["Connected"] = 1] = "Connected";
14579
- PCState[PCState["Disconnected"] = 2] = "Disconnected";
14580
- PCState[PCState["Reconnecting"] = 3] = "Reconnecting";
14581
- PCState[PCState["Closed"] = 4] = "Closed";
14582
- })(PCState || (PCState = {}));
14583
- /** @internal */
14584
- class RTCEngine extends eventsExports.EventEmitter {
14585
- get isClosed() {
14586
- return this._isClosed;
14702
+ const monitorFrequency = 2000;
14703
+ function computeBitrate(currentStats, prevStats) {
14704
+ if (!prevStats) {
14705
+ return 0;
14587
14706
  }
14588
- get pendingReconnect() {
14589
- return !!this.reconnectTimeout;
14707
+ let bytesNow;
14708
+ let bytesPrev;
14709
+ if ('bytesReceived' in currentStats) {
14710
+ bytesNow = currentStats.bytesReceived;
14711
+ bytesPrev = prevStats.bytesReceived;
14712
+ } else if ('bytesSent' in currentStats) {
14713
+ bytesNow = currentStats.bytesSent;
14714
+ bytesPrev = prevStats.bytesSent;
14590
14715
  }
14591
- constructor(options) {
14592
- var _a;
14593
- super();
14594
- this.options = options;
14595
- this.rtcConfig = {};
14596
- this.peerConnectionTimeout = roomConnectOptionDefaults.peerConnectionTimeout;
14597
- this.fullReconnectOnNext = false;
14598
- this.subscriberPrimary = false;
14599
- this.pcState = PCState.New;
14600
- this._isClosed = true;
14601
- this.pendingTrackResolvers = {};
14602
- this.reconnectAttempts = 0;
14603
- this.reconnectStart = 0;
14604
- this.attemptingReconnect = false;
14605
- /** keeps track of how often an initial join connection has been tried */
14606
- this.joinAttempts = 0;
14607
- /** specifies how often an initial join connection is allowed to retry */
14608
- this.maxJoinAttempts = 1;
14609
- this.shouldFailNext = false;
14610
- this.log = livekitLogger;
14611
- this.handleDataChannel = _b => __awaiter(this, [_b], void 0, function (_ref) {
14612
- var _this = this;
14613
- let {
14614
- channel
14615
- } = _ref;
14616
- return function* () {
14617
- if (!channel) {
14618
- return;
14619
- }
14620
- if (channel.label === reliableDataChannel) {
14621
- _this.reliableDCSub = channel;
14622
- } else if (channel.label === lossyDataChannel) {
14623
- _this.lossyDCSub = channel;
14624
- } else {
14625
- return;
14626
- }
14627
- _this.log.debug("on data channel ".concat(channel.id, ", ").concat(channel.label), _this.logContext);
14628
- channel.onmessage = _this.handleDataMessage;
14629
- }();
14630
- });
14631
- this.handleDataMessage = message => __awaiter(this, void 0, void 0, function* () {
14632
- var _c, _d;
14633
- // make sure to respect incoming data message order by processing message events one after the other
14634
- const unlock = yield this.dataProcessLock.lock();
14635
- try {
14636
- // decode
14637
- let buffer;
14638
- if (message.data instanceof ArrayBuffer) {
14639
- buffer = message.data;
14640
- } else if (message.data instanceof Blob) {
14641
- buffer = yield message.data.arrayBuffer();
14642
- } else {
14643
- this.log.error('unsupported data type', Object.assign(Object.assign({}, this.logContext), {
14644
- data: message.data
14645
- }));
14646
- return;
14647
- }
14648
- const dp = DataPacket.fromBinary(new Uint8Array(buffer));
14649
- if (((_c = dp.value) === null || _c === void 0 ? void 0 : _c.case) === 'speaker') {
14650
- // dispatch speaker updates
14651
- this.emit(EngineEvent.ActiveSpeakersUpdate, dp.value.value.speakers);
14652
- } else if (((_d = dp.value) === null || _d === void 0 ? void 0 : _d.case) === 'user') {
14653
- this.emit(EngineEvent.DataPacketReceived, dp.value.value, dp.kind);
14654
- }
14655
- } finally {
14656
- unlock();
14716
+ if (bytesNow === undefined || bytesPrev === undefined || currentStats.timestamp === undefined || prevStats.timestamp === undefined) {
14717
+ return 0;
14718
+ }
14719
+ return (bytesNow - bytesPrev) * 8 * 1000 / (currentStats.timestamp - prevStats.timestamp);
14720
+ }
14721
+
14722
+ class LocalAudioTrack extends LocalTrack {
14723
+ /**
14724
+ * boolean indicating whether enhanced noise cancellation is currently being used on this track
14725
+ */
14726
+ get enhancedNoiseCancellation() {
14727
+ return this.isKrispNoiseFilterEnabled;
14728
+ }
14729
+ /**
14730
+ *
14731
+ * @param mediaTrack
14732
+ * @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks
14733
+ * @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK
14734
+ */
14735
+ constructor(mediaTrack, constraints) {
14736
+ let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
14737
+ let audioContext = arguments.length > 3 ? arguments[3] : undefined;
14738
+ let loggerOptions = arguments.length > 4 ? arguments[4] : undefined;
14739
+ super(mediaTrack, Track.Kind.Audio, constraints, userProvidedTrack, loggerOptions);
14740
+ /** @internal */
14741
+ this.stopOnMute = false;
14742
+ this.isKrispNoiseFilterEnabled = false;
14743
+ this.monitorSender = () => __awaiter(this, void 0, void 0, function* () {
14744
+ if (!this.sender) {
14745
+ this._currentBitrate = 0;
14746
+ return;
14657
14747
  }
14658
- });
14659
- this.handleDataError = event => {
14660
- const channel = event.currentTarget;
14661
- const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable';
14662
- if (event instanceof ErrorEvent && event.error) {
14663
- const {
14664
- error
14665
- } = event.error;
14666
- this.log.error("DataChannel error on ".concat(channelKind, ": ").concat(event.message), Object.assign(Object.assign({}, this.logContext), {
14667
- error
14668
- }));
14669
- } else {
14670
- this.log.error("Unknown DataChannel error on ".concat(channelKind), Object.assign(Object.assign({}, this.logContext), {
14671
- event
14748
+ let stats;
14749
+ try {
14750
+ stats = yield this.getSenderStats();
14751
+ } catch (e) {
14752
+ this.log.error('could not get audio sender stats', Object.assign(Object.assign({}, this.logContext), {
14753
+ error: e
14672
14754
  }));
14755
+ return;
14673
14756
  }
14674
- };
14675
- this.handleBufferedAmountLow = event => {
14676
- const channel = event.currentTarget;
14677
- const channelKind = channel.maxRetransmits === 0 ? DataPacket_Kind.LOSSY : DataPacket_Kind.RELIABLE;
14678
- this.updateAndEmitDCBufferStatus(channelKind);
14679
- };
14680
- // websocket reconnect behavior. if websocket is interrupted, and the PeerConnection
14681
- // continues to work, we can reconnect to websocket to continue the session
14682
- // after a number of retries, we'll close and give up permanently
14683
- this.handleDisconnect = (connection, disconnectReason) => {
14684
- if (this._isClosed) {
14685
- return;
14686
- }
14687
- this.log.warn("".concat(connection, " disconnected"), this.logContext);
14688
- if (this.reconnectAttempts === 0) {
14689
- // only reset start time on the first try
14690
- this.reconnectStart = Date.now();
14691
- }
14692
- const disconnect = duration => {
14693
- this.log.warn("could not recover connection after ".concat(this.reconnectAttempts, " attempts, ").concat(duration, "ms. giving up"), this.logContext);
14694
- this.emit(EngineEvent.Disconnected);
14695
- this.close();
14696
- };
14697
- const duration = Date.now() - this.reconnectStart;
14698
- let delay = this.getNextRetryDelay({
14699
- elapsedMs: duration,
14700
- retryCount: this.reconnectAttempts
14701
- });
14702
- if (delay === null) {
14703
- disconnect(duration);
14704
- return;
14705
- }
14706
- if (connection === leaveReconnect) {
14707
- delay = 0;
14708
- }
14709
- this.log.debug("reconnecting in ".concat(delay, "ms"), this.logContext);
14710
- this.clearReconnectTimeout();
14711
- if (this.token && this.regionUrlProvider) {
14712
- // token may have been refreshed, we do not want to recreate the regionUrlProvider
14713
- // since the current engine may have inherited a regional url
14714
- this.regionUrlProvider.updateToken(this.token);
14757
+ if (stats && this.prevStats) {
14758
+ this._currentBitrate = computeBitrate(stats, this.prevStats);
14715
14759
  }
14716
- this.reconnectTimeout = CriticalTimers.setTimeout(() => this.attemptReconnect(disconnectReason).finally(() => this.reconnectTimeout = undefined), delay);
14717
- };
14718
- this.waitForRestarted = () => {
14719
- return new Promise((resolve, reject) => {
14720
- if (this.pcState === PCState.Connected) {
14721
- resolve();
14722
- }
14723
- const onRestarted = () => {
14724
- this.off(EngineEvent.Disconnected, onDisconnected);
14725
- resolve();
14726
- };
14727
- const onDisconnected = () => {
14728
- this.off(EngineEvent.Restarted, onRestarted);
14729
- reject();
14730
- };
14731
- this.once(EngineEvent.Restarted, onRestarted);
14732
- this.once(EngineEvent.Disconnected, onDisconnected);
14733
- });
14760
+ this.prevStats = stats;
14761
+ });
14762
+ this.handleKrispNoiseFilterEnable = () => {
14763
+ this.isKrispNoiseFilterEnabled = true;
14764
+ this.log.debug("Krisp noise filter enabled", this.logContext);
14765
+ this.emit(TrackEvent.AudioTrackFeatureUpdate, this, AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION, true);
14734
14766
  };
14735
- this.updateAndEmitDCBufferStatus = kind => {
14736
- const status = this.isBufferStatusLow(kind);
14737
- if (typeof status !== 'undefined' && status !== this.dcBufferStatus.get(kind)) {
14738
- this.dcBufferStatus.set(kind, status);
14739
- this.emit(EngineEvent.DCBufferStatusChanged, status, kind);
14740
- }
14767
+ this.handleKrispNoiseFilterDisable = () => {
14768
+ this.isKrispNoiseFilterEnabled = false;
14769
+ this.log.debug("Krisp noise filter disabled", this.logContext);
14770
+ this.emit(TrackEvent.AudioTrackFeatureUpdate, this, AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION, false);
14741
14771
  };
14742
- this.isBufferStatusLow = kind => {
14743
- const dc = this.dataChannelForKind(kind);
14744
- if (dc) {
14745
- return dc.bufferedAmount <= dc.bufferedAmountLowThreshold;
14772
+ this.audioContext = audioContext;
14773
+ this.checkForSilence();
14774
+ }
14775
+ setDeviceId(deviceId) {
14776
+ return __awaiter(this, void 0, void 0, function* () {
14777
+ if (this._constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)) {
14778
+ return true;
14746
14779
  }
14747
- };
14748
- this.handleBrowserOnLine = () => {
14749
- // in case the engine is currently reconnecting, attempt a reconnect immediately after the browser state has changed to 'onLine'
14750
- if (this.client.currentState === SignalConnectionState.RECONNECTING) {
14751
- this.clearReconnectTimeout();
14752
- this.attemptReconnect(ReconnectReason.RR_SIGNAL_DISCONNECTED);
14780
+ this._constraints.deviceId = deviceId;
14781
+ if (!this.isMuted) {
14782
+ yield this.restartTrack();
14753
14783
  }
14754
- };
14755
- this.log = getLogger((_a = options.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Engine);
14756
- this.loggerOptions = {
14757
- loggerName: options.loggerName,
14758
- loggerContextCb: () => this.logContext
14759
- };
14760
- this.client = new SignalClient(undefined, this.loggerOptions);
14761
- this.client.signalLatency = this.options.expSignalLatency;
14762
- this.reconnectPolicy = this.options.reconnectPolicy;
14763
- this.registerOnLineListener();
14764
- this.closingLock = new Mutex();
14765
- this.dataProcessLock = new Mutex();
14766
- this.dcBufferStatus = new Map([[DataPacket_Kind.LOSSY, true], [DataPacket_Kind.RELIABLE, true]]);
14767
- this.client.onParticipantUpdate = updates => this.emit(EngineEvent.ParticipantUpdate, updates);
14768
- this.client.onConnectionQuality = update => this.emit(EngineEvent.ConnectionQualityUpdate, update);
14769
- this.client.onRoomUpdate = update => this.emit(EngineEvent.RoomUpdate, update);
14770
- this.client.onSubscriptionError = resp => this.emit(EngineEvent.SubscriptionError, resp);
14771
- this.client.onSubscriptionPermissionUpdate = update => this.emit(EngineEvent.SubscriptionPermissionUpdate, update);
14772
- this.client.onSpeakersChanged = update => this.emit(EngineEvent.SpeakersChanged, update);
14773
- this.client.onStreamStateUpdate = update => this.emit(EngineEvent.StreamStateChanged, update);
14774
- }
14775
- /** @internal */
14776
- get logContext() {
14777
- var _a, _b, _c, _d, _e, _f, _g, _h;
14778
- return {
14779
- room: (_b = (_a = this.latestJoinResponse) === null || _a === void 0 ? void 0 : _a.room) === null || _b === void 0 ? void 0 : _b.name,
14780
- roomID: (_d = (_c = this.latestJoinResponse) === null || _c === void 0 ? void 0 : _c.room) === null || _d === void 0 ? void 0 : _d.sid,
14781
- participant: (_f = (_e = this.latestJoinResponse) === null || _e === void 0 ? void 0 : _e.participant) === null || _f === void 0 ? void 0 : _f.identity,
14782
- pID: (_h = (_g = this.latestJoinResponse) === null || _g === void 0 ? void 0 : _g.participant) === null || _h === void 0 ? void 0 : _h.sid
14783
- };
14784
+ return this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
14785
+ });
14784
14786
  }
14785
- join(url, token, opts, abortSignal) {
14787
+ mute() {
14788
+ const _super = Object.create(null, {
14789
+ mute: {
14790
+ get: () => super.mute
14791
+ }
14792
+ });
14786
14793
  return __awaiter(this, void 0, void 0, function* () {
14787
- this.url = url;
14788
- this.token = token;
14789
- this.signalOpts = opts;
14790
- this.maxJoinAttempts = opts.maxRetries;
14794
+ const unlock = yield this.muteLock.lock();
14791
14795
  try {
14792
- this.joinAttempts += 1;
14793
- this.setupSignalClientCallbacks();
14794
- const joinResponse = yield this.client.join(url, token, opts, abortSignal);
14795
- this._isClosed = false;
14796
- this.latestJoinResponse = joinResponse;
14797
- this.subscriberPrimary = joinResponse.subscriberPrimary;
14798
- if (!this.pcManager) {
14799
- yield this.configure(joinResponse);
14800
- }
14801
- // create offer
14802
- if (!this.subscriberPrimary) {
14803
- this.negotiate();
14796
+ if (this.isMuted) {
14797
+ this.log.debug('Track already muted', this.logContext);
14798
+ return this;
14804
14799
  }
14805
- this.clientConfiguration = joinResponse.clientConfiguration;
14806
- return joinResponse;
14807
- } catch (e) {
14808
- if (e instanceof ConnectionError) {
14809
- if (e.reason === 1 /* ConnectionErrorReason.ServerUnreachable */) {
14810
- this.log.warn("Couldn't connect to server, attempt ".concat(this.joinAttempts, " of ").concat(this.maxJoinAttempts), this.logContext);
14811
- if (this.joinAttempts < this.maxJoinAttempts) {
14812
- return this.join(url, token, opts, abortSignal);
14813
- }
14814
- }
14800
+ // disabled special handling as it will cause BT headsets to switch communication modes
14801
+ if (this.source === Track.Source.Microphone && this.stopOnMute && !this.isUserProvided) {
14802
+ this.log.debug('stopping mic track', this.logContext);
14803
+ // also stop the track, so that microphone indicator is turned off
14804
+ this._mediaStreamTrack.stop();
14815
14805
  }
14816
- throw e;
14806
+ yield _super.mute.call(this);
14807
+ return this;
14808
+ } finally {
14809
+ unlock();
14817
14810
  }
14818
14811
  });
14819
14812
  }
14820
- close() {
14821
- return __awaiter(this, void 0, void 0, function* () {
14822
- const unlock = yield this.closingLock.lock();
14823
- if (this.isClosed) {
14824
- unlock();
14825
- return;
14813
+ unmute() {
14814
+ const _super = Object.create(null, {
14815
+ unmute: {
14816
+ get: () => super.unmute
14826
14817
  }
14818
+ });
14819
+ return __awaiter(this, void 0, void 0, function* () {
14820
+ const unlock = yield this.muteLock.lock();
14827
14821
  try {
14828
- this._isClosed = true;
14829
- this.emit(EngineEvent.Closing);
14830
- this.removeAllListeners();
14831
- this.deregisterOnLineListener();
14832
- this.clearPendingReconnect();
14833
- yield this.cleanupPeerConnections();
14834
- yield this.cleanupClient();
14822
+ if (!this.isMuted) {
14823
+ this.log.debug('Track already unmuted', this.logContext);
14824
+ return this;
14825
+ }
14826
+ const deviceHasChanged = this._constraints.deviceId && this._mediaStreamTrack.getSettings().deviceId !== unwrapConstraint(this._constraints.deviceId);
14827
+ if (this.source === Track.Source.Microphone && (this.stopOnMute || this._mediaStreamTrack.readyState === 'ended' || deviceHasChanged) && !this.isUserProvided) {
14828
+ this.log.debug('reacquiring mic track', this.logContext);
14829
+ yield this.restartTrack();
14830
+ }
14831
+ yield _super.unmute.call(this);
14832
+ return this;
14835
14833
  } finally {
14836
14834
  unlock();
14837
14835
  }
14838
14836
  });
14839
14837
  }
14840
- cleanupPeerConnections() {
14838
+ restartTrack(options) {
14841
14839
  return __awaiter(this, void 0, void 0, function* () {
14842
- var _a;
14843
- yield (_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.close();
14844
- this.pcManager = undefined;
14845
- const dcCleanup = dc => {
14846
- if (!dc) return;
14847
- dc.close();
14848
- dc.onbufferedamountlow = null;
14849
- dc.onclose = null;
14850
- dc.onclosing = null;
14851
- dc.onerror = null;
14852
- dc.onmessage = null;
14853
- dc.onopen = null;
14854
- };
14855
- dcCleanup(this.lossyDC);
14856
- dcCleanup(this.lossyDCSub);
14857
- dcCleanup(this.reliableDC);
14858
- dcCleanup(this.reliableDCSub);
14859
- this.lossyDC = undefined;
14860
- this.lossyDCSub = undefined;
14861
- this.reliableDC = undefined;
14862
- this.reliableDCSub = undefined;
14840
+ let constraints;
14841
+ if (options) {
14842
+ const streamConstraints = constraintsForOptions({
14843
+ audio: options
14844
+ });
14845
+ if (typeof streamConstraints.audio !== 'boolean') {
14846
+ constraints = streamConstraints.audio;
14847
+ }
14848
+ }
14849
+ yield this.restart(constraints);
14863
14850
  });
14864
14851
  }
14865
- cleanupClient() {
14852
+ restart(constraints) {
14853
+ const _super = Object.create(null, {
14854
+ restart: {
14855
+ get: () => super.restart
14856
+ }
14857
+ });
14866
14858
  return __awaiter(this, void 0, void 0, function* () {
14867
- yield this.client.close();
14868
- this.client.resetCallbacks();
14859
+ const track = yield _super.restart.call(this, constraints);
14860
+ this.checkForSilence();
14861
+ return track;
14869
14862
  });
14870
14863
  }
14871
- addTrack(req) {
14872
- if (this.pendingTrackResolvers[req.cid]) {
14873
- throw new TrackInvalidError('a track with the same ID has already been published');
14864
+ /* @internal */
14865
+ startMonitor() {
14866
+ if (!isWeb()) {
14867
+ return;
14874
14868
  }
14875
- return new Promise((resolve, reject) => {
14876
- const publicationTimeout = setTimeout(() => {
14877
- delete this.pendingTrackResolvers[req.cid];
14878
- reject(new ConnectionError('publication of local track timed out, no response from server'));
14879
- }, 10000);
14880
- this.pendingTrackResolvers[req.cid] = {
14881
- resolve: info => {
14882
- clearTimeout(publicationTimeout);
14883
- resolve(info);
14884
- },
14885
- reject: () => {
14886
- clearTimeout(publicationTimeout);
14887
- reject(new Error('Cancelled publication by calling unpublish'));
14888
- }
14889
- };
14890
- this.client.sendAddTrack(req);
14891
- });
14892
- }
14893
- /**
14894
- * Removes sender from PeerConnection, returning true if it was removed successfully
14895
- * and a negotiation is necessary
14896
- * @param sender
14897
- * @returns
14898
- */
14899
- removeTrack(sender) {
14900
- if (sender.track && this.pendingTrackResolvers[sender.track.id]) {
14901
- const {
14902
- reject
14903
- } = this.pendingTrackResolvers[sender.track.id];
14904
- if (reject) {
14905
- reject();
14906
- }
14907
- delete this.pendingTrackResolvers[sender.track.id];
14908
- }
14909
- try {
14910
- this.pcManager.removeTrack(sender);
14911
- return true;
14912
- } catch (e) {
14913
- this.log.warn('failed to remove track', Object.assign(Object.assign({}, this.logContext), {
14914
- error: e
14915
- }));
14869
+ if (this.monitorInterval) {
14870
+ return;
14916
14871
  }
14917
- return false;
14918
- }
14919
- updateMuteStatus(trackSid, muted) {
14920
- this.client.sendMuteTrack(trackSid, muted);
14921
- }
14922
- get dataSubscriberReadyState() {
14923
- var _a;
14924
- return (_a = this.reliableDCSub) === null || _a === void 0 ? void 0 : _a.readyState;
14872
+ this.monitorInterval = setInterval(() => {
14873
+ this.monitorSender();
14874
+ }, monitorFrequency);
14925
14875
  }
14926
- getConnectedServerAddress() {
14876
+ setProcessor(processor) {
14927
14877
  return __awaiter(this, void 0, void 0, function* () {
14928
14878
  var _a;
14929
- return (_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.getConnectedAddress();
14879
+ const unlock = yield this.processorLock.lock();
14880
+ try {
14881
+ if (!this.audioContext) {
14882
+ throw Error('Audio context needs to be set on LocalAudioTrack in order to enable processors');
14883
+ }
14884
+ if (this.processor) {
14885
+ yield this.stopProcessor();
14886
+ }
14887
+ const processorOptions = {
14888
+ kind: this.kind,
14889
+ track: this._mediaStreamTrack,
14890
+ audioContext: this.audioContext
14891
+ };
14892
+ this.log.debug("setting up audio processor ".concat(processor.name), this.logContext);
14893
+ yield processor.init(processorOptions);
14894
+ this.processor = processor;
14895
+ if (this.processor.processedTrack) {
14896
+ yield (_a = this.sender) === null || _a === void 0 ? void 0 : _a.replaceTrack(this.processor.processedTrack);
14897
+ this.processor.processedTrack.addEventListener('enable-lk-krisp-noise-filter', this.handleKrispNoiseFilterEnable);
14898
+ this.processor.processedTrack.addEventListener('disable-lk-krisp-noise-filter', this.handleKrispNoiseFilterDisable);
14899
+ }
14900
+ this.emit(TrackEvent.TrackProcessorUpdate, this.processor);
14901
+ } finally {
14902
+ unlock();
14903
+ }
14930
14904
  });
14931
14905
  }
14932
- /* @internal */
14933
- setRegionUrlProvider(provider) {
14934
- this.regionUrlProvider = provider;
14906
+ /**
14907
+ * @internal
14908
+ * @experimental
14909
+ */
14910
+ setAudioContext(audioContext) {
14911
+ this.audioContext = audioContext;
14935
14912
  }
14936
- configure(joinResponse) {
14913
+ getSenderStats() {
14937
14914
  return __awaiter(this, void 0, void 0, function* () {
14938
14915
  var _a;
14939
- // already configured
14940
- if (this.pcManager && this.pcManager.currentState !== PCTransportState.NEW) {
14941
- return;
14916
+ if (!((_a = this.sender) === null || _a === void 0 ? void 0 : _a.getStats)) {
14917
+ return undefined;
14942
14918
  }
14943
- this.participantSid = (_a = joinResponse.participant) === null || _a === void 0 ? void 0 : _a.sid;
14944
- const rtcConfig = this.makeRTCConfiguration(joinResponse);
14945
- this.pcManager = new PCTransportManager(rtcConfig, joinResponse.subscriberPrimary, this.loggerOptions);
14946
- this.emit(EngineEvent.TransportsCreated, this.pcManager.publisher, this.pcManager.subscriber);
14947
- this.pcManager.onIceCandidate = (candidate, target) => {
14948
- this.client.sendIceCandidate(candidate, target);
14949
- };
14950
- this.pcManager.onPublisherOffer = offer => {
14951
- this.client.sendOffer(offer);
14952
- };
14953
- this.pcManager.onDataChannel = this.handleDataChannel;
14954
- this.pcManager.onStateChange = (connectionState, publisherState, subscriberState) => __awaiter(this, void 0, void 0, function* () {
14955
- this.log.debug("primary PC state changed ".concat(connectionState), this.logContext);
14956
- if (['closed', 'disconnected', 'failed'].includes(publisherState)) {
14957
- // reset publisher connection promise
14958
- this.publisherConnectionPromise = undefined;
14959
- }
14960
- if (connectionState === PCTransportState.CONNECTED) {
14961
- const shouldEmit = this.pcState === PCState.New;
14962
- this.pcState = PCState.Connected;
14963
- if (shouldEmit) {
14964
- this.emit(EngineEvent.Connected, joinResponse);
14965
- }
14966
- } else if (connectionState === PCTransportState.FAILED) {
14967
- // on Safari, PeerConnection will switch to 'disconnected' during renegotiation
14968
- if (this.pcState === PCState.Connected) {
14969
- this.pcState = PCState.Disconnected;
14970
- this.handleDisconnect('peerconnection failed', subscriberState === 'failed' ? ReconnectReason.RR_SUBSCRIBER_FAILED : ReconnectReason.RR_PUBLISHER_FAILED);
14971
- }
14972
- }
14973
- // detect cases where both signal client and peer connection are severed and assume that user has lost network connection
14974
- const isSignalSevered = this.client.isDisconnected || this.client.currentState === SignalConnectionState.RECONNECTING;
14975
- const isPCSevered = [PCTransportState.FAILED, PCTransportState.CLOSING, PCTransportState.CLOSED].includes(connectionState);
14976
- if (isSignalSevered && isPCSevered && !this._isClosed) {
14977
- this.emit(EngineEvent.Offline);
14919
+ const stats = yield this.sender.getStats();
14920
+ let audioStats;
14921
+ stats.forEach(v => {
14922
+ if (v.type === 'outbound-rtp') {
14923
+ audioStats = {
14924
+ type: 'audio',
14925
+ streamId: v.id,
14926
+ packetsSent: v.packetsSent,
14927
+ packetsLost: v.packetsLost,
14928
+ bytesSent: v.bytesSent,
14929
+ timestamp: v.timestamp,
14930
+ roundTripTime: v.roundTripTime,
14931
+ jitter: v.jitter
14932
+ };
14978
14933
  }
14979
14934
  });
14980
- this.pcManager.onTrack = ev => {
14981
- this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
14982
- };
14983
- this.createDataChannels();
14935
+ return audioStats;
14984
14936
  });
14985
14937
  }
14986
- setupSignalClientCallbacks() {
14987
- // configure signaling client
14988
- this.client.onAnswer = sd => __awaiter(this, void 0, void 0, function* () {
14989
- if (!this.pcManager) {
14990
- return;
14991
- }
14992
- this.log.debug('received server answer', Object.assign(Object.assign({}, this.logContext), {
14993
- RTCSdpType: sd.type
14994
- }));
14995
- yield this.pcManager.setPublisherAnswer(sd);
14996
- });
14997
- // add candidate on trickle
14998
- this.client.onTrickle = (candidate, target) => {
14999
- if (!this.pcManager) {
15000
- return;
15001
- }
15002
- this.log.trace('got ICE candidate from peer', Object.assign(Object.assign({}, this.logContext), {
15003
- candidate,
15004
- target
15005
- }));
15006
- this.pcManager.addIceCandidate(candidate, target);
15007
- };
15008
- // when server creates an offer for the client
15009
- this.client.onOffer = sd => __awaiter(this, void 0, void 0, function* () {
15010
- if (!this.pcManager) {
15011
- return;
14938
+ checkForSilence() {
14939
+ return __awaiter(this, void 0, void 0, function* () {
14940
+ const trackIsSilent = yield detectSilence(this);
14941
+ if (trackIsSilent) {
14942
+ if (!this.isMuted) {
14943
+ this.log.warn('silence detected on local audio track', this.logContext);
14944
+ }
14945
+ this.emit(TrackEvent.AudioSilenceDetected);
15012
14946
  }
15013
- const answer = yield this.pcManager.createSubscriberAnswerFromOffer(sd);
15014
- this.client.sendAnswer(answer);
14947
+ return trackIsSilent;
15015
14948
  });
15016
- this.client.onLocalTrackPublished = res => {
15017
- var _a;
15018
- this.log.debug('received trackPublishedResponse', Object.assign(Object.assign({}, this.logContext), {
15019
- cid: res.cid,
15020
- track: (_a = res.track) === null || _a === void 0 ? void 0 : _a.sid
15021
- }));
15022
- if (!this.pendingTrackResolvers[res.cid]) {
15023
- this.log.error("missing track resolver for ".concat(res.cid), Object.assign(Object.assign({}, this.logContext), {
15024
- cid: res.cid
15025
- }));
15026
- return;
15027
- }
15028
- const {
15029
- resolve
15030
- } = this.pendingTrackResolvers[res.cid];
15031
- delete this.pendingTrackResolvers[res.cid];
15032
- resolve(res.track);
15033
- };
15034
- this.client.onLocalTrackUnpublished = response => {
15035
- this.emit(EngineEvent.LocalTrackUnpublished, response);
15036
- };
15037
- this.client.onTokenRefresh = token => {
15038
- this.token = token;
15039
- };
15040
- this.client.onRemoteMuteChanged = (trackSid, muted) => {
15041
- this.emit(EngineEvent.RemoteMute, trackSid, muted);
15042
- };
15043
- this.client.onSubscribedQualityUpdate = update => {
15044
- this.emit(EngineEvent.SubscribedQualityUpdate, update);
15045
- };
15046
- this.client.onClose = () => {
15047
- this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED);
15048
- };
15049
- this.client.onLeave = leave => {
15050
- if (leave === null || leave === void 0 ? void 0 : leave.canReconnect) {
15051
- this.fullReconnectOnNext = true;
15052
- // reconnect immediately instead of waiting for next attempt
15053
- this.handleDisconnect(leaveReconnect);
15054
- } else {
15055
- this.emit(EngineEvent.Disconnected, leave === null || leave === void 0 ? void 0 : leave.reason);
15056
- this.close();
15057
- }
15058
- this.log.debug('client leave request', Object.assign(Object.assign({}, this.logContext), {
15059
- reason: leave === null || leave === void 0 ? void 0 : leave.reason
15060
- }));
15061
- };
15062
14949
  }
15063
- makeRTCConfiguration(serverResponse) {
15064
- var _a;
15065
- const rtcConfig = Object.assign({}, this.rtcConfig);
15066
- if ((_a = this.signalOpts) === null || _a === void 0 ? void 0 : _a.e2eeEnabled) {
15067
- this.log.debug('E2EE - setting up transports with insertable streams', this.logContext);
15068
- // this makes sure that no data is sent before the transforms are ready
15069
- // @ts-ignore
15070
- rtcConfig.encodedInsertableStreams = true;
15071
- }
15072
- // update ICE servers before creating PeerConnection
15073
- if (serverResponse.iceServers && !rtcConfig.iceServers) {
15074
- const rtcIceServers = [];
15075
- serverResponse.iceServers.forEach(iceServer => {
15076
- const rtcIceServer = {
15077
- urls: iceServer.urls
15078
- };
15079
- if (iceServer.username) rtcIceServer.username = iceServer.username;
15080
- if (iceServer.credential) {
15081
- rtcIceServer.credential = iceServer.credential;
15082
- }
15083
- rtcIceServers.push(rtcIceServer);
15084
- });
15085
- rtcConfig.iceServers = rtcIceServers;
15086
- }
15087
- if (serverResponse.clientConfiguration && serverResponse.clientConfiguration.forceRelay === ClientConfigSetting.ENABLED) {
15088
- rtcConfig.iceTransportPolicy = 'relay';
15089
- }
15090
- // @ts-ignore
15091
- rtcConfig.sdpSemantics = 'unified-plan';
15092
- // @ts-ignore
15093
- rtcConfig.continualGatheringPolicy = 'gather_continually';
15094
- return rtcConfig;
14950
+ }
14951
+
14952
+ /** @internal */
14953
+ function mediaTrackToLocalTrack(mediaStreamTrack, constraints, loggerOptions) {
14954
+ switch (mediaStreamTrack.kind) {
14955
+ case 'audio':
14956
+ return new LocalAudioTrack(mediaStreamTrack, constraints, false, undefined, loggerOptions);
14957
+ case 'video':
14958
+ return new LocalVideoTrack(mediaStreamTrack, constraints, false, loggerOptions);
14959
+ default:
14960
+ throw new TrackInvalidError("unsupported track type: ".concat(mediaStreamTrack.kind));
15095
14961
  }
15096
- createDataChannels() {
15097
- if (!this.pcManager) {
15098
- return;
15099
- }
15100
- // clear old data channel callbacks if recreate
15101
- if (this.lossyDC) {
15102
- this.lossyDC.onmessage = null;
15103
- this.lossyDC.onerror = null;
14962
+ }
14963
+ /* @internal */
14964
+ const presets169 = Object.values(VideoPresets);
14965
+ /* @internal */
14966
+ const presets43 = Object.values(VideoPresets43);
14967
+ /* @internal */
14968
+ const presetsScreenShare = Object.values(ScreenSharePresets);
14969
+ /* @internal */
14970
+ const defaultSimulcastPresets169 = [VideoPresets.h180, VideoPresets.h360];
14971
+ /* @internal */
14972
+ const defaultSimulcastPresets43 = [VideoPresets43.h180, VideoPresets43.h360];
14973
+ /* @internal */
14974
+ const computeDefaultScreenShareSimulcastPresets = fromPreset => {
14975
+ const layers = [{
14976
+ scaleResolutionDownBy: 2,
14977
+ fps: fromPreset.encoding.maxFramerate
14978
+ }];
14979
+ return layers.map(t => {
14980
+ var _a, _b;
14981
+ return new VideoPreset(Math.floor(fromPreset.width / t.scaleResolutionDownBy), Math.floor(fromPreset.height / t.scaleResolutionDownBy), Math.max(150000, Math.floor(fromPreset.encoding.maxBitrate / (Math.pow(t.scaleResolutionDownBy, 2) * (((_a = fromPreset.encoding.maxFramerate) !== null && _a !== void 0 ? _a : 30) / ((_b = t.fps) !== null && _b !== void 0 ? _b : 30))))), t.fps, fromPreset.encoding.priority);
14982
+ });
14983
+ };
14984
+ // /**
14985
+ // *
14986
+ // * @internal
14987
+ // * @experimental
14988
+ // */
14989
+ // const computeDefaultMultiCodecSimulcastEncodings = (width: number, height: number) => {
14990
+ // // use vp8 as a default
14991
+ // const vp8 = determineAppropriateEncoding(false, width, height);
14992
+ // const vp9 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.9 };
14993
+ // const h264 = { ...vp8, maxBitrate: vp8.maxBitrate * 1.1 };
14994
+ // const av1 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.7 };
14995
+ // return {
14996
+ // vp8,
14997
+ // vp9,
14998
+ // h264,
14999
+ // av1,
15000
+ // };
15001
+ // };
15002
+ const videoRids = ['q', 'h', 'f'];
15003
+ /* @internal */
15004
+ function computeVideoEncodings(isScreenShare, width, height, options) {
15005
+ var _a, _b;
15006
+ let videoEncoding = options === null || options === void 0 ? void 0 : options.videoEncoding;
15007
+ if (isScreenShare) {
15008
+ videoEncoding = options === null || options === void 0 ? void 0 : options.screenShareEncoding;
15009
+ }
15010
+ const useSimulcast = options === null || options === void 0 ? void 0 : options.simulcast;
15011
+ const scalabilityMode = options === null || options === void 0 ? void 0 : options.scalabilityMode;
15012
+ const videoCodec = options === null || options === void 0 ? void 0 : options.videoCodec;
15013
+ if (!videoEncoding && !useSimulcast && !scalabilityMode || !width || !height) {
15014
+ // when we aren't simulcasting or svc, will need to return a single encoding without
15015
+ // capping bandwidth. we always require a encoding for dynacast
15016
+ return [{}];
15017
+ }
15018
+ if (!videoEncoding) {
15019
+ // find the right encoding based on width/height
15020
+ videoEncoding = determineAppropriateEncoding(isScreenShare, width, height, videoCodec);
15021
+ livekitLogger.debug('using video encoding', videoEncoding);
15022
+ }
15023
+ const original = new VideoPreset(width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate, videoEncoding.priority);
15024
+ if (scalabilityMode && isSVCCodec(videoCodec)) {
15025
+ const sm = new ScalabilityMode(scalabilityMode);
15026
+ const encodings = [];
15027
+ if (sm.spatial > 3) {
15028
+ throw new Error("unsupported scalabilityMode: ".concat(scalabilityMode));
15104
15029
  }
15105
- if (this.reliableDC) {
15106
- this.reliableDC.onmessage = null;
15107
- this.reliableDC.onerror = null;
15030
+ // Before M113 in Chrome, defining multiple encodings with an SVC codec indicated
15031
+ // that SVC mode should be used. Safari still works this way.
15032
+ // This is a bit confusing but is due to how libwebrtc interpreted the encodings field
15033
+ // before M113.
15034
+ // Announced here: https://groups.google.com/g/discuss-webrtc/c/-QQ3pxrl-fw?pli=1
15035
+ const browser = getBrowser();
15036
+ if (isSafari() || (browser === null || browser === void 0 ? void 0 : browser.name) === 'Chrome' && compareVersions(browser === null || browser === void 0 ? void 0 : browser.version, '113') < 0) {
15037
+ const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
15038
+ for (let i = 0; i < sm.spatial; i += 1) {
15039
+ // in legacy SVC, scaleResolutionDownBy cannot be set
15040
+ encodings.push({
15041
+ rid: videoRids[2 - i],
15042
+ maxBitrate: videoEncoding.maxBitrate / Math.pow(bitratesRatio, i),
15043
+ maxFramerate: original.encoding.maxFramerate
15044
+ });
15045
+ }
15046
+ // legacy SVC, scalabilityMode is set only on the first encoding
15047
+ /* @ts-ignore */
15048
+ encodings[0].scalabilityMode = scalabilityMode;
15049
+ } else {
15050
+ encodings.push({
15051
+ maxBitrate: videoEncoding.maxBitrate,
15052
+ maxFramerate: original.encoding.maxFramerate,
15053
+ /* @ts-ignore */
15054
+ scalabilityMode: scalabilityMode
15055
+ });
15108
15056
  }
15109
- // create data channels
15110
- this.lossyDC = this.pcManager.createPublisherDataChannel(lossyDataChannel, {
15111
- // will drop older packets that arrive
15112
- ordered: true,
15113
- maxRetransmits: 0
15114
- });
15115
- this.reliableDC = this.pcManager.createPublisherDataChannel(reliableDataChannel, {
15116
- ordered: true
15057
+ livekitLogger.debug("using svc encoding", {
15058
+ encodings
15117
15059
  });
15118
- // also handle messages over the pub channel, for backwards compatibility
15119
- this.lossyDC.onmessage = this.handleDataMessage;
15120
- this.reliableDC.onmessage = this.handleDataMessage;
15121
- // handle datachannel errors
15122
- this.lossyDC.onerror = this.handleDataError;
15123
- this.reliableDC.onerror = this.handleDataError;
15124
- // set up dc buffer threshold, set to 64kB (otherwise 0 by default)
15125
- this.lossyDC.bufferedAmountLowThreshold = 65535;
15126
- this.reliableDC.bufferedAmountLowThreshold = 65535;
15127
- // handle buffer amount low events
15128
- this.lossyDC.onbufferedamountlow = this.handleBufferedAmountLow;
15129
- this.reliableDC.onbufferedamountlow = this.handleBufferedAmountLow;
15060
+ return encodings;
15130
15061
  }
15131
- setPreferredCodec(transceiver, kind, videoCodec) {
15132
- if (!('getCapabilities' in RTCRtpReceiver)) {
15133
- return;
15062
+ if (!useSimulcast) {
15063
+ return [videoEncoding];
15064
+ }
15065
+ let presets = [];
15066
+ if (isScreenShare) {
15067
+ presets = (_a = sortPresets(options === null || options === void 0 ? void 0 : options.screenShareSimulcastLayers)) !== null && _a !== void 0 ? _a : defaultSimulcastLayers(isScreenShare, original);
15068
+ } else {
15069
+ presets = (_b = sortPresets(options === null || options === void 0 ? void 0 : options.videoSimulcastLayers)) !== null && _b !== void 0 ? _b : defaultSimulcastLayers(isScreenShare, original);
15070
+ }
15071
+ let midPreset;
15072
+ if (presets.length > 0) {
15073
+ const lowPreset = presets[0];
15074
+ if (presets.length > 1) {
15075
+ [, midPreset] = presets;
15134
15076
  }
15135
- // when setting codec preferences, the capabilites need to be read from the RTCRtpReceiver
15136
- const cap = RTCRtpReceiver.getCapabilities(kind);
15137
- if (!cap) return;
15138
- this.log.debug('get receiver capabilities', Object.assign(Object.assign({}, this.logContext), {
15139
- cap
15140
- }));
15141
- const matched = [];
15142
- const partialMatched = [];
15143
- const unmatched = [];
15144
- cap.codecs.forEach(c => {
15145
- const codec = c.mimeType.toLowerCase();
15146
- if (codec === 'audio/opus') {
15147
- matched.push(c);
15148
- return;
15149
- }
15150
- const matchesVideoCodec = codec === "video/".concat(videoCodec);
15151
- if (!matchesVideoCodec) {
15152
- unmatched.push(c);
15153
- return;
15154
- }
15155
- // for h264 codecs that have sdpFmtpLine available, use only if the
15156
- // profile-level-id is 42e01f for cross-browser compatibility
15157
- if (videoCodec === 'h264') {
15158
- if (c.sdpFmtpLine && c.sdpFmtpLine.includes('profile-level-id=42e01f')) {
15159
- matched.push(c);
15160
- } else {
15161
- partialMatched.push(c);
15162
- }
15163
- return;
15164
- }
15165
- matched.push(c);
15166
- });
15167
- if (supportsSetCodecPreferences(transceiver)) {
15168
- transceiver.setCodecPreferences(matched.concat(partialMatched, unmatched));
15077
+ // NOTE:
15078
+ // 1. Ordering of these encodings is important. Chrome seems
15079
+ // to use the index into encodings to decide which layer
15080
+ // to disable when CPU constrained.
15081
+ // So encodings should be ordered in increasing spatial
15082
+ // resolution order.
15083
+ // 2. livekit-server translates rids into layers. So, all encodings
15084
+ // should have the base layer `q` and then more added
15085
+ // based on other conditions.
15086
+ const size = Math.max(width, height);
15087
+ if (size >= 960 && midPreset) {
15088
+ return encodingsFromPresets(width, height, [lowPreset, midPreset, original]);
15089
+ }
15090
+ if (size >= 480) {
15091
+ return encodingsFromPresets(width, height, [lowPreset, original]);
15169
15092
  }
15170
15093
  }
15171
- createSender(track, opts, encodings) {
15172
- return __awaiter(this, void 0, void 0, function* () {
15173
- if (supportsTransceiver()) {
15174
- const sender = yield this.createTransceiverRTCRtpSender(track, opts, encodings);
15175
- return sender;
15176
- }
15177
- if (supportsAddTrack()) {
15178
- this.log.warn('using add-track fallback', this.logContext);
15179
- const sender = yield this.createRTCRtpSender(track.mediaStreamTrack);
15180
- return sender;
15181
- }
15182
- throw new UnexpectedConnectionState('Required webRTC APIs not supported on this device');
15183
- });
15184
- }
15185
- createSimulcastSender(track, simulcastTrack, opts, encodings) {
15186
- return __awaiter(this, void 0, void 0, function* () {
15187
- // store RTCRtpSender
15188
- if (supportsTransceiver()) {
15189
- return this.createSimulcastTransceiverSender(track, simulcastTrack, opts, encodings);
15190
- }
15191
- if (supportsAddTrack()) {
15192
- this.log.debug('using add-track fallback', this.logContext);
15193
- return this.createRTCRtpSender(track.mediaStreamTrack);
15194
- }
15195
- throw new UnexpectedConnectionState('Cannot stream on this device');
15196
- });
15094
+ return encodingsFromPresets(width, height, [original]);
15095
+ }
15096
+ function computeTrackBackupEncodings(track, videoCodec, opts) {
15097
+ var _a, _b, _c, _d;
15098
+ // backupCodec should not be true anymore, default codec is set in LocalParticipant.publish
15099
+ if (!opts.backupCodec || opts.backupCodec === true || opts.backupCodec.codec === opts.videoCodec) {
15100
+ // backup codec publishing is disabled
15101
+ return;
15197
15102
  }
15198
- createTransceiverRTCRtpSender(track, opts, encodings) {
15199
- return __awaiter(this, void 0, void 0, function* () {
15200
- if (!this.pcManager) {
15201
- throw new UnexpectedConnectionState('publisher is closed');
15202
- }
15203
- const streams = [];
15204
- if (track.mediaStream) {
15205
- streams.push(track.mediaStream);
15206
- }
15207
- const transceiverInit = {
15208
- direction: 'sendonly',
15209
- streams
15210
- };
15211
- if (encodings) {
15212
- transceiverInit.sendEncodings = encodings;
15213
- }
15214
- // addTransceiver for react-native is async. web is synchronous, but await won't effect it.
15215
- const transceiver = yield this.pcManager.addPublisherTransceiver(track.mediaStreamTrack, transceiverInit);
15216
- if (track.kind === Track.Kind.Video && opts.videoCodec) {
15217
- this.setPreferredCodec(transceiver, track.kind, opts.videoCodec);
15218
- track.codec = opts.videoCodec;
15219
- }
15220
- return transceiver.sender;
15103
+ if (videoCodec !== opts.backupCodec.codec) {
15104
+ livekitLogger.warn('requested a different codec than specified as backup', {
15105
+ serverRequested: videoCodec,
15106
+ backup: opts.backupCodec.codec
15221
15107
  });
15222
15108
  }
15223
- createSimulcastTransceiverSender(track, simulcastTrack, opts, encodings) {
15224
- return __awaiter(this, void 0, void 0, function* () {
15225
- if (!this.pcManager) {
15226
- throw new UnexpectedConnectionState('publisher is closed');
15227
- }
15228
- const transceiverInit = {
15229
- direction: 'sendonly'
15230
- };
15231
- if (encodings) {
15232
- transceiverInit.sendEncodings = encodings;
15233
- }
15234
- // addTransceiver for react-native is async. web is synchronous, but await won't effect it.
15235
- const transceiver = yield this.pcManager.addPublisherTransceiver(simulcastTrack.mediaStreamTrack, transceiverInit);
15236
- if (!opts.videoCodec) {
15237
- return;
15109
+ opts.videoCodec = videoCodec;
15110
+ // use backup encoding setting as videoEncoding for backup codec publishing
15111
+ opts.videoEncoding = opts.backupCodec.encoding;
15112
+ const settings = track.mediaStreamTrack.getSettings();
15113
+ const width = (_a = settings.width) !== null && _a !== void 0 ? _a : (_b = track.dimensions) === null || _b === void 0 ? void 0 : _b.width;
15114
+ const height = (_c = settings.height) !== null && _c !== void 0 ? _c : (_d = track.dimensions) === null || _d === void 0 ? void 0 : _d.height;
15115
+ const encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, width, height, opts);
15116
+ return encodings;
15117
+ }
15118
+ /* @internal */
15119
+ function determineAppropriateEncoding(isScreenShare, width, height, codec) {
15120
+ const presets = presetsForResolution(isScreenShare, width, height);
15121
+ let {
15122
+ encoding
15123
+ } = presets[0];
15124
+ // handle portrait by swapping dimensions
15125
+ const size = Math.max(width, height);
15126
+ for (let i = 0; i < presets.length; i += 1) {
15127
+ const preset = presets[i];
15128
+ encoding = preset.encoding;
15129
+ if (preset.width >= size) {
15130
+ break;
15131
+ }
15132
+ }
15133
+ // presets are based on the assumption of vp8 as a codec
15134
+ // for other codecs we adjust the maxBitrate if no specific videoEncoding has been provided
15135
+ // users should override these with ones that are optimized for their use case
15136
+ // NOTE: SVC codec bitrates are inclusive of all scalability layers. while
15137
+ // bitrate for non-SVC codecs does not include other simulcast layers.
15138
+ if (codec) {
15139
+ switch (codec) {
15140
+ case 'av1':
15141
+ encoding = Object.assign({}, encoding);
15142
+ encoding.maxBitrate = encoding.maxBitrate * 0.7;
15143
+ break;
15144
+ case 'vp9':
15145
+ encoding = Object.assign({}, encoding);
15146
+ encoding.maxBitrate = encoding.maxBitrate * 0.85;
15147
+ break;
15148
+ }
15149
+ }
15150
+ return encoding;
15151
+ }
15152
+ /* @internal */
15153
+ function presetsForResolution(isScreenShare, width, height) {
15154
+ if (isScreenShare) {
15155
+ return presetsScreenShare;
15156
+ }
15157
+ const aspect = width > height ? width / height : height / width;
15158
+ if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
15159
+ return presets169;
15160
+ }
15161
+ return presets43;
15162
+ }
15163
+ /* @internal */
15164
+ function defaultSimulcastLayers(isScreenShare, original) {
15165
+ if (isScreenShare) {
15166
+ return computeDefaultScreenShareSimulcastPresets(original);
15167
+ }
15168
+ const {
15169
+ width,
15170
+ height
15171
+ } = original;
15172
+ const aspect = width > height ? width / height : height / width;
15173
+ if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
15174
+ return defaultSimulcastPresets169;
15175
+ }
15176
+ return defaultSimulcastPresets43;
15177
+ }
15178
+ // presets should be ordered by low, medium, high
15179
+ function encodingsFromPresets(width, height, presets) {
15180
+ const encodings = [];
15181
+ presets.forEach((preset, idx) => {
15182
+ if (idx >= videoRids.length) {
15183
+ return;
15184
+ }
15185
+ const size = Math.min(width, height);
15186
+ const rid = videoRids[idx];
15187
+ const encoding = {
15188
+ rid,
15189
+ scaleResolutionDownBy: Math.max(1, size / Math.min(preset.width, preset.height)),
15190
+ maxBitrate: preset.encoding.maxBitrate
15191
+ };
15192
+ if (preset.encoding.maxFramerate) {
15193
+ encoding.maxFramerate = preset.encoding.maxFramerate;
15194
+ }
15195
+ const canSetPriority = isFireFox() || idx === 0;
15196
+ if (preset.encoding.priority && canSetPriority) {
15197
+ encoding.priority = preset.encoding.priority;
15198
+ encoding.networkPriority = preset.encoding.priority;
15199
+ }
15200
+ encodings.push(encoding);
15201
+ });
15202
+ // RN ios simulcast requires all same framerates.
15203
+ if (isReactNative() && getReactNativeOs() === 'ios') {
15204
+ let topFramerate = undefined;
15205
+ encodings.forEach(encoding => {
15206
+ if (!topFramerate) {
15207
+ topFramerate = encoding.maxFramerate;
15208
+ } else if (encoding.maxFramerate && encoding.maxFramerate > topFramerate) {
15209
+ topFramerate = encoding.maxFramerate;
15238
15210
  }
15239
- this.setPreferredCodec(transceiver, track.kind, opts.videoCodec);
15240
- track.setSimulcastTrackSender(opts.videoCodec, transceiver.sender);
15241
- return transceiver.sender;
15242
15211
  });
15243
- }
15244
- createRTCRtpSender(track) {
15245
- return __awaiter(this, void 0, void 0, function* () {
15246
- if (!this.pcManager) {
15247
- throw new UnexpectedConnectionState('publisher is closed');
15212
+ let notifyOnce = true;
15213
+ encodings.forEach(encoding => {
15214
+ var _a;
15215
+ if (encoding.maxFramerate != topFramerate) {
15216
+ if (notifyOnce) {
15217
+ notifyOnce = false;
15218
+ livekitLogger.info("Simulcast on iOS React-Native requires all encodings to share the same framerate.");
15219
+ }
15220
+ livekitLogger.info("Setting framerate of encoding \"".concat((_a = encoding.rid) !== null && _a !== void 0 ? _a : '', "\" to ").concat(topFramerate));
15221
+ encoding.maxFramerate = topFramerate;
15248
15222
  }
15249
- return this.pcManager.addPublisherTrack(track);
15250
15223
  });
15251
15224
  }
15252
- attemptReconnect(reason) {
15253
- return __awaiter(this, void 0, void 0, function* () {
15254
- var _a, _b, _c;
15255
- if (this._isClosed) {
15256
- return;
15225
+ return encodings;
15226
+ }
15227
+ /** @internal */
15228
+ function sortPresets(presets) {
15229
+ if (!presets) return;
15230
+ return presets.sort((a, b) => {
15231
+ const {
15232
+ encoding: aEnc
15233
+ } = a;
15234
+ const {
15235
+ encoding: bEnc
15236
+ } = b;
15237
+ if (aEnc.maxBitrate > bEnc.maxBitrate) {
15238
+ return 1;
15239
+ }
15240
+ if (aEnc.maxBitrate < bEnc.maxBitrate) return -1;
15241
+ if (aEnc.maxBitrate === bEnc.maxBitrate && aEnc.maxFramerate && bEnc.maxFramerate) {
15242
+ return aEnc.maxFramerate > bEnc.maxFramerate ? 1 : -1;
15243
+ }
15244
+ return 0;
15245
+ });
15246
+ }
15247
+ /** @internal */
15248
+ class ScalabilityMode {
15249
+ constructor(scalabilityMode) {
15250
+ const results = scalabilityMode.match(/^L(\d)T(\d)(h|_KEY|_KEY_SHIFT){0,1}$/);
15251
+ if (!results) {
15252
+ throw new Error('invalid scalability mode');
15253
+ }
15254
+ this.spatial = parseInt(results[1]);
15255
+ this.temporal = parseInt(results[2]);
15256
+ if (results.length > 3) {
15257
+ switch (results[3]) {
15258
+ case 'h':
15259
+ case '_KEY':
15260
+ case '_KEY_SHIFT':
15261
+ this.suffix = results[3];
15257
15262
  }
15258
- // guard for attempting reconnection multiple times while one attempt is still not finished
15259
- if (this.attemptingReconnect) {
15260
- livekitLogger.warn('already attempting reconnect, returning early', this.logContext);
15263
+ }
15264
+ }
15265
+ toString() {
15266
+ var _a;
15267
+ return "L".concat(this.spatial, "T").concat(this.temporal).concat((_a = this.suffix) !== null && _a !== void 0 ? _a : '');
15268
+ }
15269
+ }
15270
+
15271
+ const refreshSubscribedCodecAfterNewCodec = 5000;
15272
+ class LocalVideoTrack extends LocalTrack {
15273
+ /**
15274
+ *
15275
+ * @param mediaTrack
15276
+ * @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks
15277
+ * @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK
15278
+ */
15279
+ constructor(mediaTrack, constraints) {
15280
+ let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
15281
+ let loggerOptions = arguments.length > 3 ? arguments[3] : undefined;
15282
+ super(mediaTrack, Track.Kind.Video, constraints, userProvidedTrack, loggerOptions);
15283
+ /* @internal */
15284
+ this.simulcastCodecs = new Map();
15285
+ this.monitorSender = () => __awaiter(this, void 0, void 0, function* () {
15286
+ if (!this.sender) {
15287
+ this._currentBitrate = 0;
15261
15288
  return;
15262
15289
  }
15263
- if (((_a = this.clientConfiguration) === null || _a === void 0 ? void 0 : _a.resumeConnection) === ClientConfigSetting.DISABLED ||
15264
- // signaling state could change to closed due to hardware sleep
15265
- // those connections cannot be resumed
15266
- ((_c = (_b = this.pcManager) === null || _b === void 0 ? void 0 : _b.currentState) !== null && _c !== void 0 ? _c : PCTransportState.NEW) === PCTransportState.NEW) {
15267
- this.fullReconnectOnNext = true;
15268
- }
15290
+ let stats;
15269
15291
  try {
15270
- this.attemptingReconnect = true;
15271
- if (this.fullReconnectOnNext) {
15272
- yield this.restartConnection();
15273
- } else {
15274
- yield this.resumeConnection(reason);
15275
- }
15276
- this.clearPendingReconnect();
15277
- this.fullReconnectOnNext = false;
15292
+ stats = yield this.getSenderStats();
15278
15293
  } catch (e) {
15279
- this.reconnectAttempts += 1;
15280
- let recoverable = true;
15281
- if (e instanceof UnexpectedConnectionState) {
15282
- this.log.debug('received unrecoverable error', Object.assign(Object.assign({}, this.logContext), {
15283
- error: e
15284
- }));
15285
- // unrecoverable
15286
- recoverable = false;
15287
- } else if (!(e instanceof SignalReconnectError)) {
15288
- // cannot resume
15289
- this.fullReconnectOnNext = true;
15290
- }
15291
- if (recoverable) {
15292
- this.handleDisconnect('reconnect', ReconnectReason.RR_UNKNOWN);
15293
- } else {
15294
- this.log.info("could not recover connection after ".concat(this.reconnectAttempts, " attempts, ").concat(Date.now() - this.reconnectStart, "ms. giving up"), this.logContext);
15295
- this.emit(EngineEvent.Disconnected);
15296
- yield this.close();
15297
- }
15298
- } finally {
15299
- this.attemptingReconnect = false;
15294
+ this.log.error('could not get audio sender stats', Object.assign(Object.assign({}, this.logContext), {
15295
+ error: e
15296
+ }));
15297
+ return;
15298
+ }
15299
+ const statsMap = new Map(stats.map(s => [s.rid, s]));
15300
+ if (this.prevStats) {
15301
+ let totalBitrate = 0;
15302
+ statsMap.forEach((s, key) => {
15303
+ var _a;
15304
+ const prev = (_a = this.prevStats) === null || _a === void 0 ? void 0 : _a.get(key);
15305
+ totalBitrate += computeBitrate(s, prev);
15306
+ });
15307
+ this._currentBitrate = totalBitrate;
15300
15308
  }
15309
+ this.prevStats = statsMap;
15301
15310
  });
15311
+ this.senderLock = new Mutex();
15302
15312
  }
15303
- getNextRetryDelay(context) {
15304
- try {
15305
- return this.reconnectPolicy.nextRetryDelayInMs(context);
15306
- } catch (e) {
15307
- this.log.warn('encountered error in reconnect policy', Object.assign(Object.assign({}, this.logContext), {
15308
- error: e
15309
- }));
15313
+ get isSimulcast() {
15314
+ if (this.sender && this.sender.getParameters().encodings.length > 1) {
15315
+ return true;
15310
15316
  }
15311
- // error in user code with provided reconnect policy, stop reconnecting
15312
- return null;
15317
+ return false;
15313
15318
  }
15314
- restartConnection(regionUrl) {
15319
+ /* @internal */
15320
+ startMonitor(signalClient) {
15321
+ var _a;
15322
+ this.signalClient = signalClient;
15323
+ if (!isWeb()) {
15324
+ return;
15325
+ }
15326
+ // save original encodings
15327
+ // TODO : merge simulcast tracks stats
15328
+ const params = (_a = this.sender) === null || _a === void 0 ? void 0 : _a.getParameters();
15329
+ if (params) {
15330
+ this.encodings = params.encodings;
15331
+ }
15332
+ if (this.monitorInterval) {
15333
+ return;
15334
+ }
15335
+ this.monitorInterval = setInterval(() => {
15336
+ this.monitorSender();
15337
+ }, monitorFrequency);
15338
+ }
15339
+ stop() {
15340
+ this._mediaStreamTrack.getConstraints();
15341
+ this.simulcastCodecs.forEach(trackInfo => {
15342
+ trackInfo.mediaStreamTrack.stop();
15343
+ });
15344
+ super.stop();
15345
+ }
15346
+ pauseUpstream() {
15347
+ const _super = Object.create(null, {
15348
+ pauseUpstream: {
15349
+ get: () => super.pauseUpstream
15350
+ }
15351
+ });
15315
15352
  return __awaiter(this, void 0, void 0, function* () {
15316
- var _a, _b, _c;
15353
+ var _a, e_1, _b, _c;
15354
+ var _d;
15355
+ yield _super.pauseUpstream.call(this);
15317
15356
  try {
15318
- if (!this.url || !this.token) {
15319
- // permanent failure, don't attempt reconnection
15320
- throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
15321
- }
15322
- this.log.info("reconnecting, attempt: ".concat(this.reconnectAttempts), this.logContext);
15323
- this.emit(EngineEvent.Restarting);
15324
- if (!this.client.isDisconnected) {
15325
- yield this.client.sendLeave();
15357
+ for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
15358
+ _c = _g.value;
15359
+ _e = false;
15360
+ const sc = _c;
15361
+ yield (_d = sc.sender) === null || _d === void 0 ? void 0 : _d.replaceTrack(null);
15326
15362
  }
15327
- yield this.cleanupPeerConnections();
15328
- yield this.cleanupClient();
15329
- let joinResponse;
15363
+ } catch (e_1_1) {
15364
+ e_1 = {
15365
+ error: e_1_1
15366
+ };
15367
+ } finally {
15330
15368
  try {
15331
- if (!this.signalOpts) {
15332
- this.log.warn('attempted connection restart, without signal options present', this.logContext);
15333
- throw new SignalReconnectError();
15334
- }
15335
- // in case a regionUrl is passed, the region URL takes precedence
15336
- joinResponse = yield this.join(regionUrl !== null && regionUrl !== void 0 ? regionUrl : this.url, this.token, this.signalOpts);
15337
- } catch (e) {
15338
- if (e instanceof ConnectionError && e.reason === 0 /* ConnectionErrorReason.NotAllowed */) {
15339
- throw new UnexpectedConnectionState('could not reconnect, token might be expired');
15340
- }
15341
- throw new SignalReconnectError();
15342
- }
15343
- if (this.shouldFailNext) {
15344
- this.shouldFailNext = false;
15345
- throw new Error('simulated failure');
15346
- }
15347
- this.client.setReconnected();
15348
- this.emit(EngineEvent.SignalRestarted, joinResponse);
15349
- yield this.waitForPCReconnected();
15350
- // re-check signal connection state before setting engine as resumed
15351
- if (this.client.currentState !== SignalConnectionState.CONNECTED) {
15352
- throw new SignalReconnectError('Signal connection got severed during reconnect');
15353
- }
15354
- (_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.resetAttempts();
15355
- // reconnect success
15356
- this.emit(EngineEvent.Restarted);
15357
- } catch (error) {
15358
- const nextRegionUrl = yield (_b = this.regionUrlProvider) === null || _b === void 0 ? void 0 : _b.getNextBestRegionUrl();
15359
- if (nextRegionUrl) {
15360
- yield this.restartConnection(nextRegionUrl);
15361
- return;
15362
- } else {
15363
- // no more regions to try (or we're not on cloud)
15364
- (_c = this.regionUrlProvider) === null || _c === void 0 ? void 0 : _c.resetAttempts();
15365
- throw error;
15369
+ if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
15370
+ } finally {
15371
+ if (e_1) throw e_1.error;
15366
15372
  }
15367
15373
  }
15368
15374
  });
15369
15375
  }
15370
- resumeConnection(reason) {
15371
- return __awaiter(this, void 0, void 0, function* () {
15372
- var _a;
15373
- if (!this.url || !this.token) {
15374
- // permanent failure, don't attempt reconnection
15375
- throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
15376
- }
15377
- // trigger publisher reconnect
15378
- if (!this.pcManager) {
15379
- throw new UnexpectedConnectionState('publisher and subscriber connections unset');
15376
+ resumeUpstream() {
15377
+ const _super = Object.create(null, {
15378
+ resumeUpstream: {
15379
+ get: () => super.resumeUpstream
15380
15380
  }
15381
- this.log.info("resuming signal connection, attempt ".concat(this.reconnectAttempts), this.logContext);
15382
- this.emit(EngineEvent.Resuming);
15383
- let res;
15381
+ });
15382
+ return __awaiter(this, void 0, void 0, function* () {
15383
+ var _a, e_2, _b, _c;
15384
+ var _d;
15385
+ yield _super.resumeUpstream.call(this);
15384
15386
  try {
15385
- this.setupSignalClientCallbacks();
15386
- res = yield this.client.reconnect(this.url, this.token, this.participantSid, reason);
15387
- } catch (error) {
15388
- let message = '';
15389
- if (error instanceof Error) {
15390
- message = error.message;
15391
- this.log.error(error.message, Object.assign(Object.assign({}, this.logContext), {
15392
- error
15393
- }));
15394
- }
15395
- if (error instanceof ConnectionError && error.reason === 0 /* ConnectionErrorReason.NotAllowed */) {
15396
- throw new UnexpectedConnectionState('could not reconnect, token might be expired');
15387
+ for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
15388
+ _c = _g.value;
15389
+ _e = false;
15390
+ const sc = _c;
15391
+ yield (_d = sc.sender) === null || _d === void 0 ? void 0 : _d.replaceTrack(sc.mediaStreamTrack);
15397
15392
  }
15398
- if (error instanceof ConnectionError && error.reason === 4 /* ConnectionErrorReason.LeaveRequest */) {
15399
- throw error;
15393
+ } catch (e_2_1) {
15394
+ e_2 = {
15395
+ error: e_2_1
15396
+ };
15397
+ } finally {
15398
+ try {
15399
+ if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
15400
+ } finally {
15401
+ if (e_2) throw e_2.error;
15400
15402
  }
15401
- throw new SignalReconnectError(message);
15402
- }
15403
- this.emit(EngineEvent.SignalResumed);
15404
- if (res) {
15405
- const rtcConfig = this.makeRTCConfiguration(res);
15406
- this.pcManager.updateConfiguration(rtcConfig);
15407
- } else {
15408
- this.log.warn('Did not receive reconnect response', this.logContext);
15409
- }
15410
- if (this.shouldFailNext) {
15411
- this.shouldFailNext = false;
15412
- throw new Error('simulated failure');
15413
- }
15414
- yield this.pcManager.triggerIceRestart();
15415
- yield this.waitForPCReconnected();
15416
- // re-check signal connection state before setting engine as resumed
15417
- if (this.client.currentState !== SignalConnectionState.CONNECTED) {
15418
- throw new SignalReconnectError('Signal connection got severed during reconnect');
15419
- }
15420
- this.client.setReconnected();
15421
- // recreate publish datachannel if it's id is null
15422
- // (for safari https://bugs.webkit.org/show_bug.cgi?id=184688)
15423
- if (((_a = this.reliableDC) === null || _a === void 0 ? void 0 : _a.readyState) === 'open' && this.reliableDC.id === null) {
15424
- this.createDataChannels();
15425
15403
  }
15426
- // resume success
15427
- this.emit(EngineEvent.Resumed);
15428
15404
  });
15429
15405
  }
15430
- waitForPCInitialConnection(timeout, abortController) {
15431
- return __awaiter(this, void 0, void 0, function* () {
15432
- if (!this.pcManager) {
15433
- throw new UnexpectedConnectionState('PC manager is closed');
15406
+ mute() {
15407
+ const _super = Object.create(null, {
15408
+ mute: {
15409
+ get: () => super.mute
15434
15410
  }
15435
- yield this.pcManager.ensurePCTransportConnection(abortController, timeout);
15436
15411
  });
15437
- }
15438
- waitForPCReconnected() {
15439
15412
  return __awaiter(this, void 0, void 0, function* () {
15440
- this.pcState = PCState.Reconnecting;
15441
- this.log.debug('waiting for peer connection to reconnect', this.logContext);
15413
+ const unlock = yield this.muteLock.lock();
15442
15414
  try {
15443
- yield sleep(minReconnectWait); // FIXME setTimeout again not ideal for a connection critical path
15444
- if (!this.pcManager) {
15445
- throw new UnexpectedConnectionState('PC manager is closed');
15415
+ if (this.isMuted) {
15416
+ this.log.debug('Track already muted', this.logContext);
15417
+ return this;
15446
15418
  }
15447
- yield this.pcManager.ensurePCTransportConnection(undefined, this.peerConnectionTimeout);
15448
- this.pcState = PCState.Connected;
15449
- } catch (e) {
15450
- // TODO do we need a `failed` state here for the PC?
15451
- this.pcState = PCState.Disconnected;
15452
- throw new ConnectionError("could not establish PC connection, ".concat(e.message));
15419
+ if (this.source === Track.Source.Camera && !this.isUserProvided) {
15420
+ this.log.debug('stopping camera track', this.logContext);
15421
+ // also stop the track, so that camera indicator is turned off
15422
+ this._mediaStreamTrack.stop();
15423
+ }
15424
+ yield _super.mute.call(this);
15425
+ return this;
15426
+ } finally {
15427
+ unlock();
15453
15428
  }
15454
15429
  });
15455
15430
  }
15456
- /* @internal */
15457
- sendDataPacket(packet, kind) {
15458
- return __awaiter(this, void 0, void 0, function* () {
15459
- const msg = packet.toBinary();
15460
- // make sure we do have a data connection
15461
- yield this.ensurePublisherConnected(kind);
15462
- const dc = this.dataChannelForKind(kind);
15463
- if (dc) {
15464
- dc.send(msg);
15431
+ unmute() {
15432
+ const _super = Object.create(null, {
15433
+ unmute: {
15434
+ get: () => super.unmute
15465
15435
  }
15466
- this.updateAndEmitDCBufferStatus(kind);
15467
15436
  });
15468
- }
15469
- /**
15470
- * @internal
15471
- */
15472
- ensureDataTransportConnected(kind_1) {
15473
- return __awaiter(this, arguments, void 0, function (kind) {
15474
- var _this2 = this;
15475
- let subscriber = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.subscriberPrimary;
15476
- return function* () {
15477
- var _a;
15478
- if (!_this2.pcManager) {
15479
- throw new UnexpectedConnectionState('PC manager is closed');
15437
+ return __awaiter(this, void 0, void 0, function* () {
15438
+ const unlock = yield this.muteLock.lock();
15439
+ try {
15440
+ if (!this.isMuted) {
15441
+ this.log.debug('Track already unmuted', this.logContext);
15442
+ return this;
15480
15443
  }
15481
- const transport = subscriber ? _this2.pcManager.subscriber : _this2.pcManager.publisher;
15482
- const transportName = subscriber ? 'Subscriber' : 'Publisher';
15483
- if (!transport) {
15484
- throw new ConnectionError("".concat(transportName, " connection not set"));
15444
+ if (this.source === Track.Source.Camera && !this.isUserProvided) {
15445
+ this.log.debug('reacquiring camera track', this.logContext);
15446
+ yield this.restartTrack();
15485
15447
  }
15486
- if (!subscriber && !_this2.pcManager.publisher.isICEConnected && _this2.pcManager.publisher.getICEConnectionState() !== 'checking') {
15487
- // start negotiation
15488
- _this2.negotiate();
15489
- }
15490
- const targetChannel = _this2.dataChannelForKind(kind, subscriber);
15491
- if ((targetChannel === null || targetChannel === void 0 ? void 0 : targetChannel.readyState) === 'open') {
15492
- return;
15493
- }
15494
- // wait until ICE connected
15495
- const endTime = new Date().getTime() + _this2.peerConnectionTimeout;
15496
- while (new Date().getTime() < endTime) {
15497
- if (transport.isICEConnected && ((_a = _this2.dataChannelForKind(kind, subscriber)) === null || _a === void 0 ? void 0 : _a.readyState) === 'open') {
15498
- return;
15499
- }
15500
- yield sleep(50);
15501
- }
15502
- throw new ConnectionError("could not establish ".concat(transportName, " connection, state: ").concat(transport.getICEConnectionState()));
15503
- }();
15448
+ yield _super.unmute.call(this);
15449
+ return this;
15450
+ } finally {
15451
+ unlock();
15452
+ }
15504
15453
  });
15505
15454
  }
15506
- ensurePublisherConnected(kind) {
15455
+ setTrackMuted(muted) {
15456
+ super.setTrackMuted(muted);
15457
+ for (const sc of this.simulcastCodecs.values()) {
15458
+ sc.mediaStreamTrack.enabled = !muted;
15459
+ }
15460
+ }
15461
+ getSenderStats() {
15507
15462
  return __awaiter(this, void 0, void 0, function* () {
15508
- if (!this.publisherConnectionPromise) {
15509
- this.publisherConnectionPromise = this.ensureDataTransportConnected(kind, false);
15463
+ var _a;
15464
+ if (!((_a = this.sender) === null || _a === void 0 ? void 0 : _a.getStats)) {
15465
+ return [];
15510
15466
  }
15511
- yield this.publisherConnectionPromise;
15467
+ const items = [];
15468
+ const stats = yield this.sender.getStats();
15469
+ stats.forEach(v => {
15470
+ var _a;
15471
+ if (v.type === 'outbound-rtp') {
15472
+ const vs = {
15473
+ type: 'video',
15474
+ streamId: v.id,
15475
+ frameHeight: v.frameHeight,
15476
+ frameWidth: v.frameWidth,
15477
+ framesPerSecond: v.framesPerSecond,
15478
+ framesSent: v.framesSent,
15479
+ firCount: v.firCount,
15480
+ pliCount: v.pliCount,
15481
+ nackCount: v.nackCount,
15482
+ packetsSent: v.packetsSent,
15483
+ bytesSent: v.bytesSent,
15484
+ qualityLimitationReason: v.qualityLimitationReason,
15485
+ qualityLimitationDurations: v.qualityLimitationDurations,
15486
+ qualityLimitationResolutionChanges: v.qualityLimitationResolutionChanges,
15487
+ rid: (_a = v.rid) !== null && _a !== void 0 ? _a : v.id,
15488
+ retransmittedPacketsSent: v.retransmittedPacketsSent,
15489
+ targetBitrate: v.targetBitrate,
15490
+ timestamp: v.timestamp
15491
+ };
15492
+ // locate the appropriate remote-inbound-rtp item
15493
+ const r = stats.get(v.remoteId);
15494
+ if (r) {
15495
+ vs.jitter = r.jitter;
15496
+ vs.packetsLost = r.packetsLost;
15497
+ vs.roundTripTime = r.roundTripTime;
15498
+ }
15499
+ items.push(vs);
15500
+ }
15501
+ });
15502
+ // make sure highest res layer is always first
15503
+ items.sort((a, b) => {
15504
+ var _a, _b;
15505
+ return ((_a = b.frameWidth) !== null && _a !== void 0 ? _a : 0) - ((_b = a.frameWidth) !== null && _b !== void 0 ? _b : 0);
15506
+ });
15507
+ return items;
15512
15508
  });
15513
15509
  }
15514
- /* @internal */
15515
- verifyTransport() {
15516
- if (!this.pcManager) {
15517
- return false;
15518
- }
15519
- // primary connection
15520
- if (this.pcManager.currentState !== PCTransportState.CONNECTED) {
15521
- return false;
15522
- }
15523
- // ensure signal is connected
15524
- if (!this.client.ws || this.client.ws.readyState === WebSocket.CLOSED) {
15525
- return false;
15510
+ setPublishingQuality(maxQuality) {
15511
+ const qualities = [];
15512
+ for (let q = VideoQuality.LOW; q <= VideoQuality.HIGH; q += 1) {
15513
+ qualities.push(new SubscribedQuality({
15514
+ quality: q,
15515
+ enabled: q <= maxQuality
15516
+ }));
15526
15517
  }
15527
- return true;
15518
+ this.log.debug("setting publishing quality. max quality ".concat(maxQuality), this.logContext);
15519
+ this.setPublishingLayers(qualities);
15528
15520
  }
15529
- /** @internal */
15530
- negotiate() {
15521
+ setDeviceId(deviceId) {
15531
15522
  return __awaiter(this, void 0, void 0, function* () {
15532
- // observe signal state
15533
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
15534
- if (!this.pcManager) {
15535
- reject(new NegotiationError('PC manager is closed'));
15536
- return;
15523
+ if (this._constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)) {
15524
+ return true;
15525
+ }
15526
+ this._constraints.deviceId = deviceId;
15527
+ // when video is muted, underlying media stream track is stopped and
15528
+ // will be restarted later
15529
+ if (!this.isMuted) {
15530
+ yield this.restartTrack();
15531
+ }
15532
+ return this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
15533
+ });
15534
+ }
15535
+ restartTrack(options) {
15536
+ return __awaiter(this, void 0, void 0, function* () {
15537
+ var _a, e_3, _b, _c;
15538
+ let constraints;
15539
+ if (options) {
15540
+ const streamConstraints = constraintsForOptions({
15541
+ video: options
15542
+ });
15543
+ if (typeof streamConstraints.video !== 'boolean') {
15544
+ constraints = streamConstraints.video;
15537
15545
  }
15538
- this.pcManager.requirePublisher();
15539
- const abortController = new AbortController();
15540
- const handleClosed = () => {
15541
- abortController.abort();
15542
- this.log.debug('engine disconnected while negotiation was ongoing', this.logContext);
15543
- resolve();
15544
- return;
15545
- };
15546
- if (this.isClosed) {
15547
- reject('cannot negotiate on closed engine');
15546
+ }
15547
+ yield this.restart(constraints);
15548
+ try {
15549
+ for (var _d = true, _e = __asyncValues(this.simulcastCodecs.values()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
15550
+ _c = _f.value;
15551
+ _d = false;
15552
+ const sc = _c;
15553
+ if (sc.sender) {
15554
+ sc.mediaStreamTrack = this.mediaStreamTrack.clone();
15555
+ yield sc.sender.replaceTrack(sc.mediaStreamTrack);
15556
+ }
15548
15557
  }
15549
- this.on(EngineEvent.Closing, handleClosed);
15550
- this.pcManager.publisher.once(PCEvents.RTPVideoPayloadTypes, rtpTypes => {
15551
- const rtpMap = new Map();
15552
- rtpTypes.forEach(rtp => {
15553
- const codec = rtp.codec.toLowerCase();
15554
- if (isVideoCodec(codec)) {
15555
- rtpMap.set(rtp.payload, codec);
15556
- }
15557
- });
15558
- this.emit(EngineEvent.RTPVideoMapUpdate, rtpMap);
15559
- });
15558
+ } catch (e_3_1) {
15559
+ e_3 = {
15560
+ error: e_3_1
15561
+ };
15562
+ } finally {
15560
15563
  try {
15561
- yield this.pcManager.negotiate(abortController);
15562
- resolve();
15563
- } catch (e) {
15564
- if (e instanceof NegotiationError) {
15565
- this.fullReconnectOnNext = true;
15566
- }
15567
- this.handleDisconnect('negotiation', ReconnectReason.RR_UNKNOWN);
15568
- reject(e);
15564
+ if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
15569
15565
  } finally {
15570
- this.off(EngineEvent.Closing, handleClosed);
15566
+ if (e_3) throw e_3.error;
15571
15567
  }
15572
- }));
15568
+ }
15573
15569
  });
15574
15570
  }
15575
- dataChannelForKind(kind, sub) {
15576
- if (!sub) {
15577
- if (kind === DataPacket_Kind.LOSSY) {
15578
- return this.lossyDC;
15579
- }
15580
- if (kind === DataPacket_Kind.RELIABLE) {
15581
- return this.reliableDC;
15582
- }
15583
- } else {
15584
- if (kind === DataPacket_Kind.LOSSY) {
15585
- return this.lossyDCSub;
15586
- }
15587
- if (kind === DataPacket_Kind.RELIABLE) {
15588
- return this.reliableDCSub;
15571
+ setProcessor(processor_1) {
15572
+ const _super = Object.create(null, {
15573
+ setProcessor: {
15574
+ get: () => super.setProcessor
15589
15575
  }
15576
+ });
15577
+ return __awaiter(this, arguments, void 0, function (processor) {
15578
+ var _this = this;
15579
+ let showProcessedStreamLocally = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
15580
+ return function* () {
15581
+ var _a, e_4, _b, _c;
15582
+ var _d, _e;
15583
+ yield _super.setProcessor.call(_this, processor, showProcessedStreamLocally);
15584
+ if ((_d = _this.processor) === null || _d === void 0 ? void 0 : _d.processedTrack) {
15585
+ try {
15586
+ for (var _f = true, _g = __asyncValues(_this.simulcastCodecs.values()), _h; _h = yield _g.next(), _a = _h.done, !_a; _f = true) {
15587
+ _c = _h.value;
15588
+ _f = false;
15589
+ const sc = _c;
15590
+ yield (_e = sc.sender) === null || _e === void 0 ? void 0 : _e.replaceTrack(_this.processor.processedTrack);
15591
+ }
15592
+ } catch (e_4_1) {
15593
+ e_4 = {
15594
+ error: e_4_1
15595
+ };
15596
+ } finally {
15597
+ try {
15598
+ if (!_f && !_a && (_b = _g.return)) yield _b.call(_g);
15599
+ } finally {
15600
+ if (e_4) throw e_4.error;
15601
+ }
15602
+ }
15603
+ }
15604
+ }();
15605
+ });
15606
+ }
15607
+ addSimulcastTrack(codec, encodings) {
15608
+ if (this.simulcastCodecs.has(codec)) {
15609
+ this.log.error("".concat(codec, " already added, skipping adding simulcast codec"), this.logContext);
15610
+ return;
15590
15611
  }
15612
+ const simulcastCodecInfo = {
15613
+ codec,
15614
+ mediaStreamTrack: this.mediaStreamTrack.clone(),
15615
+ sender: undefined,
15616
+ encodings
15617
+ };
15618
+ this.simulcastCodecs.set(codec, simulcastCodecInfo);
15619
+ return simulcastCodecInfo;
15591
15620
  }
15592
- /** @internal */
15593
- sendSyncState(remoteTracks, localTracks) {
15594
- var _a, _b;
15595
- if (!this.pcManager) {
15596
- this.log.warn('sync state cannot be sent without peer connection setup', this.logContext);
15621
+ setSimulcastTrackSender(codec, sender) {
15622
+ const simulcastCodecInfo = this.simulcastCodecs.get(codec);
15623
+ if (!simulcastCodecInfo) {
15597
15624
  return;
15598
15625
  }
15599
- const previousAnswer = this.pcManager.subscriber.getLocalDescription();
15600
- const previousOffer = this.pcManager.subscriber.getRemoteDescription();
15601
- /* 1. autosubscribe on, so subscribed tracks = all tracks - unsub tracks,
15602
- in this case, we send unsub tracks, so server add all tracks to this
15603
- subscribe pc and unsub special tracks from it.
15604
- 2. autosubscribe off, we send subscribed tracks.
15605
- */
15606
- const autoSubscribe = (_b = (_a = this.signalOpts) === null || _a === void 0 ? void 0 : _a.autoSubscribe) !== null && _b !== void 0 ? _b : true;
15607
- const trackSids = new Array();
15608
- const trackSidsDisabled = new Array();
15609
- remoteTracks.forEach(track => {
15610
- if (track.isDesired !== autoSubscribe) {
15611
- trackSids.push(track.trackSid);
15612
- }
15613
- if (!track.isEnabled) {
15614
- trackSidsDisabled.push(track.trackSid);
15615
- }
15616
- });
15617
- this.client.sendSyncState(new SyncState({
15618
- answer: previousAnswer ? toProtoSessionDescription({
15619
- sdp: previousAnswer.sdp,
15620
- type: previousAnswer.type
15621
- }) : undefined,
15622
- offer: previousOffer ? toProtoSessionDescription({
15623
- sdp: previousOffer.sdp,
15624
- type: previousOffer.type
15625
- }) : undefined,
15626
- subscription: new UpdateSubscription({
15627
- trackSids,
15628
- subscribe: !autoSubscribe,
15629
- participantTracks: []
15630
- }),
15631
- publishTracks: getTrackPublicationInfo(localTracks),
15632
- dataChannels: this.dataChannelsInfo(),
15633
- trackSidsDisabled
15634
- }));
15635
- }
15636
- /* @internal */
15637
- failNext() {
15638
- // debugging method to fail the next reconnect/resume attempt
15639
- this.shouldFailNext = true;
15640
- }
15641
- dataChannelsInfo() {
15642
- const infos = [];
15643
- const getInfo = (dc, target) => {
15644
- if ((dc === null || dc === void 0 ? void 0 : dc.id) !== undefined && dc.id !== null) {
15645
- infos.push(new DataChannelInfo({
15646
- label: dc.label,
15647
- id: dc.id,
15648
- target
15649
- }));
15626
+ simulcastCodecInfo.sender = sender;
15627
+ // browser will reenable disabled codec/layers after new codec has been published,
15628
+ // so refresh subscribedCodecs after publish a new codec
15629
+ setTimeout(() => {
15630
+ if (this.subscribedCodecs) {
15631
+ this.setPublishingCodecs(this.subscribedCodecs);
15650
15632
  }
15651
- };
15652
- getInfo(this.dataChannelForKind(DataPacket_Kind.LOSSY), SignalTarget.PUBLISHER);
15653
- getInfo(this.dataChannelForKind(DataPacket_Kind.RELIABLE), SignalTarget.PUBLISHER);
15654
- getInfo(this.dataChannelForKind(DataPacket_Kind.LOSSY, true), SignalTarget.SUBSCRIBER);
15655
- getInfo(this.dataChannelForKind(DataPacket_Kind.RELIABLE, true), SignalTarget.SUBSCRIBER);
15656
- return infos;
15657
- }
15658
- clearReconnectTimeout() {
15659
- if (this.reconnectTimeout) {
15660
- CriticalTimers.clearTimeout(this.reconnectTimeout);
15661
- }
15662
- }
15663
- clearPendingReconnect() {
15664
- this.clearReconnectTimeout();
15665
- this.reconnectAttempts = 0;
15666
- }
15667
- registerOnLineListener() {
15668
- if (isWeb()) {
15669
- window.addEventListener('online', this.handleBrowserOnLine);
15670
- }
15671
- }
15672
- deregisterOnLineListener() {
15673
- if (isWeb()) {
15674
- window.removeEventListener('online', this.handleBrowserOnLine);
15675
- }
15676
- }
15677
- }
15678
- class SignalReconnectError extends Error {}
15679
-
15680
- class RegionUrlProvider {
15681
- constructor(url, token) {
15682
- this.lastUpdateAt = 0;
15683
- this.settingsCacheTime = 3000;
15684
- this.attemptedRegions = [];
15685
- this.serverUrl = new URL(url);
15686
- this.token = token;
15687
- }
15688
- updateToken(token) {
15689
- this.token = token;
15690
- }
15691
- isCloud() {
15692
- return isCloud(this.serverUrl);
15693
- }
15694
- getServerUrl() {
15695
- return this.serverUrl;
15633
+ }, refreshSubscribedCodecAfterNewCodec);
15696
15634
  }
15697
- getNextBestRegionUrl(abortSignal) {
15635
+ /**
15636
+ * @internal
15637
+ * Sets codecs that should be publishing, returns new codecs that have not yet
15638
+ * been published
15639
+ */
15640
+ setPublishingCodecs(codecs) {
15698
15641
  return __awaiter(this, void 0, void 0, function* () {
15699
- if (!this.isCloud()) {
15700
- throw Error('region availability is only supported for LiveKit Cloud domains');
15701
- }
15702
- if (!this.regionSettings || Date.now() - this.lastUpdateAt > this.settingsCacheTime) {
15703
- this.regionSettings = yield this.fetchRegionSettings(abortSignal);
15704
- }
15705
- const regionsLeft = this.regionSettings.regions.filter(region => !this.attemptedRegions.find(attempted => attempted.url === region.url));
15706
- if (regionsLeft.length > 0) {
15707
- const nextRegion = regionsLeft[0];
15708
- this.attemptedRegions.push(nextRegion);
15709
- livekitLogger.debug("next region: ".concat(nextRegion.region));
15710
- return nextRegion.url;
15711
- } else {
15712
- return null;
15642
+ var _a, codecs_1, codecs_1_1;
15643
+ var _b, e_5, _c, _d;
15644
+ this.log.debug('setting publishing codecs', Object.assign(Object.assign({}, this.logContext), {
15645
+ codecs,
15646
+ currentCodec: this.codec
15647
+ }));
15648
+ // only enable simulcast codec for preference codec setted
15649
+ if (!this.codec && codecs.length > 0) {
15650
+ yield this.setPublishingLayers(codecs[0].qualities);
15651
+ return [];
15713
15652
  }
15714
- });
15715
- }
15716
- resetAttempts() {
15717
- this.attemptedRegions = [];
15718
- }
15719
- /* @internal */
15720
- fetchRegionSettings(signal) {
15721
- return __awaiter(this, void 0, void 0, function* () {
15722
- const regionSettingsResponse = yield fetch("".concat(getCloudConfigUrl(this.serverUrl), "/regions"), {
15723
- headers: {
15724
- authorization: "Bearer ".concat(this.token)
15725
- },
15726
- signal
15727
- });
15728
- if (regionSettingsResponse.ok) {
15729
- const regionSettings = yield regionSettingsResponse.json();
15730
- this.lastUpdateAt = Date.now();
15731
- return regionSettings;
15732
- } else {
15733
- throw new ConnectionError("Could not fetch region settings: ".concat(regionSettingsResponse.statusText), regionSettingsResponse.status === 401 ? 0 /* ConnectionErrorReason.NotAllowed */ : undefined, regionSettingsResponse.status);
15653
+ this.subscribedCodecs = codecs;
15654
+ const newCodecs = [];
15655
+ try {
15656
+ for (_a = true, codecs_1 = __asyncValues(codecs); codecs_1_1 = yield codecs_1.next(), _b = codecs_1_1.done, !_b; _a = true) {
15657
+ _d = codecs_1_1.value;
15658
+ _a = false;
15659
+ const codec = _d;
15660
+ if (!this.codec || this.codec === codec.codec) {
15661
+ yield this.setPublishingLayers(codec.qualities);
15662
+ } else {
15663
+ const simulcastCodecInfo = this.simulcastCodecs.get(codec.codec);
15664
+ this.log.debug("try setPublishingCodec for ".concat(codec.codec), Object.assign(Object.assign({}, this.logContext), {
15665
+ simulcastCodecInfo
15666
+ }));
15667
+ if (!simulcastCodecInfo || !simulcastCodecInfo.sender) {
15668
+ for (const q of codec.qualities) {
15669
+ if (q.enabled) {
15670
+ newCodecs.push(codec.codec);
15671
+ break;
15672
+ }
15673
+ }
15674
+ } else if (simulcastCodecInfo.encodings) {
15675
+ this.log.debug("try setPublishingLayersForSender ".concat(codec.codec), this.logContext);
15676
+ yield setPublishingLayersForSender(simulcastCodecInfo.sender, simulcastCodecInfo.encodings, codec.qualities, this.senderLock, this.log, this.logContext);
15677
+ }
15678
+ }
15679
+ }
15680
+ } catch (e_5_1) {
15681
+ e_5 = {
15682
+ error: e_5_1
15683
+ };
15684
+ } finally {
15685
+ try {
15686
+ if (!_a && !_b && (_c = codecs_1.return)) yield _c.call(codecs_1);
15687
+ } finally {
15688
+ if (e_5) throw e_5.error;
15689
+ }
15734
15690
  }
15691
+ return newCodecs;
15735
15692
  });
15736
15693
  }
15737
- }
15738
- function getCloudConfigUrl(serverUrl) {
15739
- return "".concat(serverUrl.protocol.replace('ws', 'http'), "//").concat(serverUrl.host, "/settings");
15740
- }
15741
-
15742
- const monitorFrequency = 2000;
15743
- function computeBitrate(currentStats, prevStats) {
15744
- if (!prevStats) {
15745
- return 0;
15746
- }
15747
- let bytesNow;
15748
- let bytesPrev;
15749
- if ('bytesReceived' in currentStats) {
15750
- bytesNow = currentStats.bytesReceived;
15751
- bytesPrev = prevStats.bytesReceived;
15752
- } else if ('bytesSent' in currentStats) {
15753
- bytesNow = currentStats.bytesSent;
15754
- bytesPrev = prevStats.bytesSent;
15755
- }
15756
- if (bytesNow === undefined || bytesPrev === undefined || currentStats.timestamp === undefined || prevStats.timestamp === undefined) {
15757
- return 0;
15758
- }
15759
- return (bytesNow - bytesPrev) * 8 * 1000 / (currentStats.timestamp - prevStats.timestamp);
15760
- }
15761
-
15762
- class LocalAudioTrack extends LocalTrack {
15763
15694
  /**
15764
- * boolean indicating whether enhanced noise cancellation is currently being used on this track
15765
- */
15766
- get enhancedNoiseCancellation() {
15767
- return this.isKrispNoiseFilterEnabled;
15768
- }
15769
- /**
15770
- *
15771
- * @param mediaTrack
15772
- * @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks
15773
- * @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK
15695
+ * @internal
15696
+ * Sets layers that should be publishing
15774
15697
  */
15775
- constructor(mediaTrack, constraints) {
15776
- let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
15777
- let audioContext = arguments.length > 3 ? arguments[3] : undefined;
15778
- let loggerOptions = arguments.length > 4 ? arguments[4] : undefined;
15779
- super(mediaTrack, Track.Kind.Audio, constraints, userProvidedTrack, loggerOptions);
15780
- /** @internal */
15781
- this.stopOnMute = false;
15782
- this.isKrispNoiseFilterEnabled = false;
15783
- this.monitorSender = () => __awaiter(this, void 0, void 0, function* () {
15784
- if (!this.sender) {
15785
- this._currentBitrate = 0;
15786
- return;
15787
- }
15788
- let stats;
15789
- try {
15790
- stats = yield this.getSenderStats();
15791
- } catch (e) {
15792
- this.log.error('could not get audio sender stats', Object.assign(Object.assign({}, this.logContext), {
15793
- error: e
15794
- }));
15698
+ setPublishingLayers(qualities) {
15699
+ return __awaiter(this, void 0, void 0, function* () {
15700
+ this.log.debug('setting publishing layers', Object.assign(Object.assign({}, this.logContext), {
15701
+ qualities
15702
+ }));
15703
+ if (!this.sender || !this.encodings) {
15795
15704
  return;
15796
15705
  }
15797
- if (stats && this.prevStats) {
15798
- this._currentBitrate = computeBitrate(stats, this.prevStats);
15799
- }
15800
- this.prevStats = stats;
15801
- });
15802
- this.handleKrispNoiseFilterEnable = () => {
15803
- this.isKrispNoiseFilterEnabled = true;
15804
- this.log.debug("Krisp noise filter enabled", this.logContext);
15805
- this.emit(TrackEvent.AudioTrackFeatureUpdate, this, AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION, true);
15806
- };
15807
- this.handleKrispNoiseFilterDisable = () => {
15808
- this.isKrispNoiseFilterEnabled = false;
15809
- this.log.debug("Krisp noise filter disabled", this.logContext);
15810
- this.emit(TrackEvent.AudioTrackFeatureUpdate, this, AudioTrackFeature.TF_ENHANCED_NOISE_CANCELLATION, false);
15811
- };
15812
- this.audioContext = audioContext;
15813
- this.checkForSilence();
15814
- }
15815
- setDeviceId(deviceId) {
15816
- return __awaiter(this, void 0, void 0, function* () {
15817
- if (this._constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)) {
15818
- return true;
15819
- }
15820
- this._constraints.deviceId = deviceId;
15821
- if (!this.isMuted) {
15822
- yield this.restartTrack();
15823
- }
15824
- return this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
15825
- });
15826
- }
15827
- mute() {
15828
- const _super = Object.create(null, {
15829
- mute: {
15830
- get: () => super.mute
15706
+ yield setPublishingLayersForSender(this.sender, this.encodings, qualities, this.senderLock, this.log, this.logContext);
15707
+ });
15708
+ }
15709
+ handleAppVisibilityChanged() {
15710
+ const _super = Object.create(null, {
15711
+ handleAppVisibilityChanged: {
15712
+ get: () => super.handleAppVisibilityChanged
15831
15713
  }
15832
15714
  });
15833
15715
  return __awaiter(this, void 0, void 0, function* () {
15834
- const unlock = yield this.muteLock.lock();
15835
- try {
15836
- if (this.isMuted) {
15837
- this.log.debug('Track already muted', this.logContext);
15838
- return this;
15839
- }
15840
- // disabled special handling as it will cause BT headsets to switch communication modes
15841
- if (this.source === Track.Source.Microphone && this.stopOnMute && !this.isUserProvided) {
15842
- this.log.debug('stopping mic track', this.logContext);
15843
- // also stop the track, so that microphone indicator is turned off
15844
- this._mediaStreamTrack.stop();
15845
- }
15846
- yield _super.mute.call(this);
15847
- return this;
15848
- } finally {
15849
- unlock();
15716
+ yield _super.handleAppVisibilityChanged.call(this);
15717
+ if (!isMobile()) return;
15718
+ if (this.isInBackground && this.source === Track.Source.Camera) {
15719
+ this._mediaStreamTrack.enabled = false;
15850
15720
  }
15851
15721
  });
15852
15722
  }
15853
- unmute() {
15854
- const _super = Object.create(null, {
15855
- unmute: {
15856
- get: () => super.unmute
15723
+ }
15724
+ function setPublishingLayersForSender(sender, senderEncodings, qualities, senderLock, log, logContext) {
15725
+ return __awaiter(this, void 0, void 0, function* () {
15726
+ const unlock = yield senderLock.lock();
15727
+ log.debug('setPublishingLayersForSender', Object.assign(Object.assign({}, logContext), {
15728
+ sender,
15729
+ qualities,
15730
+ senderEncodings
15731
+ }));
15732
+ try {
15733
+ const params = sender.getParameters();
15734
+ const {
15735
+ encodings
15736
+ } = params;
15737
+ if (!encodings) {
15738
+ return;
15857
15739
  }
15858
- });
15859
- return __awaiter(this, void 0, void 0, function* () {
15860
- const unlock = yield this.muteLock.lock();
15861
- try {
15862
- if (!this.isMuted) {
15863
- this.log.debug('Track already unmuted', this.logContext);
15864
- return this;
15865
- }
15866
- const deviceHasChanged = this._constraints.deviceId && this._mediaStreamTrack.getSettings().deviceId !== unwrapConstraint(this._constraints.deviceId);
15867
- if (this.source === Track.Source.Microphone && (this.stopOnMute || this._mediaStreamTrack.readyState === 'ended' || deviceHasChanged) && !this.isUserProvided) {
15868
- this.log.debug('reacquiring mic track', this.logContext);
15869
- yield this.restartTrack();
15870
- }
15871
- yield _super.unmute.call(this);
15872
- return this;
15873
- } finally {
15874
- unlock();
15740
+ if (encodings.length !== senderEncodings.length) {
15741
+ log.warn('cannot set publishing layers, encodings mismatch', Object.assign(Object.assign({}, logContext), {
15742
+ encodings,
15743
+ senderEncodings
15744
+ }));
15745
+ return;
15875
15746
  }
15876
- });
15877
- }
15878
- restartTrack(options) {
15879
- return __awaiter(this, void 0, void 0, function* () {
15880
- let constraints;
15881
- if (options) {
15882
- const streamConstraints = constraintsForOptions({
15883
- audio: options
15747
+ let hasChanged = false;
15748
+ /* disable closable spatial layer as it has video blur / frozen issue with current server / client
15749
+ 1. chrome 113: when switching to up layer with scalability Mode change, it will generate a
15750
+ low resolution frame and recover very quickly, but noticable
15751
+ 2. livekit sfu: additional pli request cause video frozen for a few frames, also noticable */
15752
+ const closableSpatial = false;
15753
+ /* @ts-ignore */
15754
+ if (closableSpatial && encodings[0].scalabilityMode) ; else {
15755
+ // simulcast dynacast encodings
15756
+ encodings.forEach((encoding, idx) => {
15757
+ var _a;
15758
+ let rid = (_a = encoding.rid) !== null && _a !== void 0 ? _a : '';
15759
+ if (rid === '') {
15760
+ rid = 'q';
15761
+ }
15762
+ const quality = videoQualityForRid(rid);
15763
+ const subscribedQuality = qualities.find(q => q.quality === quality);
15764
+ if (!subscribedQuality) {
15765
+ return;
15766
+ }
15767
+ if (encoding.active !== subscribedQuality.enabled) {
15768
+ hasChanged = true;
15769
+ encoding.active = subscribedQuality.enabled;
15770
+ log.debug("setting layer ".concat(subscribedQuality.quality, " to ").concat(encoding.active ? 'enabled' : 'disabled'), logContext);
15771
+ // FireFox does not support setting encoding.active to false, so we
15772
+ // have a workaround of lowering its bitrate and resolution to the min.
15773
+ if (isFireFox()) {
15774
+ if (subscribedQuality.enabled) {
15775
+ encoding.scaleResolutionDownBy = senderEncodings[idx].scaleResolutionDownBy;
15776
+ encoding.maxBitrate = senderEncodings[idx].maxBitrate;
15777
+ /* @ts-ignore */
15778
+ encoding.maxFrameRate = senderEncodings[idx].maxFrameRate;
15779
+ } else {
15780
+ encoding.scaleResolutionDownBy = 4;
15781
+ encoding.maxBitrate = 10;
15782
+ /* @ts-ignore */
15783
+ encoding.maxFrameRate = 2;
15784
+ }
15785
+ }
15786
+ }
15884
15787
  });
15885
- if (typeof streamConstraints.audio !== 'boolean') {
15886
- constraints = streamConstraints.audio;
15887
- }
15888
15788
  }
15889
- yield this.restart(constraints);
15890
- });
15891
- }
15892
- restart(constraints) {
15893
- const _super = Object.create(null, {
15894
- restart: {
15895
- get: () => super.restart
15789
+ if (hasChanged) {
15790
+ params.encodings = encodings;
15791
+ log.debug("setting encodings", Object.assign(Object.assign({}, logContext), {
15792
+ encodings: params.encodings
15793
+ }));
15794
+ yield sender.setParameters(params);
15896
15795
  }
15897
- });
15898
- return __awaiter(this, void 0, void 0, function* () {
15899
- const track = yield _super.restart.call(this, constraints);
15900
- this.checkForSilence();
15901
- return track;
15902
- });
15903
- }
15904
- /* @internal */
15905
- startMonitor() {
15906
- if (!isWeb()) {
15907
- return;
15908
- }
15909
- if (this.monitorInterval) {
15910
- return;
15796
+ } finally {
15797
+ unlock();
15911
15798
  }
15912
- this.monitorInterval = setInterval(() => {
15913
- this.monitorSender();
15914
- }, monitorFrequency);
15799
+ });
15800
+ }
15801
+ function videoQualityForRid(rid) {
15802
+ switch (rid) {
15803
+ case 'f':
15804
+ return VideoQuality.HIGH;
15805
+ case 'h':
15806
+ return VideoQuality.MEDIUM;
15807
+ case 'q':
15808
+ return VideoQuality.LOW;
15809
+ default:
15810
+ return VideoQuality.HIGH;
15915
15811
  }
15916
- setProcessor(processor) {
15917
- return __awaiter(this, void 0, void 0, function* () {
15918
- var _a;
15919
- const unlock = yield this.processorLock.lock();
15920
- try {
15921
- if (!this.audioContext) {
15922
- throw Error('Audio context needs to be set on LocalAudioTrack in order to enable processors');
15923
- }
15924
- if (this.processor) {
15925
- yield this.stopProcessor();
15926
- }
15927
- const processorOptions = {
15928
- kind: this.kind,
15929
- track: this._mediaStreamTrack,
15930
- audioContext: this.audioContext
15931
- };
15932
- this.log.debug("setting up audio processor ".concat(processor.name), this.logContext);
15933
- yield processor.init(processorOptions);
15934
- this.processor = processor;
15935
- if (this.processor.processedTrack) {
15936
- yield (_a = this.sender) === null || _a === void 0 ? void 0 : _a.replaceTrack(this.processor.processedTrack);
15937
- this.processor.processedTrack.addEventListener('enable-lk-krisp-noise-filter', this.handleKrispNoiseFilterEnable);
15938
- this.processor.processedTrack.addEventListener('disable-lk-krisp-noise-filter', this.handleKrispNoiseFilterDisable);
15939
- }
15940
- this.emit(TrackEvent.TrackProcessorUpdate, this.processor);
15941
- } finally {
15942
- unlock();
15943
- }
15944
- });
15812
+ }
15813
+ function videoLayersFromEncodings(width, height, encodings, svc) {
15814
+ // default to a single layer, HQ
15815
+ if (!encodings) {
15816
+ return [new VideoLayer({
15817
+ quality: VideoQuality.HIGH,
15818
+ width,
15819
+ height,
15820
+ bitrate: 0,
15821
+ ssrc: 0
15822
+ })];
15945
15823
  }
15946
- /**
15947
- * @internal
15948
- * @experimental
15949
- */
15950
- setAudioContext(audioContext) {
15951
- this.audioContext = audioContext;
15824
+ if (svc) {
15825
+ // svc layers
15826
+ /* @ts-ignore */
15827
+ const encodingSM = encodings[0].scalabilityMode;
15828
+ const sm = new ScalabilityMode(encodingSM);
15829
+ const layers = [];
15830
+ const resRatio = sm.suffix == 'h' ? 1.5 : 2;
15831
+ const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
15832
+ for (let i = 0; i < sm.spatial; i += 1) {
15833
+ layers.push(new VideoLayer({
15834
+ quality: VideoQuality.HIGH - i,
15835
+ width: Math.ceil(width / Math.pow(resRatio, i)),
15836
+ height: Math.ceil(height / Math.pow(resRatio, i)),
15837
+ bitrate: encodings[0].maxBitrate ? Math.ceil(encodings[0].maxBitrate / Math.pow(bitratesRatio, i)) : 0,
15838
+ ssrc: 0
15839
+ }));
15840
+ }
15841
+ return layers;
15952
15842
  }
15953
- getSenderStats() {
15954
- return __awaiter(this, void 0, void 0, function* () {
15955
- var _a;
15956
- if (!((_a = this.sender) === null || _a === void 0 ? void 0 : _a.getStats)) {
15957
- return undefined;
15958
- }
15959
- const stats = yield this.sender.getStats();
15960
- let audioStats;
15961
- stats.forEach(v => {
15962
- if (v.type === 'outbound-rtp') {
15963
- audioStats = {
15964
- type: 'audio',
15965
- streamId: v.id,
15966
- packetsSent: v.packetsSent,
15967
- packetsLost: v.packetsLost,
15968
- bytesSent: v.bytesSent,
15969
- timestamp: v.timestamp,
15970
- roundTripTime: v.roundTripTime,
15971
- jitter: v.jitter
15972
- };
15973
- }
15974
- });
15975
- return audioStats;
15976
- });
15977
- }
15978
- checkForSilence() {
15979
- return __awaiter(this, void 0, void 0, function* () {
15980
- const trackIsSilent = yield detectSilence(this);
15981
- if (trackIsSilent) {
15982
- if (!this.isMuted) {
15983
- this.log.warn('silence detected on local audio track', this.logContext);
15984
- }
15985
- this.emit(TrackEvent.AudioSilenceDetected);
15986
- }
15987
- return trackIsSilent;
15843
+ return encodings.map(encoding => {
15844
+ var _a, _b, _c;
15845
+ const scale = (_a = encoding.scaleResolutionDownBy) !== null && _a !== void 0 ? _a : 1;
15846
+ let quality = videoQualityForRid((_b = encoding.rid) !== null && _b !== void 0 ? _b : '');
15847
+ return new VideoLayer({
15848
+ quality,
15849
+ width: Math.ceil(width / scale),
15850
+ height: Math.ceil(height / scale),
15851
+ bitrate: (_c = encoding.maxBitrate) !== null && _c !== void 0 ? _c : 0,
15852
+ ssrc: 0
15988
15853
  });
15989
- }
15854
+ });
15990
15855
  }
15991
15856
 
15857
+ const lossyDataChannel = '_lossy';
15858
+ const reliableDataChannel = '_reliable';
15859
+ const minReconnectWait = 2 * 1000;
15860
+ const leaveReconnect = 'leave-reconnect';
15861
+ var PCState;
15862
+ (function (PCState) {
15863
+ PCState[PCState["New"] = 0] = "New";
15864
+ PCState[PCState["Connected"] = 1] = "Connected";
15865
+ PCState[PCState["Disconnected"] = 2] = "Disconnected";
15866
+ PCState[PCState["Reconnecting"] = 3] = "Reconnecting";
15867
+ PCState[PCState["Closed"] = 4] = "Closed";
15868
+ })(PCState || (PCState = {}));
15992
15869
  /** @internal */
15993
- function mediaTrackToLocalTrack(mediaStreamTrack, constraints, loggerOptions) {
15994
- switch (mediaStreamTrack.kind) {
15995
- case 'audio':
15996
- return new LocalAudioTrack(mediaStreamTrack, constraints, false, undefined, loggerOptions);
15997
- case 'video':
15998
- return new LocalVideoTrack(mediaStreamTrack, constraints, false, loggerOptions);
15999
- default:
16000
- throw new TrackInvalidError("unsupported track type: ".concat(mediaStreamTrack.kind));
16001
- }
16002
- }
16003
- /* @internal */
16004
- const presets169 = Object.values(VideoPresets);
16005
- /* @internal */
16006
- const presets43 = Object.values(VideoPresets43);
16007
- /* @internal */
16008
- const presetsScreenShare = Object.values(ScreenSharePresets);
16009
- /* @internal */
16010
- const defaultSimulcastPresets169 = [VideoPresets.h180, VideoPresets.h360];
16011
- /* @internal */
16012
- const defaultSimulcastPresets43 = [VideoPresets43.h180, VideoPresets43.h360];
16013
- /* @internal */
16014
- const computeDefaultScreenShareSimulcastPresets = fromPreset => {
16015
- const layers = [{
16016
- scaleResolutionDownBy: 2,
16017
- fps: fromPreset.encoding.maxFramerate
16018
- }];
16019
- return layers.map(t => {
16020
- var _a, _b;
16021
- return new VideoPreset(Math.floor(fromPreset.width / t.scaleResolutionDownBy), Math.floor(fromPreset.height / t.scaleResolutionDownBy), Math.max(150000, Math.floor(fromPreset.encoding.maxBitrate / (Math.pow(t.scaleResolutionDownBy, 2) * (((_a = fromPreset.encoding.maxFramerate) !== null && _a !== void 0 ? _a : 30) / ((_b = t.fps) !== null && _b !== void 0 ? _b : 30))))), t.fps, fromPreset.encoding.priority);
16022
- });
16023
- };
16024
- // /**
16025
- // *
16026
- // * @internal
16027
- // * @experimental
16028
- // */
16029
- // const computeDefaultMultiCodecSimulcastEncodings = (width: number, height: number) => {
16030
- // // use vp8 as a default
16031
- // const vp8 = determineAppropriateEncoding(false, width, height);
16032
- // const vp9 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.9 };
16033
- // const h264 = { ...vp8, maxBitrate: vp8.maxBitrate * 1.1 };
16034
- // const av1 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.7 };
16035
- // return {
16036
- // vp8,
16037
- // vp9,
16038
- // h264,
16039
- // av1,
16040
- // };
16041
- // };
16042
- const videoRids = ['q', 'h', 'f'];
16043
- /* @internal */
16044
- function computeVideoEncodings(isScreenShare, width, height, options) {
16045
- var _a, _b;
16046
- let videoEncoding = options === null || options === void 0 ? void 0 : options.videoEncoding;
16047
- if (isScreenShare) {
16048
- videoEncoding = options === null || options === void 0 ? void 0 : options.screenShareEncoding;
16049
- }
16050
- const useSimulcast = options === null || options === void 0 ? void 0 : options.simulcast;
16051
- const scalabilityMode = options === null || options === void 0 ? void 0 : options.scalabilityMode;
16052
- const videoCodec = options === null || options === void 0 ? void 0 : options.videoCodec;
16053
- if (!videoEncoding && !useSimulcast && !scalabilityMode || !width || !height) {
16054
- // when we aren't simulcasting or svc, will need to return a single encoding without
16055
- // capping bandwidth. we always require a encoding for dynacast
16056
- return [{}];
15870
+ class RTCEngine extends eventsExports.EventEmitter {
15871
+ get isClosed() {
15872
+ return this._isClosed;
16057
15873
  }
16058
- if (!videoEncoding) {
16059
- // find the right encoding based on width/height
16060
- videoEncoding = determineAppropriateEncoding(isScreenShare, width, height, videoCodec);
16061
- livekitLogger.debug('using video encoding', videoEncoding);
15874
+ get pendingReconnect() {
15875
+ return !!this.reconnectTimeout;
16062
15876
  }
16063
- const original = new VideoPreset(width, height, videoEncoding.maxBitrate, videoEncoding.maxFramerate, videoEncoding.priority);
16064
- if (scalabilityMode && isSVCCodec(videoCodec)) {
16065
- const sm = new ScalabilityMode(scalabilityMode);
16066
- const encodings = [];
16067
- if (sm.spatial > 3) {
16068
- throw new Error("unsupported scalabilityMode: ".concat(scalabilityMode));
16069
- }
16070
- // Before M113 in Chrome, defining multiple encodings with an SVC codec indicated
16071
- // that SVC mode should be used. Safari still works this way.
16072
- // This is a bit confusing but is due to how libwebrtc interpreted the encodings field
16073
- // before M113.
16074
- // Announced here: https://groups.google.com/g/discuss-webrtc/c/-QQ3pxrl-fw?pli=1
16075
- const browser = getBrowser();
16076
- if (isSafari() || (browser === null || browser === void 0 ? void 0 : browser.name) === 'Chrome' && compareVersions(browser === null || browser === void 0 ? void 0 : browser.version, '113') < 0) {
16077
- const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
16078
- for (let i = 0; i < sm.spatial; i += 1) {
16079
- // in legacy SVC, scaleResolutionDownBy cannot be set
16080
- encodings.push({
16081
- rid: videoRids[2 - i],
16082
- maxBitrate: videoEncoding.maxBitrate / Math.pow(bitratesRatio, i),
16083
- maxFramerate: original.encoding.maxFramerate
16084
- });
15877
+ constructor(options) {
15878
+ var _a;
15879
+ super();
15880
+ this.options = options;
15881
+ this.rtcConfig = {};
15882
+ this.peerConnectionTimeout = roomConnectOptionDefaults.peerConnectionTimeout;
15883
+ this.fullReconnectOnNext = false;
15884
+ this.subscriberPrimary = false;
15885
+ this.pcState = PCState.New;
15886
+ this._isClosed = true;
15887
+ this.pendingTrackResolvers = {};
15888
+ this.reconnectAttempts = 0;
15889
+ this.reconnectStart = 0;
15890
+ this.attemptingReconnect = false;
15891
+ /** keeps track of how often an initial join connection has been tried */
15892
+ this.joinAttempts = 0;
15893
+ /** specifies how often an initial join connection is allowed to retry */
15894
+ this.maxJoinAttempts = 1;
15895
+ this.shouldFailNext = false;
15896
+ this.log = livekitLogger;
15897
+ this.handleDataChannel = _b => __awaiter(this, [_b], void 0, function (_ref) {
15898
+ var _this = this;
15899
+ let {
15900
+ channel
15901
+ } = _ref;
15902
+ return function* () {
15903
+ if (!channel) {
15904
+ return;
15905
+ }
15906
+ if (channel.label === reliableDataChannel) {
15907
+ _this.reliableDCSub = channel;
15908
+ } else if (channel.label === lossyDataChannel) {
15909
+ _this.lossyDCSub = channel;
15910
+ } else {
15911
+ return;
15912
+ }
15913
+ _this.log.debug("on data channel ".concat(channel.id, ", ").concat(channel.label), _this.logContext);
15914
+ channel.onmessage = _this.handleDataMessage;
15915
+ }();
15916
+ });
15917
+ this.handleDataMessage = message => __awaiter(this, void 0, void 0, function* () {
15918
+ var _c, _d, _e;
15919
+ // make sure to respect incoming data message order by processing message events one after the other
15920
+ const unlock = yield this.dataProcessLock.lock();
15921
+ try {
15922
+ // decode
15923
+ let buffer;
15924
+ if (message.data instanceof ArrayBuffer) {
15925
+ buffer = message.data;
15926
+ } else if (message.data instanceof Blob) {
15927
+ buffer = yield message.data.arrayBuffer();
15928
+ } else {
15929
+ this.log.error('unsupported data type', Object.assign(Object.assign({}, this.logContext), {
15930
+ data: message.data
15931
+ }));
15932
+ return;
15933
+ }
15934
+ const dp = DataPacket.fromBinary(new Uint8Array(buffer));
15935
+ if (((_c = dp.value) === null || _c === void 0 ? void 0 : _c.case) === 'speaker') {
15936
+ // dispatch speaker updates
15937
+ this.emit(EngineEvent.ActiveSpeakersUpdate, dp.value.value.speakers);
15938
+ } else if (((_d = dp.value) === null || _d === void 0 ? void 0 : _d.case) === 'user') {
15939
+ this.emit(EngineEvent.DataPacketReceived, dp.value.value, dp.kind);
15940
+ } else if (((_e = dp.value) === null || _e === void 0 ? void 0 : _e.case) === 'transcription') {
15941
+ this.emit(EngineEvent.TranscriptionReceived, dp.value.value);
15942
+ }
15943
+ } finally {
15944
+ unlock();
16085
15945
  }
16086
- // legacy SVC, scalabilityMode is set only on the first encoding
16087
- /* @ts-ignore */
16088
- encodings[0].scalabilityMode = scalabilityMode;
16089
- } else {
16090
- encodings.push({
16091
- maxBitrate: videoEncoding.maxBitrate,
16092
- maxFramerate: original.encoding.maxFramerate,
16093
- /* @ts-ignore */
16094
- scalabilityMode: scalabilityMode
16095
- });
16096
- }
16097
- livekitLogger.debug("using svc encoding", {
16098
- encodings
16099
15946
  });
16100
- return encodings;
16101
- }
16102
- if (!useSimulcast) {
16103
- return [videoEncoding];
16104
- }
16105
- let presets = [];
16106
- if (isScreenShare) {
16107
- presets = (_a = sortPresets(options === null || options === void 0 ? void 0 : options.screenShareSimulcastLayers)) !== null && _a !== void 0 ? _a : defaultSimulcastLayers(isScreenShare, original);
16108
- } else {
16109
- presets = (_b = sortPresets(options === null || options === void 0 ? void 0 : options.videoSimulcastLayers)) !== null && _b !== void 0 ? _b : defaultSimulcastLayers(isScreenShare, original);
16110
- }
16111
- let midPreset;
16112
- if (presets.length > 0) {
16113
- const lowPreset = presets[0];
16114
- if (presets.length > 1) {
16115
- [, midPreset] = presets;
16116
- }
16117
- // NOTE:
16118
- // 1. Ordering of these encodings is important. Chrome seems
16119
- // to use the index into encodings to decide which layer
16120
- // to disable when CPU constrained.
16121
- // So encodings should be ordered in increasing spatial
16122
- // resolution order.
16123
- // 2. livekit-server translates rids into layers. So, all encodings
16124
- // should have the base layer `q` and then more added
16125
- // based on other conditions.
16126
- const size = Math.max(width, height);
16127
- if (size >= 960 && midPreset) {
16128
- return encodingsFromPresets(width, height, [lowPreset, midPreset, original]);
16129
- }
16130
- if (size >= 480) {
16131
- return encodingsFromPresets(width, height, [lowPreset, original]);
16132
- }
15947
+ this.handleDataError = event => {
15948
+ const channel = event.currentTarget;
15949
+ const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable';
15950
+ if (event instanceof ErrorEvent && event.error) {
15951
+ const {
15952
+ error
15953
+ } = event.error;
15954
+ this.log.error("DataChannel error on ".concat(channelKind, ": ").concat(event.message), Object.assign(Object.assign({}, this.logContext), {
15955
+ error
15956
+ }));
15957
+ } else {
15958
+ this.log.error("Unknown DataChannel error on ".concat(channelKind), Object.assign(Object.assign({}, this.logContext), {
15959
+ event
15960
+ }));
15961
+ }
15962
+ };
15963
+ this.handleBufferedAmountLow = event => {
15964
+ const channel = event.currentTarget;
15965
+ const channelKind = channel.maxRetransmits === 0 ? DataPacket_Kind.LOSSY : DataPacket_Kind.RELIABLE;
15966
+ this.updateAndEmitDCBufferStatus(channelKind);
15967
+ };
15968
+ // websocket reconnect behavior. if websocket is interrupted, and the PeerConnection
15969
+ // continues to work, we can reconnect to websocket to continue the session
15970
+ // after a number of retries, we'll close and give up permanently
15971
+ this.handleDisconnect = (connection, disconnectReason) => {
15972
+ if (this._isClosed) {
15973
+ return;
15974
+ }
15975
+ this.log.warn("".concat(connection, " disconnected"), this.logContext);
15976
+ if (this.reconnectAttempts === 0) {
15977
+ // only reset start time on the first try
15978
+ this.reconnectStart = Date.now();
15979
+ }
15980
+ const disconnect = duration => {
15981
+ this.log.warn("could not recover connection after ".concat(this.reconnectAttempts, " attempts, ").concat(duration, "ms. giving up"), this.logContext);
15982
+ this.emit(EngineEvent.Disconnected);
15983
+ this.close();
15984
+ };
15985
+ const duration = Date.now() - this.reconnectStart;
15986
+ let delay = this.getNextRetryDelay({
15987
+ elapsedMs: duration,
15988
+ retryCount: this.reconnectAttempts
15989
+ });
15990
+ if (delay === null) {
15991
+ disconnect(duration);
15992
+ return;
15993
+ }
15994
+ if (connection === leaveReconnect) {
15995
+ delay = 0;
15996
+ }
15997
+ this.log.debug("reconnecting in ".concat(delay, "ms"), this.logContext);
15998
+ this.clearReconnectTimeout();
15999
+ if (this.token && this.regionUrlProvider) {
16000
+ // token may have been refreshed, we do not want to recreate the regionUrlProvider
16001
+ // since the current engine may have inherited a regional url
16002
+ this.regionUrlProvider.updateToken(this.token);
16003
+ }
16004
+ this.reconnectTimeout = CriticalTimers.setTimeout(() => this.attemptReconnect(disconnectReason).finally(() => this.reconnectTimeout = undefined), delay);
16005
+ };
16006
+ this.waitForRestarted = () => {
16007
+ return new Promise((resolve, reject) => {
16008
+ if (this.pcState === PCState.Connected) {
16009
+ resolve();
16010
+ }
16011
+ const onRestarted = () => {
16012
+ this.off(EngineEvent.Disconnected, onDisconnected);
16013
+ resolve();
16014
+ };
16015
+ const onDisconnected = () => {
16016
+ this.off(EngineEvent.Restarted, onRestarted);
16017
+ reject();
16018
+ };
16019
+ this.once(EngineEvent.Restarted, onRestarted);
16020
+ this.once(EngineEvent.Disconnected, onDisconnected);
16021
+ });
16022
+ };
16023
+ this.updateAndEmitDCBufferStatus = kind => {
16024
+ const status = this.isBufferStatusLow(kind);
16025
+ if (typeof status !== 'undefined' && status !== this.dcBufferStatus.get(kind)) {
16026
+ this.dcBufferStatus.set(kind, status);
16027
+ this.emit(EngineEvent.DCBufferStatusChanged, status, kind);
16028
+ }
16029
+ };
16030
+ this.isBufferStatusLow = kind => {
16031
+ const dc = this.dataChannelForKind(kind);
16032
+ if (dc) {
16033
+ return dc.bufferedAmount <= dc.bufferedAmountLowThreshold;
16034
+ }
16035
+ };
16036
+ this.handleBrowserOnLine = () => {
16037
+ // in case the engine is currently reconnecting, attempt a reconnect immediately after the browser state has changed to 'onLine'
16038
+ if (this.client.currentState === SignalConnectionState.RECONNECTING) {
16039
+ this.clearReconnectTimeout();
16040
+ this.attemptReconnect(ReconnectReason.RR_SIGNAL_DISCONNECTED);
16041
+ }
16042
+ };
16043
+ this.log = getLogger((_a = options.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Engine);
16044
+ this.loggerOptions = {
16045
+ loggerName: options.loggerName,
16046
+ loggerContextCb: () => this.logContext
16047
+ };
16048
+ this.client = new SignalClient(undefined, this.loggerOptions);
16049
+ this.client.signalLatency = this.options.expSignalLatency;
16050
+ this.reconnectPolicy = this.options.reconnectPolicy;
16051
+ this.registerOnLineListener();
16052
+ this.closingLock = new Mutex();
16053
+ this.dataProcessLock = new Mutex();
16054
+ this.dcBufferStatus = new Map([[DataPacket_Kind.LOSSY, true], [DataPacket_Kind.RELIABLE, true]]);
16055
+ this.client.onParticipantUpdate = updates => this.emit(EngineEvent.ParticipantUpdate, updates);
16056
+ this.client.onConnectionQuality = update => this.emit(EngineEvent.ConnectionQualityUpdate, update);
16057
+ this.client.onRoomUpdate = update => this.emit(EngineEvent.RoomUpdate, update);
16058
+ this.client.onSubscriptionError = resp => this.emit(EngineEvent.SubscriptionError, resp);
16059
+ this.client.onSubscriptionPermissionUpdate = update => this.emit(EngineEvent.SubscriptionPermissionUpdate, update);
16060
+ this.client.onSpeakersChanged = update => this.emit(EngineEvent.SpeakersChanged, update);
16061
+ this.client.onStreamStateUpdate = update => this.emit(EngineEvent.StreamStateChanged, update);
16133
16062
  }
16134
- return encodingsFromPresets(width, height, [original]);
16135
- }
16136
- function computeTrackBackupEncodings(track, videoCodec, opts) {
16137
- var _a, _b, _c, _d;
16138
- // backupCodec should not be true anymore, default codec is set in LocalParticipant.publish
16139
- if (!opts.backupCodec || opts.backupCodec === true || opts.backupCodec.codec === opts.videoCodec) {
16140
- // backup codec publishing is disabled
16141
- return;
16063
+ /** @internal */
16064
+ get logContext() {
16065
+ var _a, _b, _c, _d, _e, _f, _g, _h;
16066
+ return {
16067
+ room: (_b = (_a = this.latestJoinResponse) === null || _a === void 0 ? void 0 : _a.room) === null || _b === void 0 ? void 0 : _b.name,
16068
+ roomID: (_d = (_c = this.latestJoinResponse) === null || _c === void 0 ? void 0 : _c.room) === null || _d === void 0 ? void 0 : _d.sid,
16069
+ participant: (_f = (_e = this.latestJoinResponse) === null || _e === void 0 ? void 0 : _e.participant) === null || _f === void 0 ? void 0 : _f.identity,
16070
+ pID: (_h = (_g = this.latestJoinResponse) === null || _g === void 0 ? void 0 : _g.participant) === null || _h === void 0 ? void 0 : _h.sid
16071
+ };
16142
16072
  }
16143
- if (videoCodec !== opts.backupCodec.codec) {
16144
- livekitLogger.warn('requested a different codec than specified as backup', {
16145
- serverRequested: videoCodec,
16146
- backup: opts.backupCodec.codec
16073
+ join(url, token, opts, abortSignal) {
16074
+ return __awaiter(this, void 0, void 0, function* () {
16075
+ this.url = url;
16076
+ this.token = token;
16077
+ this.signalOpts = opts;
16078
+ this.maxJoinAttempts = opts.maxRetries;
16079
+ try {
16080
+ this.joinAttempts += 1;
16081
+ this.setupSignalClientCallbacks();
16082
+ const joinResponse = yield this.client.join(url, token, opts, abortSignal);
16083
+ this._isClosed = false;
16084
+ this.latestJoinResponse = joinResponse;
16085
+ this.subscriberPrimary = joinResponse.subscriberPrimary;
16086
+ if (!this.pcManager) {
16087
+ yield this.configure(joinResponse);
16088
+ }
16089
+ // create offer
16090
+ if (!this.subscriberPrimary) {
16091
+ this.negotiate();
16092
+ }
16093
+ this.clientConfiguration = joinResponse.clientConfiguration;
16094
+ return joinResponse;
16095
+ } catch (e) {
16096
+ if (e instanceof ConnectionError) {
16097
+ if (e.reason === 1 /* ConnectionErrorReason.ServerUnreachable */) {
16098
+ this.log.warn("Couldn't connect to server, attempt ".concat(this.joinAttempts, " of ").concat(this.maxJoinAttempts), this.logContext);
16099
+ if (this.joinAttempts < this.maxJoinAttempts) {
16100
+ return this.join(url, token, opts, abortSignal);
16101
+ }
16102
+ }
16103
+ }
16104
+ throw e;
16105
+ }
16147
16106
  });
16148
16107
  }
16149
- opts.videoCodec = videoCodec;
16150
- // use backup encoding setting as videoEncoding for backup codec publishing
16151
- opts.videoEncoding = opts.backupCodec.encoding;
16152
- const settings = track.mediaStreamTrack.getSettings();
16153
- const width = (_a = settings.width) !== null && _a !== void 0 ? _a : (_b = track.dimensions) === null || _b === void 0 ? void 0 : _b.width;
16154
- const height = (_c = settings.height) !== null && _c !== void 0 ? _c : (_d = track.dimensions) === null || _d === void 0 ? void 0 : _d.height;
16155
- const encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, width, height, opts);
16156
- return encodings;
16157
- }
16158
- /* @internal */
16159
- function determineAppropriateEncoding(isScreenShare, width, height, codec) {
16160
- const presets = presetsForResolution(isScreenShare, width, height);
16161
- let {
16162
- encoding
16163
- } = presets[0];
16164
- // handle portrait by swapping dimensions
16165
- const size = Math.max(width, height);
16166
- for (let i = 0; i < presets.length; i += 1) {
16167
- const preset = presets[i];
16168
- encoding = preset.encoding;
16169
- if (preset.width >= size) {
16170
- break;
16171
- }
16172
- }
16173
- // presets are based on the assumption of vp8 as a codec
16174
- // for other codecs we adjust the maxBitrate if no specific videoEncoding has been provided
16175
- // users should override these with ones that are optimized for their use case
16176
- // NOTE: SVC codec bitrates are inclusive of all scalability layers. while
16177
- // bitrate for non-SVC codecs does not include other simulcast layers.
16178
- if (codec) {
16179
- switch (codec) {
16180
- case 'av1':
16181
- encoding = Object.assign({}, encoding);
16182
- encoding.maxBitrate = encoding.maxBitrate * 0.7;
16183
- break;
16184
- case 'vp9':
16185
- encoding = Object.assign({}, encoding);
16186
- encoding.maxBitrate = encoding.maxBitrate * 0.85;
16187
- break;
16188
- }
16189
- }
16190
- return encoding;
16191
- }
16192
- /* @internal */
16193
- function presetsForResolution(isScreenShare, width, height) {
16194
- if (isScreenShare) {
16195
- return presetsScreenShare;
16196
- }
16197
- const aspect = width > height ? width / height : height / width;
16198
- if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
16199
- return presets169;
16200
- }
16201
- return presets43;
16202
- }
16203
- /* @internal */
16204
- function defaultSimulcastLayers(isScreenShare, original) {
16205
- if (isScreenShare) {
16206
- return computeDefaultScreenShareSimulcastPresets(original);
16207
- }
16208
- const {
16209
- width,
16210
- height
16211
- } = original;
16212
- const aspect = width > height ? width / height : height / width;
16213
- if (Math.abs(aspect - 16.0 / 9) < Math.abs(aspect - 4.0 / 3)) {
16214
- return defaultSimulcastPresets169;
16215
- }
16216
- return defaultSimulcastPresets43;
16217
- }
16218
- // presets should be ordered by low, medium, high
16219
- function encodingsFromPresets(width, height, presets) {
16220
- const encodings = [];
16221
- presets.forEach((preset, idx) => {
16222
- if (idx >= videoRids.length) {
16223
- return;
16224
- }
16225
- const size = Math.min(width, height);
16226
- const rid = videoRids[idx];
16227
- const encoding = {
16228
- rid,
16229
- scaleResolutionDownBy: Math.max(1, size / Math.min(preset.width, preset.height)),
16230
- maxBitrate: preset.encoding.maxBitrate
16231
- };
16232
- if (preset.encoding.maxFramerate) {
16233
- encoding.maxFramerate = preset.encoding.maxFramerate;
16234
- }
16235
- const canSetPriority = isFireFox() || idx === 0;
16236
- if (preset.encoding.priority && canSetPriority) {
16237
- encoding.priority = preset.encoding.priority;
16238
- encoding.networkPriority = preset.encoding.priority;
16239
- }
16240
- encodings.push(encoding);
16241
- });
16242
- // RN ios simulcast requires all same framerates.
16243
- if (isReactNative() && getReactNativeOs() === 'ios') {
16244
- let topFramerate = undefined;
16245
- encodings.forEach(encoding => {
16246
- if (!topFramerate) {
16247
- topFramerate = encoding.maxFramerate;
16248
- } else if (encoding.maxFramerate && encoding.maxFramerate > topFramerate) {
16249
- topFramerate = encoding.maxFramerate;
16108
+ close() {
16109
+ return __awaiter(this, void 0, void 0, function* () {
16110
+ const unlock = yield this.closingLock.lock();
16111
+ if (this.isClosed) {
16112
+ unlock();
16113
+ return;
16114
+ }
16115
+ try {
16116
+ this._isClosed = true;
16117
+ this.emit(EngineEvent.Closing);
16118
+ this.removeAllListeners();
16119
+ this.deregisterOnLineListener();
16120
+ this.clearPendingReconnect();
16121
+ yield this.cleanupPeerConnections();
16122
+ yield this.cleanupClient();
16123
+ } finally {
16124
+ unlock();
16250
16125
  }
16251
16126
  });
16252
- let notifyOnce = true;
16253
- encodings.forEach(encoding => {
16254
- var _a;
16255
- if (encoding.maxFramerate != topFramerate) {
16256
- if (notifyOnce) {
16257
- notifyOnce = false;
16258
- livekitLogger.info("Simulcast on iOS React-Native requires all encodings to share the same framerate.");
16127
+ }
16128
+ cleanupPeerConnections() {
16129
+ return __awaiter(this, void 0, void 0, function* () {
16130
+ var _a;
16131
+ yield (_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.close();
16132
+ this.pcManager = undefined;
16133
+ const dcCleanup = dc => {
16134
+ if (!dc) return;
16135
+ dc.close();
16136
+ dc.onbufferedamountlow = null;
16137
+ dc.onclose = null;
16138
+ dc.onclosing = null;
16139
+ dc.onerror = null;
16140
+ dc.onmessage = null;
16141
+ dc.onopen = null;
16142
+ };
16143
+ dcCleanup(this.lossyDC);
16144
+ dcCleanup(this.lossyDCSub);
16145
+ dcCleanup(this.reliableDC);
16146
+ dcCleanup(this.reliableDCSub);
16147
+ this.lossyDC = undefined;
16148
+ this.lossyDCSub = undefined;
16149
+ this.reliableDC = undefined;
16150
+ this.reliableDCSub = undefined;
16151
+ });
16152
+ }
16153
+ cleanupClient() {
16154
+ return __awaiter(this, void 0, void 0, function* () {
16155
+ yield this.client.close();
16156
+ this.client.resetCallbacks();
16157
+ });
16158
+ }
16159
+ addTrack(req) {
16160
+ if (this.pendingTrackResolvers[req.cid]) {
16161
+ throw new TrackInvalidError('a track with the same ID has already been published');
16162
+ }
16163
+ return new Promise((resolve, reject) => {
16164
+ const publicationTimeout = setTimeout(() => {
16165
+ delete this.pendingTrackResolvers[req.cid];
16166
+ reject(new ConnectionError('publication of local track timed out, no response from server'));
16167
+ }, 10000);
16168
+ this.pendingTrackResolvers[req.cid] = {
16169
+ resolve: info => {
16170
+ clearTimeout(publicationTimeout);
16171
+ resolve(info);
16172
+ },
16173
+ reject: () => {
16174
+ clearTimeout(publicationTimeout);
16175
+ reject(new Error('Cancelled publication by calling unpublish'));
16259
16176
  }
16260
- livekitLogger.info("Setting framerate of encoding \"".concat((_a = encoding.rid) !== null && _a !== void 0 ? _a : '', "\" to ").concat(topFramerate));
16261
- encoding.maxFramerate = topFramerate;
16262
- }
16177
+ };
16178
+ this.client.sendAddTrack(req);
16263
16179
  });
16264
16180
  }
16265
- return encodings;
16266
- }
16267
- /** @internal */
16268
- function sortPresets(presets) {
16269
- if (!presets) return;
16270
- return presets.sort((a, b) => {
16271
- const {
16272
- encoding: aEnc
16273
- } = a;
16274
- const {
16275
- encoding: bEnc
16276
- } = b;
16277
- if (aEnc.maxBitrate > bEnc.maxBitrate) {
16278
- return 1;
16279
- }
16280
- if (aEnc.maxBitrate < bEnc.maxBitrate) return -1;
16281
- if (aEnc.maxBitrate === bEnc.maxBitrate && aEnc.maxFramerate && bEnc.maxFramerate) {
16282
- return aEnc.maxFramerate > bEnc.maxFramerate ? 1 : -1;
16283
- }
16284
- return 0;
16285
- });
16286
- }
16287
- /** @internal */
16288
- class ScalabilityMode {
16289
- constructor(scalabilityMode) {
16290
- const results = scalabilityMode.match(/^L(\d)T(\d)(h|_KEY|_KEY_SHIFT){0,1}$/);
16291
- if (!results) {
16292
- throw new Error('invalid scalability mode');
16293
- }
16294
- this.spatial = parseInt(results[1]);
16295
- this.temporal = parseInt(results[2]);
16296
- if (results.length > 3) {
16297
- switch (results[3]) {
16298
- case 'h':
16299
- case '_KEY':
16300
- case '_KEY_SHIFT':
16301
- this.suffix = results[3];
16181
+ /**
16182
+ * Removes sender from PeerConnection, returning true if it was removed successfully
16183
+ * and a negotiation is necessary
16184
+ * @param sender
16185
+ * @returns
16186
+ */
16187
+ removeTrack(sender) {
16188
+ if (sender.track && this.pendingTrackResolvers[sender.track.id]) {
16189
+ const {
16190
+ reject
16191
+ } = this.pendingTrackResolvers[sender.track.id];
16192
+ if (reject) {
16193
+ reject();
16302
16194
  }
16195
+ delete this.pendingTrackResolvers[sender.track.id];
16196
+ }
16197
+ try {
16198
+ this.pcManager.removeTrack(sender);
16199
+ return true;
16200
+ } catch (e) {
16201
+ this.log.warn('failed to remove track', Object.assign(Object.assign({}, this.logContext), {
16202
+ error: e
16203
+ }));
16303
16204
  }
16205
+ return false;
16304
16206
  }
16305
- toString() {
16207
+ updateMuteStatus(trackSid, muted) {
16208
+ this.client.sendMuteTrack(trackSid, muted);
16209
+ }
16210
+ get dataSubscriberReadyState() {
16306
16211
  var _a;
16307
- return "L".concat(this.spatial, "T").concat(this.temporal).concat((_a = this.suffix) !== null && _a !== void 0 ? _a : '');
16212
+ return (_a = this.reliableDCSub) === null || _a === void 0 ? void 0 : _a.readyState;
16308
16213
  }
16309
- }
16310
-
16311
- const refreshSubscribedCodecAfterNewCodec = 5000;
16312
- class LocalVideoTrack extends LocalTrack {
16313
- /**
16314
- *
16315
- * @param mediaTrack
16316
- * @param constraints MediaTrackConstraints that are being used when restarting or reacquiring tracks
16317
- * @param userProvidedTrack Signals to the SDK whether or not the mediaTrack should be managed (i.e. released and reacquired) internally by the SDK
16318
- */
16319
- constructor(mediaTrack, constraints) {
16320
- let userProvidedTrack = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
16321
- let loggerOptions = arguments.length > 3 ? arguments[3] : undefined;
16322
- super(mediaTrack, Track.Kind.Video, constraints, userProvidedTrack, loggerOptions);
16323
- /* @internal */
16324
- this.simulcastCodecs = new Map();
16325
- this.monitorSender = () => __awaiter(this, void 0, void 0, function* () {
16326
- if (!this.sender) {
16327
- this._currentBitrate = 0;
16214
+ getConnectedServerAddress() {
16215
+ return __awaiter(this, void 0, void 0, function* () {
16216
+ var _a;
16217
+ return (_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.getConnectedAddress();
16218
+ });
16219
+ }
16220
+ /* @internal */
16221
+ setRegionUrlProvider(provider) {
16222
+ this.regionUrlProvider = provider;
16223
+ }
16224
+ configure(joinResponse) {
16225
+ return __awaiter(this, void 0, void 0, function* () {
16226
+ var _a;
16227
+ // already configured
16228
+ if (this.pcManager && this.pcManager.currentState !== PCTransportState.NEW) {
16328
16229
  return;
16329
16230
  }
16330
- let stats;
16331
- try {
16332
- stats = yield this.getSenderStats();
16333
- } catch (e) {
16334
- this.log.error('could not get audio sender stats', Object.assign(Object.assign({}, this.logContext), {
16335
- error: e
16231
+ this.participantSid = (_a = joinResponse.participant) === null || _a === void 0 ? void 0 : _a.sid;
16232
+ const rtcConfig = this.makeRTCConfiguration(joinResponse);
16233
+ this.pcManager = new PCTransportManager(rtcConfig, joinResponse.subscriberPrimary, this.loggerOptions);
16234
+ this.emit(EngineEvent.TransportsCreated, this.pcManager.publisher, this.pcManager.subscriber);
16235
+ this.pcManager.onIceCandidate = (candidate, target) => {
16236
+ this.client.sendIceCandidate(candidate, target);
16237
+ };
16238
+ this.pcManager.onPublisherOffer = offer => {
16239
+ this.client.sendOffer(offer);
16240
+ };
16241
+ this.pcManager.onDataChannel = this.handleDataChannel;
16242
+ this.pcManager.onStateChange = (connectionState, publisherState, subscriberState) => __awaiter(this, void 0, void 0, function* () {
16243
+ this.log.debug("primary PC state changed ".concat(connectionState), this.logContext);
16244
+ if (['closed', 'disconnected', 'failed'].includes(publisherState)) {
16245
+ // reset publisher connection promise
16246
+ this.publisherConnectionPromise = undefined;
16247
+ }
16248
+ if (connectionState === PCTransportState.CONNECTED) {
16249
+ const shouldEmit = this.pcState === PCState.New;
16250
+ this.pcState = PCState.Connected;
16251
+ if (shouldEmit) {
16252
+ this.emit(EngineEvent.Connected, joinResponse);
16253
+ }
16254
+ } else if (connectionState === PCTransportState.FAILED) {
16255
+ // on Safari, PeerConnection will switch to 'disconnected' during renegotiation
16256
+ if (this.pcState === PCState.Connected) {
16257
+ this.pcState = PCState.Disconnected;
16258
+ this.handleDisconnect('peerconnection failed', subscriberState === 'failed' ? ReconnectReason.RR_SUBSCRIBER_FAILED : ReconnectReason.RR_PUBLISHER_FAILED);
16259
+ }
16260
+ }
16261
+ // detect cases where both signal client and peer connection are severed and assume that user has lost network connection
16262
+ const isSignalSevered = this.client.isDisconnected || this.client.currentState === SignalConnectionState.RECONNECTING;
16263
+ const isPCSevered = [PCTransportState.FAILED, PCTransportState.CLOSING, PCTransportState.CLOSED].includes(connectionState);
16264
+ if (isSignalSevered && isPCSevered && !this._isClosed) {
16265
+ this.emit(EngineEvent.Offline);
16266
+ }
16267
+ });
16268
+ this.pcManager.onTrack = ev => {
16269
+ this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
16270
+ };
16271
+ this.createDataChannels();
16272
+ });
16273
+ }
16274
+ setupSignalClientCallbacks() {
16275
+ // configure signaling client
16276
+ this.client.onAnswer = sd => __awaiter(this, void 0, void 0, function* () {
16277
+ if (!this.pcManager) {
16278
+ return;
16279
+ }
16280
+ this.log.debug('received server answer', Object.assign(Object.assign({}, this.logContext), {
16281
+ RTCSdpType: sd.type
16282
+ }));
16283
+ yield this.pcManager.setPublisherAnswer(sd);
16284
+ });
16285
+ // add candidate on trickle
16286
+ this.client.onTrickle = (candidate, target) => {
16287
+ if (!this.pcManager) {
16288
+ return;
16289
+ }
16290
+ this.log.trace('got ICE candidate from peer', Object.assign(Object.assign({}, this.logContext), {
16291
+ candidate,
16292
+ target
16293
+ }));
16294
+ this.pcManager.addIceCandidate(candidate, target);
16295
+ };
16296
+ // when server creates an offer for the client
16297
+ this.client.onOffer = sd => __awaiter(this, void 0, void 0, function* () {
16298
+ if (!this.pcManager) {
16299
+ return;
16300
+ }
16301
+ const answer = yield this.pcManager.createSubscriberAnswerFromOffer(sd);
16302
+ this.client.sendAnswer(answer);
16303
+ });
16304
+ this.client.onLocalTrackPublished = res => {
16305
+ var _a;
16306
+ this.log.debug('received trackPublishedResponse', Object.assign(Object.assign({}, this.logContext), {
16307
+ cid: res.cid,
16308
+ track: (_a = res.track) === null || _a === void 0 ? void 0 : _a.sid
16309
+ }));
16310
+ if (!this.pendingTrackResolvers[res.cid]) {
16311
+ this.log.error("missing track resolver for ".concat(res.cid), Object.assign(Object.assign({}, this.logContext), {
16312
+ cid: res.cid
16336
16313
  }));
16337
16314
  return;
16338
16315
  }
16339
- const statsMap = new Map(stats.map(s => [s.rid, s]));
16340
- if (this.prevStats) {
16341
- let totalBitrate = 0;
16342
- statsMap.forEach((s, key) => {
16343
- var _a;
16344
- const prev = (_a = this.prevStats) === null || _a === void 0 ? void 0 : _a.get(key);
16345
- totalBitrate += computeBitrate(s, prev);
16346
- });
16347
- this._currentBitrate = totalBitrate;
16316
+ const {
16317
+ resolve
16318
+ } = this.pendingTrackResolvers[res.cid];
16319
+ delete this.pendingTrackResolvers[res.cid];
16320
+ resolve(res.track);
16321
+ };
16322
+ this.client.onLocalTrackUnpublished = response => {
16323
+ this.emit(EngineEvent.LocalTrackUnpublished, response);
16324
+ };
16325
+ this.client.onTokenRefresh = token => {
16326
+ this.token = token;
16327
+ };
16328
+ this.client.onRemoteMuteChanged = (trackSid, muted) => {
16329
+ this.emit(EngineEvent.RemoteMute, trackSid, muted);
16330
+ };
16331
+ this.client.onSubscribedQualityUpdate = update => {
16332
+ this.emit(EngineEvent.SubscribedQualityUpdate, update);
16333
+ };
16334
+ this.client.onClose = () => {
16335
+ this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED);
16336
+ };
16337
+ this.client.onLeave = leave => {
16338
+ if (leave === null || leave === void 0 ? void 0 : leave.canReconnect) {
16339
+ this.fullReconnectOnNext = true;
16340
+ // reconnect immediately instead of waiting for next attempt
16341
+ this.handleDisconnect(leaveReconnect);
16342
+ } else {
16343
+ this.emit(EngineEvent.Disconnected, leave === null || leave === void 0 ? void 0 : leave.reason);
16344
+ this.close();
16348
16345
  }
16349
- this.prevStats = statsMap;
16350
- });
16351
- this.senderLock = new Mutex();
16346
+ this.log.debug('client leave request', Object.assign(Object.assign({}, this.logContext), {
16347
+ reason: leave === null || leave === void 0 ? void 0 : leave.reason
16348
+ }));
16349
+ };
16352
16350
  }
16353
- get isSimulcast() {
16354
- if (this.sender && this.sender.getParameters().encodings.length > 1) {
16355
- return true;
16351
+ makeRTCConfiguration(serverResponse) {
16352
+ var _a;
16353
+ const rtcConfig = Object.assign({}, this.rtcConfig);
16354
+ if ((_a = this.signalOpts) === null || _a === void 0 ? void 0 : _a.e2eeEnabled) {
16355
+ this.log.debug('E2EE - setting up transports with insertable streams', this.logContext);
16356
+ // this makes sure that no data is sent before the transforms are ready
16357
+ // @ts-ignore
16358
+ rtcConfig.encodedInsertableStreams = true;
16356
16359
  }
16357
- return false;
16360
+ // update ICE servers before creating PeerConnection
16361
+ if (serverResponse.iceServers && !rtcConfig.iceServers) {
16362
+ const rtcIceServers = [];
16363
+ serverResponse.iceServers.forEach(iceServer => {
16364
+ const rtcIceServer = {
16365
+ urls: iceServer.urls
16366
+ };
16367
+ if (iceServer.username) rtcIceServer.username = iceServer.username;
16368
+ if (iceServer.credential) {
16369
+ rtcIceServer.credential = iceServer.credential;
16370
+ }
16371
+ rtcIceServers.push(rtcIceServer);
16372
+ });
16373
+ rtcConfig.iceServers = rtcIceServers;
16374
+ }
16375
+ if (serverResponse.clientConfiguration && serverResponse.clientConfiguration.forceRelay === ClientConfigSetting.ENABLED) {
16376
+ rtcConfig.iceTransportPolicy = 'relay';
16377
+ }
16378
+ // @ts-ignore
16379
+ rtcConfig.sdpSemantics = 'unified-plan';
16380
+ // @ts-ignore
16381
+ rtcConfig.continualGatheringPolicy = 'gather_continually';
16382
+ return rtcConfig;
16358
16383
  }
16359
- /* @internal */
16360
- startMonitor(signalClient) {
16361
- var _a;
16362
- this.signalClient = signalClient;
16363
- if (!isWeb()) {
16384
+ createDataChannels() {
16385
+ if (!this.pcManager) {
16364
16386
  return;
16365
16387
  }
16366
- // save original encodings
16367
- // TODO : merge simulcast tracks stats
16368
- const params = (_a = this.sender) === null || _a === void 0 ? void 0 : _a.getParameters();
16369
- if (params) {
16370
- this.encodings = params.encodings;
16388
+ // clear old data channel callbacks if recreate
16389
+ if (this.lossyDC) {
16390
+ this.lossyDC.onmessage = null;
16391
+ this.lossyDC.onerror = null;
16371
16392
  }
16372
- if (this.monitorInterval) {
16373
- return;
16393
+ if (this.reliableDC) {
16394
+ this.reliableDC.onmessage = null;
16395
+ this.reliableDC.onerror = null;
16374
16396
  }
16375
- this.monitorInterval = setInterval(() => {
16376
- this.monitorSender();
16377
- }, monitorFrequency);
16378
- }
16379
- stop() {
16380
- this._mediaStreamTrack.getConstraints();
16381
- this.simulcastCodecs.forEach(trackInfo => {
16382
- trackInfo.mediaStreamTrack.stop();
16397
+ // create data channels
16398
+ this.lossyDC = this.pcManager.createPublisherDataChannel(lossyDataChannel, {
16399
+ // will drop older packets that arrive
16400
+ ordered: true,
16401
+ maxRetransmits: 0
16383
16402
  });
16384
- super.stop();
16403
+ this.reliableDC = this.pcManager.createPublisherDataChannel(reliableDataChannel, {
16404
+ ordered: true
16405
+ });
16406
+ // also handle messages over the pub channel, for backwards compatibility
16407
+ this.lossyDC.onmessage = this.handleDataMessage;
16408
+ this.reliableDC.onmessage = this.handleDataMessage;
16409
+ // handle datachannel errors
16410
+ this.lossyDC.onerror = this.handleDataError;
16411
+ this.reliableDC.onerror = this.handleDataError;
16412
+ // set up dc buffer threshold, set to 64kB (otherwise 0 by default)
16413
+ this.lossyDC.bufferedAmountLowThreshold = 65535;
16414
+ this.reliableDC.bufferedAmountLowThreshold = 65535;
16415
+ // handle buffer amount low events
16416
+ this.lossyDC.onbufferedamountlow = this.handleBufferedAmountLow;
16417
+ this.reliableDC.onbufferedamountlow = this.handleBufferedAmountLow;
16385
16418
  }
16386
- pauseUpstream() {
16387
- const _super = Object.create(null, {
16388
- pauseUpstream: {
16389
- get: () => super.pauseUpstream
16419
+ createSender(track, opts, encodings) {
16420
+ return __awaiter(this, void 0, void 0, function* () {
16421
+ if (supportsTransceiver()) {
16422
+ const sender = yield this.createTransceiverRTCRtpSender(track, opts, encodings);
16423
+ return sender;
16424
+ }
16425
+ if (supportsAddTrack()) {
16426
+ this.log.warn('using add-track fallback', this.logContext);
16427
+ const sender = yield this.createRTCRtpSender(track.mediaStreamTrack);
16428
+ return sender;
16390
16429
  }
16430
+ throw new UnexpectedConnectionState('Required webRTC APIs not supported on this device');
16391
16431
  });
16432
+ }
16433
+ createSimulcastSender(track, simulcastTrack, opts, encodings) {
16392
16434
  return __awaiter(this, void 0, void 0, function* () {
16393
- var _a, e_1, _b, _c;
16394
- var _d;
16395
- yield _super.pauseUpstream.call(this);
16396
- try {
16397
- for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
16398
- _c = _g.value;
16399
- _e = false;
16400
- const sc = _c;
16401
- yield (_d = sc.sender) === null || _d === void 0 ? void 0 : _d.replaceTrack(null);
16402
- }
16403
- } catch (e_1_1) {
16404
- e_1 = {
16405
- error: e_1_1
16406
- };
16407
- } finally {
16408
- try {
16409
- if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
16410
- } finally {
16411
- if (e_1) throw e_1.error;
16412
- }
16435
+ // store RTCRtpSender
16436
+ if (supportsTransceiver()) {
16437
+ return this.createSimulcastTransceiverSender(track, simulcastTrack, opts, encodings);
16438
+ }
16439
+ if (supportsAddTrack()) {
16440
+ this.log.debug('using add-track fallback', this.logContext);
16441
+ return this.createRTCRtpSender(track.mediaStreamTrack);
16413
16442
  }
16443
+ throw new UnexpectedConnectionState('Cannot stream on this device');
16414
16444
  });
16415
16445
  }
16416
- resumeUpstream() {
16417
- const _super = Object.create(null, {
16418
- resumeUpstream: {
16419
- get: () => super.resumeUpstream
16446
+ createTransceiverRTCRtpSender(track, opts, encodings) {
16447
+ return __awaiter(this, void 0, void 0, function* () {
16448
+ if (!this.pcManager) {
16449
+ throw new UnexpectedConnectionState('publisher is closed');
16450
+ }
16451
+ const streams = [];
16452
+ if (track.mediaStream) {
16453
+ streams.push(track.mediaStream);
16454
+ }
16455
+ if (track instanceof LocalVideoTrack) {
16456
+ track.codec = opts.videoCodec;
16457
+ }
16458
+ const transceiverInit = {
16459
+ direction: 'sendonly',
16460
+ streams
16461
+ };
16462
+ if (encodings) {
16463
+ transceiverInit.sendEncodings = encodings;
16420
16464
  }
16465
+ // addTransceiver for react-native is async. web is synchronous, but await won't effect it.
16466
+ const transceiver = yield this.pcManager.addPublisherTransceiver(track.mediaStreamTrack, transceiverInit);
16467
+ return transceiver.sender;
16421
16468
  });
16469
+ }
16470
+ createSimulcastTransceiverSender(track, simulcastTrack, opts, encodings) {
16422
16471
  return __awaiter(this, void 0, void 0, function* () {
16423
- var _a, e_2, _b, _c;
16424
- var _d;
16425
- yield _super.resumeUpstream.call(this);
16426
- try {
16427
- for (var _e = true, _f = __asyncValues(this.simulcastCodecs.values()), _g; _g = yield _f.next(), _a = _g.done, !_a; _e = true) {
16428
- _c = _g.value;
16429
- _e = false;
16430
- const sc = _c;
16431
- yield (_d = sc.sender) === null || _d === void 0 ? void 0 : _d.replaceTrack(sc.mediaStreamTrack);
16432
- }
16433
- } catch (e_2_1) {
16434
- e_2 = {
16435
- error: e_2_1
16436
- };
16437
- } finally {
16438
- try {
16439
- if (!_e && !_a && (_b = _f.return)) yield _b.call(_f);
16440
- } finally {
16441
- if (e_2) throw e_2.error;
16442
- }
16472
+ if (!this.pcManager) {
16473
+ throw new UnexpectedConnectionState('publisher is closed');
16443
16474
  }
16475
+ const transceiverInit = {
16476
+ direction: 'sendonly'
16477
+ };
16478
+ if (encodings) {
16479
+ transceiverInit.sendEncodings = encodings;
16480
+ }
16481
+ // addTransceiver for react-native is async. web is synchronous, but await won't effect it.
16482
+ const transceiver = yield this.pcManager.addPublisherTransceiver(simulcastTrack.mediaStreamTrack, transceiverInit);
16483
+ if (!opts.videoCodec) {
16484
+ return;
16485
+ }
16486
+ track.setSimulcastTrackSender(opts.videoCodec, transceiver.sender);
16487
+ return transceiver.sender;
16444
16488
  });
16445
16489
  }
16446
- mute() {
16447
- const _super = Object.create(null, {
16448
- mute: {
16449
- get: () => super.mute
16490
+ createRTCRtpSender(track) {
16491
+ return __awaiter(this, void 0, void 0, function* () {
16492
+ if (!this.pcManager) {
16493
+ throw new UnexpectedConnectionState('publisher is closed');
16450
16494
  }
16495
+ return this.pcManager.addPublisherTrack(track);
16451
16496
  });
16497
+ }
16498
+ attemptReconnect(reason) {
16452
16499
  return __awaiter(this, void 0, void 0, function* () {
16453
- const unlock = yield this.muteLock.lock();
16500
+ var _a, _b, _c;
16501
+ if (this._isClosed) {
16502
+ return;
16503
+ }
16504
+ // guard for attempting reconnection multiple times while one attempt is still not finished
16505
+ if (this.attemptingReconnect) {
16506
+ livekitLogger.warn('already attempting reconnect, returning early', this.logContext);
16507
+ return;
16508
+ }
16509
+ if (((_a = this.clientConfiguration) === null || _a === void 0 ? void 0 : _a.resumeConnection) === ClientConfigSetting.DISABLED ||
16510
+ // signaling state could change to closed due to hardware sleep
16511
+ // those connections cannot be resumed
16512
+ ((_c = (_b = this.pcManager) === null || _b === void 0 ? void 0 : _b.currentState) !== null && _c !== void 0 ? _c : PCTransportState.NEW) === PCTransportState.NEW) {
16513
+ this.fullReconnectOnNext = true;
16514
+ }
16454
16515
  try {
16455
- if (this.isMuted) {
16456
- this.log.debug('Track already muted', this.logContext);
16457
- return this;
16516
+ this.attemptingReconnect = true;
16517
+ if (this.fullReconnectOnNext) {
16518
+ yield this.restartConnection();
16519
+ } else {
16520
+ yield this.resumeConnection(reason);
16458
16521
  }
16459
- if (this.source === Track.Source.Camera && !this.isUserProvided) {
16460
- this.log.debug('stopping camera track', this.logContext);
16461
- // also stop the track, so that camera indicator is turned off
16462
- this._mediaStreamTrack.stop();
16522
+ this.clearPendingReconnect();
16523
+ this.fullReconnectOnNext = false;
16524
+ } catch (e) {
16525
+ this.reconnectAttempts += 1;
16526
+ let recoverable = true;
16527
+ if (e instanceof UnexpectedConnectionState) {
16528
+ this.log.debug('received unrecoverable error', Object.assign(Object.assign({}, this.logContext), {
16529
+ error: e
16530
+ }));
16531
+ // unrecoverable
16532
+ recoverable = false;
16533
+ } else if (!(e instanceof SignalReconnectError)) {
16534
+ // cannot resume
16535
+ this.fullReconnectOnNext = true;
16536
+ }
16537
+ if (recoverable) {
16538
+ this.handleDisconnect('reconnect', ReconnectReason.RR_UNKNOWN);
16539
+ } else {
16540
+ this.log.info("could not recover connection after ".concat(this.reconnectAttempts, " attempts, ").concat(Date.now() - this.reconnectStart, "ms. giving up"), this.logContext);
16541
+ this.emit(EngineEvent.Disconnected);
16542
+ yield this.close();
16463
16543
  }
16464
- yield _super.mute.call(this);
16465
- return this;
16466
16544
  } finally {
16467
- unlock();
16545
+ this.attemptingReconnect = false;
16468
16546
  }
16469
16547
  });
16470
16548
  }
16471
- unmute() {
16472
- const _super = Object.create(null, {
16473
- unmute: {
16474
- get: () => super.unmute
16475
- }
16476
- });
16549
+ getNextRetryDelay(context) {
16550
+ try {
16551
+ return this.reconnectPolicy.nextRetryDelayInMs(context);
16552
+ } catch (e) {
16553
+ this.log.warn('encountered error in reconnect policy', Object.assign(Object.assign({}, this.logContext), {
16554
+ error: e
16555
+ }));
16556
+ }
16557
+ // error in user code with provided reconnect policy, stop reconnecting
16558
+ return null;
16559
+ }
16560
+ restartConnection(regionUrl) {
16477
16561
  return __awaiter(this, void 0, void 0, function* () {
16478
- const unlock = yield this.muteLock.lock();
16562
+ var _a, _b, _c;
16479
16563
  try {
16480
- if (!this.isMuted) {
16481
- this.log.debug('Track already unmuted', this.logContext);
16482
- return this;
16564
+ if (!this.url || !this.token) {
16565
+ // permanent failure, don't attempt reconnection
16566
+ throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
16483
16567
  }
16484
- if (this.source === Track.Source.Camera && !this.isUserProvided) {
16485
- this.log.debug('reacquiring camera track', this.logContext);
16486
- yield this.restartTrack();
16568
+ this.log.info("reconnecting, attempt: ".concat(this.reconnectAttempts), this.logContext);
16569
+ this.emit(EngineEvent.Restarting);
16570
+ if (!this.client.isDisconnected) {
16571
+ yield this.client.sendLeave();
16572
+ }
16573
+ yield this.cleanupPeerConnections();
16574
+ yield this.cleanupClient();
16575
+ let joinResponse;
16576
+ try {
16577
+ if (!this.signalOpts) {
16578
+ this.log.warn('attempted connection restart, without signal options present', this.logContext);
16579
+ throw new SignalReconnectError();
16580
+ }
16581
+ // in case a regionUrl is passed, the region URL takes precedence
16582
+ joinResponse = yield this.join(regionUrl !== null && regionUrl !== void 0 ? regionUrl : this.url, this.token, this.signalOpts);
16583
+ } catch (e) {
16584
+ if (e instanceof ConnectionError && e.reason === 0 /* ConnectionErrorReason.NotAllowed */) {
16585
+ throw new UnexpectedConnectionState('could not reconnect, token might be expired');
16586
+ }
16587
+ throw new SignalReconnectError();
16588
+ }
16589
+ if (this.shouldFailNext) {
16590
+ this.shouldFailNext = false;
16591
+ throw new Error('simulated failure');
16592
+ }
16593
+ this.client.setReconnected();
16594
+ this.emit(EngineEvent.SignalRestarted, joinResponse);
16595
+ yield this.waitForPCReconnected();
16596
+ // re-check signal connection state before setting engine as resumed
16597
+ if (this.client.currentState !== SignalConnectionState.CONNECTED) {
16598
+ throw new SignalReconnectError('Signal connection got severed during reconnect');
16599
+ }
16600
+ (_a = this.regionUrlProvider) === null || _a === void 0 ? void 0 : _a.resetAttempts();
16601
+ // reconnect success
16602
+ this.emit(EngineEvent.Restarted);
16603
+ } catch (error) {
16604
+ const nextRegionUrl = yield (_b = this.regionUrlProvider) === null || _b === void 0 ? void 0 : _b.getNextBestRegionUrl();
16605
+ if (nextRegionUrl) {
16606
+ yield this.restartConnection(nextRegionUrl);
16607
+ return;
16608
+ } else {
16609
+ // no more regions to try (or we're not on cloud)
16610
+ (_c = this.regionUrlProvider) === null || _c === void 0 ? void 0 : _c.resetAttempts();
16611
+ throw error;
16487
16612
  }
16488
- yield _super.unmute.call(this);
16489
- return this;
16490
- } finally {
16491
- unlock();
16492
16613
  }
16493
16614
  });
16494
16615
  }
16495
- setTrackMuted(muted) {
16496
- super.setTrackMuted(muted);
16497
- for (const sc of this.simulcastCodecs.values()) {
16498
- sc.mediaStreamTrack.enabled = !muted;
16499
- }
16500
- }
16501
- getSenderStats() {
16616
+ resumeConnection(reason) {
16502
16617
  return __awaiter(this, void 0, void 0, function* () {
16503
16618
  var _a;
16504
- if (!((_a = this.sender) === null || _a === void 0 ? void 0 : _a.getStats)) {
16505
- return [];
16619
+ if (!this.url || !this.token) {
16620
+ // permanent failure, don't attempt reconnection
16621
+ throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
16506
16622
  }
16507
- const items = [];
16508
- const stats = yield this.sender.getStats();
16509
- stats.forEach(v => {
16510
- var _a;
16511
- if (v.type === 'outbound-rtp') {
16512
- const vs = {
16513
- type: 'video',
16514
- streamId: v.id,
16515
- frameHeight: v.frameHeight,
16516
- frameWidth: v.frameWidth,
16517
- framesPerSecond: v.framesPerSecond,
16518
- framesSent: v.framesSent,
16519
- firCount: v.firCount,
16520
- pliCount: v.pliCount,
16521
- nackCount: v.nackCount,
16522
- packetsSent: v.packetsSent,
16523
- bytesSent: v.bytesSent,
16524
- qualityLimitationReason: v.qualityLimitationReason,
16525
- qualityLimitationDurations: v.qualityLimitationDurations,
16526
- qualityLimitationResolutionChanges: v.qualityLimitationResolutionChanges,
16527
- rid: (_a = v.rid) !== null && _a !== void 0 ? _a : v.id,
16528
- retransmittedPacketsSent: v.retransmittedPacketsSent,
16529
- targetBitrate: v.targetBitrate,
16530
- timestamp: v.timestamp
16531
- };
16532
- // locate the appropriate remote-inbound-rtp item
16533
- const r = stats.get(v.remoteId);
16534
- if (r) {
16535
- vs.jitter = r.jitter;
16536
- vs.packetsLost = r.packetsLost;
16537
- vs.roundTripTime = r.roundTripTime;
16538
- }
16539
- items.push(vs);
16623
+ // trigger publisher reconnect
16624
+ if (!this.pcManager) {
16625
+ throw new UnexpectedConnectionState('publisher and subscriber connections unset');
16626
+ }
16627
+ this.log.info("resuming signal connection, attempt ".concat(this.reconnectAttempts), this.logContext);
16628
+ this.emit(EngineEvent.Resuming);
16629
+ let res;
16630
+ try {
16631
+ this.setupSignalClientCallbacks();
16632
+ res = yield this.client.reconnect(this.url, this.token, this.participantSid, reason);
16633
+ } catch (error) {
16634
+ let message = '';
16635
+ if (error instanceof Error) {
16636
+ message = error.message;
16637
+ this.log.error(error.message, Object.assign(Object.assign({}, this.logContext), {
16638
+ error
16639
+ }));
16540
16640
  }
16541
- });
16542
- // make sure highest res layer is always first
16543
- items.sort((a, b) => {
16544
- var _a, _b;
16545
- return ((_a = b.frameWidth) !== null && _a !== void 0 ? _a : 0) - ((_b = a.frameWidth) !== null && _b !== void 0 ? _b : 0);
16546
- });
16547
- return items;
16641
+ if (error instanceof ConnectionError && error.reason === 0 /* ConnectionErrorReason.NotAllowed */) {
16642
+ throw new UnexpectedConnectionState('could not reconnect, token might be expired');
16643
+ }
16644
+ if (error instanceof ConnectionError && error.reason === 4 /* ConnectionErrorReason.LeaveRequest */) {
16645
+ throw error;
16646
+ }
16647
+ throw new SignalReconnectError(message);
16648
+ }
16649
+ this.emit(EngineEvent.SignalResumed);
16650
+ if (res) {
16651
+ const rtcConfig = this.makeRTCConfiguration(res);
16652
+ this.pcManager.updateConfiguration(rtcConfig);
16653
+ } else {
16654
+ this.log.warn('Did not receive reconnect response', this.logContext);
16655
+ }
16656
+ if (this.shouldFailNext) {
16657
+ this.shouldFailNext = false;
16658
+ throw new Error('simulated failure');
16659
+ }
16660
+ yield this.pcManager.triggerIceRestart();
16661
+ yield this.waitForPCReconnected();
16662
+ // re-check signal connection state before setting engine as resumed
16663
+ if (this.client.currentState !== SignalConnectionState.CONNECTED) {
16664
+ throw new SignalReconnectError('Signal connection got severed during reconnect');
16665
+ }
16666
+ this.client.setReconnected();
16667
+ // recreate publish datachannel if it's id is null
16668
+ // (for safari https://bugs.webkit.org/show_bug.cgi?id=184688)
16669
+ if (((_a = this.reliableDC) === null || _a === void 0 ? void 0 : _a.readyState) === 'open' && this.reliableDC.id === null) {
16670
+ this.createDataChannels();
16671
+ }
16672
+ // resume success
16673
+ this.emit(EngineEvent.Resumed);
16548
16674
  });
16549
16675
  }
16550
- setPublishingQuality(maxQuality) {
16551
- const qualities = [];
16552
- for (let q = VideoQuality.LOW; q <= VideoQuality.HIGH; q += 1) {
16553
- qualities.push(new SubscribedQuality({
16554
- quality: q,
16555
- enabled: q <= maxQuality
16556
- }));
16557
- }
16558
- this.log.debug("setting publishing quality. max quality ".concat(maxQuality), this.logContext);
16559
- this.setPublishingLayers(qualities);
16560
- }
16561
- setDeviceId(deviceId) {
16676
+ waitForPCInitialConnection(timeout, abortController) {
16562
16677
  return __awaiter(this, void 0, void 0, function* () {
16563
- if (this._constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)) {
16564
- return true;
16565
- }
16566
- this._constraints.deviceId = deviceId;
16567
- // when video is muted, underlying media stream track is stopped and
16568
- // will be restarted later
16569
- if (!this.isMuted) {
16570
- yield this.restartTrack();
16678
+ if (!this.pcManager) {
16679
+ throw new UnexpectedConnectionState('PC manager is closed');
16571
16680
  }
16572
- return this.isMuted || unwrapConstraint(deviceId) === this._mediaStreamTrack.getSettings().deviceId;
16681
+ yield this.pcManager.ensurePCTransportConnection(abortController, timeout);
16573
16682
  });
16574
16683
  }
16575
- restartTrack(options) {
16684
+ waitForPCReconnected() {
16576
16685
  return __awaiter(this, void 0, void 0, function* () {
16577
- var _a, e_3, _b, _c;
16578
- let constraints;
16579
- if (options) {
16580
- const streamConstraints = constraintsForOptions({
16581
- video: options
16582
- });
16583
- if (typeof streamConstraints.video !== 'boolean') {
16584
- constraints = streamConstraints.video;
16585
- }
16586
- }
16587
- yield this.restart(constraints);
16686
+ this.pcState = PCState.Reconnecting;
16687
+ this.log.debug('waiting for peer connection to reconnect', this.logContext);
16588
16688
  try {
16589
- for (var _d = true, _e = __asyncValues(this.simulcastCodecs.values()), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
16590
- _c = _f.value;
16591
- _d = false;
16592
- const sc = _c;
16593
- if (sc.sender) {
16594
- sc.mediaStreamTrack = this.mediaStreamTrack.clone();
16595
- yield sc.sender.replaceTrack(sc.mediaStreamTrack);
16596
- }
16597
- }
16598
- } catch (e_3_1) {
16599
- e_3 = {
16600
- error: e_3_1
16601
- };
16602
- } finally {
16603
- try {
16604
- if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
16605
- } finally {
16606
- if (e_3) throw e_3.error;
16689
+ yield sleep(minReconnectWait); // FIXME setTimeout again not ideal for a connection critical path
16690
+ if (!this.pcManager) {
16691
+ throw new UnexpectedConnectionState('PC manager is closed');
16607
16692
  }
16693
+ yield this.pcManager.ensurePCTransportConnection(undefined, this.peerConnectionTimeout);
16694
+ this.pcState = PCState.Connected;
16695
+ } catch (e) {
16696
+ // TODO do we need a `failed` state here for the PC?
16697
+ this.pcState = PCState.Disconnected;
16698
+ throw new ConnectionError("could not establish PC connection, ".concat(e.message));
16608
16699
  }
16609
16700
  });
16610
16701
  }
16611
- setProcessor(processor_1) {
16612
- const _super = Object.create(null, {
16613
- setProcessor: {
16614
- get: () => super.setProcessor
16702
+ /* @internal */
16703
+ sendDataPacket(packet, kind) {
16704
+ return __awaiter(this, void 0, void 0, function* () {
16705
+ const msg = packet.toBinary();
16706
+ // make sure we do have a data connection
16707
+ yield this.ensurePublisherConnected(kind);
16708
+ const dc = this.dataChannelForKind(kind);
16709
+ if (dc) {
16710
+ dc.send(msg);
16615
16711
  }
16712
+ this.updateAndEmitDCBufferStatus(kind);
16616
16713
  });
16617
- return __awaiter(this, arguments, void 0, function (processor) {
16618
- var _this = this;
16619
- let showProcessedStreamLocally = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
16714
+ }
16715
+ /**
16716
+ * @internal
16717
+ */
16718
+ ensureDataTransportConnected(kind_1) {
16719
+ return __awaiter(this, arguments, void 0, function (kind) {
16720
+ var _this2 = this;
16721
+ let subscriber = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.subscriberPrimary;
16620
16722
  return function* () {
16621
- var _a, e_4, _b, _c;
16622
- var _d, _e;
16623
- yield _super.setProcessor.call(_this, processor, showProcessedStreamLocally);
16624
- if ((_d = _this.processor) === null || _d === void 0 ? void 0 : _d.processedTrack) {
16625
- try {
16626
- for (var _f = true, _g = __asyncValues(_this.simulcastCodecs.values()), _h; _h = yield _g.next(), _a = _h.done, !_a; _f = true) {
16627
- _c = _h.value;
16628
- _f = false;
16629
- const sc = _c;
16630
- yield (_e = sc.sender) === null || _e === void 0 ? void 0 : _e.replaceTrack(_this.processor.processedTrack);
16631
- }
16632
- } catch (e_4_1) {
16633
- e_4 = {
16634
- error: e_4_1
16635
- };
16636
- } finally {
16637
- try {
16638
- if (!_f && !_a && (_b = _g.return)) yield _b.call(_g);
16639
- } finally {
16640
- if (e_4) throw e_4.error;
16641
- }
16723
+ var _a;
16724
+ if (!_this2.pcManager) {
16725
+ throw new UnexpectedConnectionState('PC manager is closed');
16726
+ }
16727
+ const transport = subscriber ? _this2.pcManager.subscriber : _this2.pcManager.publisher;
16728
+ const transportName = subscriber ? 'Subscriber' : 'Publisher';
16729
+ if (!transport) {
16730
+ throw new ConnectionError("".concat(transportName, " connection not set"));
16731
+ }
16732
+ if (!subscriber && !_this2.pcManager.publisher.isICEConnected && _this2.pcManager.publisher.getICEConnectionState() !== 'checking') {
16733
+ // start negotiation
16734
+ _this2.negotiate();
16735
+ }
16736
+ const targetChannel = _this2.dataChannelForKind(kind, subscriber);
16737
+ if ((targetChannel === null || targetChannel === void 0 ? void 0 : targetChannel.readyState) === 'open') {
16738
+ return;
16739
+ }
16740
+ // wait until ICE connected
16741
+ const endTime = new Date().getTime() + _this2.peerConnectionTimeout;
16742
+ while (new Date().getTime() < endTime) {
16743
+ if (transport.isICEConnected && ((_a = _this2.dataChannelForKind(kind, subscriber)) === null || _a === void 0 ? void 0 : _a.readyState) === 'open') {
16744
+ return;
16642
16745
  }
16746
+ yield sleep(50);
16643
16747
  }
16748
+ throw new ConnectionError("could not establish ".concat(transportName, " connection, state: ").concat(transport.getICEConnectionState()));
16644
16749
  }();
16645
16750
  });
16646
16751
  }
16647
- addSimulcastTrack(codec, encodings) {
16648
- if (this.simulcastCodecs.has(codec)) {
16649
- this.log.error("".concat(codec, " already added, skipping adding simulcast codec"), this.logContext);
16650
- return;
16651
- }
16652
- const simulcastCodecInfo = {
16653
- codec,
16654
- mediaStreamTrack: this.mediaStreamTrack.clone(),
16655
- sender: undefined,
16656
- encodings
16657
- };
16658
- this.simulcastCodecs.set(codec, simulcastCodecInfo);
16659
- return simulcastCodecInfo;
16752
+ ensurePublisherConnected(kind) {
16753
+ return __awaiter(this, void 0, void 0, function* () {
16754
+ if (!this.publisherConnectionPromise) {
16755
+ this.publisherConnectionPromise = this.ensureDataTransportConnected(kind, false);
16756
+ }
16757
+ yield this.publisherConnectionPromise;
16758
+ });
16660
16759
  }
16661
- setSimulcastTrackSender(codec, sender) {
16662
- const simulcastCodecInfo = this.simulcastCodecs.get(codec);
16663
- if (!simulcastCodecInfo) {
16664
- return;
16760
+ /* @internal */
16761
+ verifyTransport() {
16762
+ if (!this.pcManager) {
16763
+ return false;
16665
16764
  }
16666
- simulcastCodecInfo.sender = sender;
16667
- // browser will reenable disabled codec/layers after new codec has been published,
16668
- // so refresh subscribedCodecs after publish a new codec
16669
- setTimeout(() => {
16670
- if (this.subscribedCodecs) {
16671
- this.setPublishingCodecs(this.subscribedCodecs);
16672
- }
16673
- }, refreshSubscribedCodecAfterNewCodec);
16765
+ // primary connection
16766
+ if (this.pcManager.currentState !== PCTransportState.CONNECTED) {
16767
+ return false;
16768
+ }
16769
+ // ensure signal is connected
16770
+ if (!this.client.ws || this.client.ws.readyState === WebSocket.CLOSED) {
16771
+ return false;
16772
+ }
16773
+ return true;
16674
16774
  }
16675
- /**
16676
- * @internal
16677
- * Sets codecs that should be publishing, returns new codecs that have not yet
16678
- * been published
16679
- */
16680
- setPublishingCodecs(codecs) {
16775
+ /** @internal */
16776
+ negotiate() {
16681
16777
  return __awaiter(this, void 0, void 0, function* () {
16682
- var _a, codecs_1, codecs_1_1;
16683
- var _b, e_5, _c, _d;
16684
- this.log.debug('setting publishing codecs', Object.assign(Object.assign({}, this.logContext), {
16685
- codecs,
16686
- currentCodec: this.codec
16687
- }));
16688
- // only enable simulcast codec for preference codec setted
16689
- if (!this.codec && codecs.length > 0) {
16690
- yield this.setPublishingLayers(codecs[0].qualities);
16691
- return [];
16692
- }
16693
- this.subscribedCodecs = codecs;
16694
- const newCodecs = [];
16695
- try {
16696
- for (_a = true, codecs_1 = __asyncValues(codecs); codecs_1_1 = yield codecs_1.next(), _b = codecs_1_1.done, !_b; _a = true) {
16697
- _d = codecs_1_1.value;
16698
- _a = false;
16699
- const codec = _d;
16700
- if (!this.codec || this.codec === codec.codec) {
16701
- yield this.setPublishingLayers(codec.qualities);
16702
- } else {
16703
- const simulcastCodecInfo = this.simulcastCodecs.get(codec.codec);
16704
- this.log.debug("try setPublishingCodec for ".concat(codec.codec), Object.assign(Object.assign({}, this.logContext), {
16705
- simulcastCodecInfo
16706
- }));
16707
- if (!simulcastCodecInfo || !simulcastCodecInfo.sender) {
16708
- for (const q of codec.qualities) {
16709
- if (q.enabled) {
16710
- newCodecs.push(codec.codec);
16711
- break;
16712
- }
16713
- }
16714
- } else if (simulcastCodecInfo.encodings) {
16715
- this.log.debug("try setPublishingLayersForSender ".concat(codec.codec), this.logContext);
16716
- yield setPublishingLayersForSender(simulcastCodecInfo.sender, simulcastCodecInfo.encodings, codec.qualities, this.senderLock, this.log, this.logContext);
16717
- }
16718
- }
16778
+ // observe signal state
16779
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
16780
+ if (!this.pcManager) {
16781
+ reject(new NegotiationError('PC manager is closed'));
16782
+ return;
16719
16783
  }
16720
- } catch (e_5_1) {
16721
- e_5 = {
16722
- error: e_5_1
16784
+ this.pcManager.requirePublisher();
16785
+ const abortController = new AbortController();
16786
+ const handleClosed = () => {
16787
+ abortController.abort();
16788
+ this.log.debug('engine disconnected while negotiation was ongoing', this.logContext);
16789
+ resolve();
16790
+ return;
16723
16791
  };
16724
- } finally {
16792
+ if (this.isClosed) {
16793
+ reject('cannot negotiate on closed engine');
16794
+ }
16795
+ this.on(EngineEvent.Closing, handleClosed);
16796
+ this.pcManager.publisher.once(PCEvents.RTPVideoPayloadTypes, rtpTypes => {
16797
+ const rtpMap = new Map();
16798
+ rtpTypes.forEach(rtp => {
16799
+ const codec = rtp.codec.toLowerCase();
16800
+ if (isVideoCodec(codec)) {
16801
+ rtpMap.set(rtp.payload, codec);
16802
+ }
16803
+ });
16804
+ this.emit(EngineEvent.RTPVideoMapUpdate, rtpMap);
16805
+ });
16725
16806
  try {
16726
- if (!_a && !_b && (_c = codecs_1.return)) yield _c.call(codecs_1);
16807
+ yield this.pcManager.negotiate(abortController);
16808
+ resolve();
16809
+ } catch (e) {
16810
+ if (e instanceof NegotiationError) {
16811
+ this.fullReconnectOnNext = true;
16812
+ }
16813
+ this.handleDisconnect('negotiation', ReconnectReason.RR_UNKNOWN);
16814
+ reject(e);
16727
16815
  } finally {
16728
- if (e_5) throw e_5.error;
16816
+ this.off(EngineEvent.Closing, handleClosed);
16729
16817
  }
16730
- }
16731
- return newCodecs;
16818
+ }));
16732
16819
  });
16733
16820
  }
16734
- /**
16735
- * @internal
16736
- * Sets layers that should be publishing
16737
- */
16738
- setPublishingLayers(qualities) {
16739
- return __awaiter(this, void 0, void 0, function* () {
16740
- this.log.debug('setting publishing layers', Object.assign(Object.assign({}, this.logContext), {
16741
- qualities
16742
- }));
16743
- if (!this.sender || !this.encodings) {
16744
- return;
16821
+ dataChannelForKind(kind, sub) {
16822
+ if (!sub) {
16823
+ if (kind === DataPacket_Kind.LOSSY) {
16824
+ return this.lossyDC;
16745
16825
  }
16746
- yield setPublishingLayersForSender(this.sender, this.encodings, qualities, this.senderLock, this.log, this.logContext);
16747
- });
16826
+ if (kind === DataPacket_Kind.RELIABLE) {
16827
+ return this.reliableDC;
16828
+ }
16829
+ } else {
16830
+ if (kind === DataPacket_Kind.LOSSY) {
16831
+ return this.lossyDCSub;
16832
+ }
16833
+ if (kind === DataPacket_Kind.RELIABLE) {
16834
+ return this.reliableDCSub;
16835
+ }
16836
+ }
16748
16837
  }
16749
- handleAppVisibilityChanged() {
16750
- const _super = Object.create(null, {
16751
- handleAppVisibilityChanged: {
16752
- get: () => super.handleAppVisibilityChanged
16838
+ /** @internal */
16839
+ sendSyncState(remoteTracks, localTracks) {
16840
+ var _a, _b;
16841
+ if (!this.pcManager) {
16842
+ this.log.warn('sync state cannot be sent without peer connection setup', this.logContext);
16843
+ return;
16844
+ }
16845
+ const previousAnswer = this.pcManager.subscriber.getLocalDescription();
16846
+ const previousOffer = this.pcManager.subscriber.getRemoteDescription();
16847
+ /* 1. autosubscribe on, so subscribed tracks = all tracks - unsub tracks,
16848
+ in this case, we send unsub tracks, so server add all tracks to this
16849
+ subscribe pc and unsub special tracks from it.
16850
+ 2. autosubscribe off, we send subscribed tracks.
16851
+ */
16852
+ const autoSubscribe = (_b = (_a = this.signalOpts) === null || _a === void 0 ? void 0 : _a.autoSubscribe) !== null && _b !== void 0 ? _b : true;
16853
+ const trackSids = new Array();
16854
+ const trackSidsDisabled = new Array();
16855
+ remoteTracks.forEach(track => {
16856
+ if (track.isDesired !== autoSubscribe) {
16857
+ trackSids.push(track.trackSid);
16753
16858
  }
16754
- });
16755
- return __awaiter(this, void 0, void 0, function* () {
16756
- yield _super.handleAppVisibilityChanged.call(this);
16757
- if (!isMobile()) return;
16758
- if (this.isInBackground && this.source === Track.Source.Camera) {
16759
- this._mediaStreamTrack.enabled = false;
16859
+ if (!track.isEnabled) {
16860
+ trackSidsDisabled.push(track.trackSid);
16760
16861
  }
16761
16862
  });
16762
- }
16763
- }
16764
- function setPublishingLayersForSender(sender, senderEncodings, qualities, senderLock, log, logContext) {
16765
- return __awaiter(this, void 0, void 0, function* () {
16766
- const unlock = yield senderLock.lock();
16767
- log.debug('setPublishingLayersForSender', Object.assign(Object.assign({}, logContext), {
16768
- sender,
16769
- qualities,
16770
- senderEncodings
16863
+ this.client.sendSyncState(new SyncState({
16864
+ answer: previousAnswer ? toProtoSessionDescription({
16865
+ sdp: previousAnswer.sdp,
16866
+ type: previousAnswer.type
16867
+ }) : undefined,
16868
+ offer: previousOffer ? toProtoSessionDescription({
16869
+ sdp: previousOffer.sdp,
16870
+ type: previousOffer.type
16871
+ }) : undefined,
16872
+ subscription: new UpdateSubscription({
16873
+ trackSids,
16874
+ subscribe: !autoSubscribe,
16875
+ participantTracks: []
16876
+ }),
16877
+ publishTracks: getTrackPublicationInfo(localTracks),
16878
+ dataChannels: this.dataChannelsInfo(),
16879
+ trackSidsDisabled
16771
16880
  }));
16772
- try {
16773
- const params = sender.getParameters();
16774
- const {
16775
- encodings
16776
- } = params;
16777
- if (!encodings) {
16778
- return;
16779
- }
16780
- if (encodings.length !== senderEncodings.length) {
16781
- log.warn('cannot set publishing layers, encodings mismatch', Object.assign(Object.assign({}, logContext), {
16782
- encodings,
16783
- senderEncodings
16784
- }));
16785
- return;
16786
- }
16787
- let hasChanged = false;
16788
- /* disable closable spatial layer as it has video blur / frozen issue with current server / client
16789
- 1. chrome 113: when switching to up layer with scalability Mode change, it will generate a
16790
- low resolution frame and recover very quickly, but noticable
16791
- 2. livekit sfu: additional pli request cause video frozen for a few frames, also noticable */
16792
- const closableSpatial = false;
16793
- /* @ts-ignore */
16794
- if (closableSpatial && encodings[0].scalabilityMode) ; else {
16795
- // simulcast dynacast encodings
16796
- encodings.forEach((encoding, idx) => {
16797
- var _a;
16798
- let rid = (_a = encoding.rid) !== null && _a !== void 0 ? _a : '';
16799
- if (rid === '') {
16800
- rid = 'q';
16801
- }
16802
- const quality = videoQualityForRid(rid);
16803
- const subscribedQuality = qualities.find(q => q.quality === quality);
16804
- if (!subscribedQuality) {
16805
- return;
16806
- }
16807
- if (encoding.active !== subscribedQuality.enabled) {
16808
- hasChanged = true;
16809
- encoding.active = subscribedQuality.enabled;
16810
- log.debug("setting layer ".concat(subscribedQuality.quality, " to ").concat(encoding.active ? 'enabled' : 'disabled'), logContext);
16811
- // FireFox does not support setting encoding.active to false, so we
16812
- // have a workaround of lowering its bitrate and resolution to the min.
16813
- if (isFireFox()) {
16814
- if (subscribedQuality.enabled) {
16815
- encoding.scaleResolutionDownBy = senderEncodings[idx].scaleResolutionDownBy;
16816
- encoding.maxBitrate = senderEncodings[idx].maxBitrate;
16817
- /* @ts-ignore */
16818
- encoding.maxFrameRate = senderEncodings[idx].maxFrameRate;
16819
- } else {
16820
- encoding.scaleResolutionDownBy = 4;
16821
- encoding.maxBitrate = 10;
16822
- /* @ts-ignore */
16823
- encoding.maxFrameRate = 2;
16824
- }
16825
- }
16826
- }
16827
- });
16828
- }
16829
- if (hasChanged) {
16830
- params.encodings = encodings;
16831
- log.debug("setting encodings", Object.assign(Object.assign({}, logContext), {
16832
- encodings: params.encodings
16881
+ }
16882
+ /* @internal */
16883
+ failNext() {
16884
+ // debugging method to fail the next reconnect/resume attempt
16885
+ this.shouldFailNext = true;
16886
+ }
16887
+ dataChannelsInfo() {
16888
+ const infos = [];
16889
+ const getInfo = (dc, target) => {
16890
+ if ((dc === null || dc === void 0 ? void 0 : dc.id) !== undefined && dc.id !== null) {
16891
+ infos.push(new DataChannelInfo({
16892
+ label: dc.label,
16893
+ id: dc.id,
16894
+ target
16833
16895
  }));
16834
- yield sender.setParameters(params);
16835
16896
  }
16836
- } finally {
16837
- unlock();
16897
+ };
16898
+ getInfo(this.dataChannelForKind(DataPacket_Kind.LOSSY), SignalTarget.PUBLISHER);
16899
+ getInfo(this.dataChannelForKind(DataPacket_Kind.RELIABLE), SignalTarget.PUBLISHER);
16900
+ getInfo(this.dataChannelForKind(DataPacket_Kind.LOSSY, true), SignalTarget.SUBSCRIBER);
16901
+ getInfo(this.dataChannelForKind(DataPacket_Kind.RELIABLE, true), SignalTarget.SUBSCRIBER);
16902
+ return infos;
16903
+ }
16904
+ clearReconnectTimeout() {
16905
+ if (this.reconnectTimeout) {
16906
+ CriticalTimers.clearTimeout(this.reconnectTimeout);
16838
16907
  }
16839
- });
16840
- }
16841
- function videoQualityForRid(rid) {
16842
- switch (rid) {
16843
- case 'f':
16844
- return VideoQuality.HIGH;
16845
- case 'h':
16846
- return VideoQuality.MEDIUM;
16847
- case 'q':
16848
- return VideoQuality.LOW;
16849
- default:
16850
- return VideoQuality.HIGH;
16851
16908
  }
16852
- }
16853
- function videoLayersFromEncodings(width, height, encodings, svc) {
16854
- // default to a single layer, HQ
16855
- if (!encodings) {
16856
- return [new VideoLayer({
16857
- quality: VideoQuality.HIGH,
16858
- width,
16859
- height,
16860
- bitrate: 0,
16861
- ssrc: 0
16862
- })];
16909
+ clearPendingReconnect() {
16910
+ this.clearReconnectTimeout();
16911
+ this.reconnectAttempts = 0;
16863
16912
  }
16864
- if (svc) {
16865
- // svc layers
16866
- /* @ts-ignore */
16867
- const encodingSM = encodings[0].scalabilityMode;
16868
- const sm = new ScalabilityMode(encodingSM);
16869
- const layers = [];
16870
- const resRatio = sm.suffix == 'h' ? 1.5 : 2;
16871
- const bitratesRatio = sm.suffix == 'h' ? 2 : 3;
16872
- for (let i = 0; i < sm.spatial; i += 1) {
16873
- layers.push(new VideoLayer({
16874
- quality: VideoQuality.HIGH - i,
16875
- width: Math.ceil(width / Math.pow(resRatio, i)),
16876
- height: Math.ceil(height / Math.pow(resRatio, i)),
16877
- bitrate: encodings[0].maxBitrate ? Math.ceil(encodings[0].maxBitrate / Math.pow(bitratesRatio, i)) : 0,
16878
- ssrc: 0
16879
- }));
16913
+ registerOnLineListener() {
16914
+ if (isWeb()) {
16915
+ window.addEventListener('online', this.handleBrowserOnLine);
16880
16916
  }
16881
- return layers;
16882
16917
  }
16883
- return encodings.map(encoding => {
16884
- var _a, _b, _c;
16885
- const scale = (_a = encoding.scaleResolutionDownBy) !== null && _a !== void 0 ? _a : 1;
16886
- let quality = videoQualityForRid((_b = encoding.rid) !== null && _b !== void 0 ? _b : '');
16887
- return new VideoLayer({
16888
- quality,
16889
- width: Math.ceil(width / scale),
16890
- height: Math.ceil(height / scale),
16891
- bitrate: (_c = encoding.maxBitrate) !== null && _c !== void 0 ? _c : 0,
16892
- ssrc: 0
16918
+ deregisterOnLineListener() {
16919
+ if (isWeb()) {
16920
+ window.removeEventListener('online', this.handleBrowserOnLine);
16921
+ }
16922
+ }
16923
+ }
16924
+ class SignalReconnectError extends Error {}
16925
+
16926
+ class RegionUrlProvider {
16927
+ constructor(url, token) {
16928
+ this.lastUpdateAt = 0;
16929
+ this.settingsCacheTime = 3000;
16930
+ this.attemptedRegions = [];
16931
+ this.serverUrl = new URL(url);
16932
+ this.token = token;
16933
+ }
16934
+ updateToken(token) {
16935
+ this.token = token;
16936
+ }
16937
+ isCloud() {
16938
+ return isCloud(this.serverUrl);
16939
+ }
16940
+ getServerUrl() {
16941
+ return this.serverUrl;
16942
+ }
16943
+ getNextBestRegionUrl(abortSignal) {
16944
+ return __awaiter(this, void 0, void 0, function* () {
16945
+ if (!this.isCloud()) {
16946
+ throw Error('region availability is only supported for LiveKit Cloud domains');
16947
+ }
16948
+ if (!this.regionSettings || Date.now() - this.lastUpdateAt > this.settingsCacheTime) {
16949
+ this.regionSettings = yield this.fetchRegionSettings(abortSignal);
16950
+ }
16951
+ const regionsLeft = this.regionSettings.regions.filter(region => !this.attemptedRegions.find(attempted => attempted.url === region.url));
16952
+ if (regionsLeft.length > 0) {
16953
+ const nextRegion = regionsLeft[0];
16954
+ this.attemptedRegions.push(nextRegion);
16955
+ livekitLogger.debug("next region: ".concat(nextRegion.region));
16956
+ return nextRegion.url;
16957
+ } else {
16958
+ return null;
16959
+ }
16893
16960
  });
16894
- });
16961
+ }
16962
+ resetAttempts() {
16963
+ this.attemptedRegions = [];
16964
+ }
16965
+ /* @internal */
16966
+ fetchRegionSettings(signal) {
16967
+ return __awaiter(this, void 0, void 0, function* () {
16968
+ const regionSettingsResponse = yield fetch("".concat(getCloudConfigUrl(this.serverUrl), "/regions"), {
16969
+ headers: {
16970
+ authorization: "Bearer ".concat(this.token)
16971
+ },
16972
+ signal
16973
+ });
16974
+ if (regionSettingsResponse.ok) {
16975
+ const regionSettings = yield regionSettingsResponse.json();
16976
+ this.lastUpdateAt = Date.now();
16977
+ return regionSettings;
16978
+ } else {
16979
+ throw new ConnectionError("Could not fetch region settings: ".concat(regionSettingsResponse.statusText), regionSettingsResponse.status === 401 ? 0 /* ConnectionErrorReason.NotAllowed */ : undefined, regionSettingsResponse.status);
16980
+ }
16981
+ });
16982
+ }
16983
+ }
16984
+ function getCloudConfigUrl(serverUrl) {
16985
+ return "".concat(serverUrl.protocol.replace('ws', 'http'), "//").concat(serverUrl.host, "/settings");
16895
16986
  }
16896
16987
 
16897
16988
  class RemoteTrack extends Track {
@@ -16953,6 +17044,19 @@ class RemoteTrack extends Track {
16953
17044
  if (!this.monitorInterval) {
16954
17045
  this.monitorInterval = setInterval(() => this.monitorReceiver(), monitorFrequency);
16955
17046
  }
17047
+ this.registerTimeSyncUpdate();
17048
+ }
17049
+ registerTimeSyncUpdate() {
17050
+ const loop = () => {
17051
+ var _a, _b;
17052
+ this.timeSyncHandle = requestAnimationFrame(() => loop());
17053
+ const newTime = (_b = (_a = this.receiver) === null || _a === void 0 ? void 0 : _a.getSynchronizationSources()[0]) === null || _b === void 0 ? void 0 : _b.rtpTimestamp;
17054
+ if (newTime && this.rtpTimestamp !== newTime) {
17055
+ this.emit(TrackEvent.TimeSyncUpdate, newTime);
17056
+ this.rtpTimestamp = newTime;
17057
+ }
17058
+ };
17059
+ loop();
16956
17060
  }
16957
17061
  }
16958
17062
 
@@ -18468,9 +18572,8 @@ class LocalParticipant extends Participant {
18468
18572
  (_d = options.red) !== null && _d !== void 0 ? _d : options.red = false;
18469
18573
  }
18470
18574
  const opts = Object.assign(Object.assign({}, this.roomOptions.publishDefaults), options);
18471
- // disable simulcast if e2ee is set on safari
18472
- if (isSafari() && this.roomOptions.e2ee) {
18473
- this.log.info("End-to-end encryption is set up, simulcast publishing will be disabled on Safari", Object.assign({}, this.logContext));
18575
+ if (!isE2EESimulcastSupported() && this.roomOptions.e2ee) {
18576
+ this.log.info("End-to-end encryption is set up, simulcast publishing will be disabled on Safari versions and iOS browsers running iOS < v17.2", Object.assign({}, this.logContext));
18474
18577
  opts.simulcast = false;
18475
18578
  }
18476
18579
  if (opts.source) {
@@ -18630,7 +18733,6 @@ class LocalParticipant extends Participant {
18630
18733
  this.log.debug('falling back to server selected codec', Object.assign(Object.assign(Object.assign({}, this.logContext), getLogContextFromTrack(track)), {
18631
18734
  codec: updatedCodec
18632
18735
  }));
18633
- /* @ts-ignore */
18634
18736
  opts.videoCodec = updatedCodec;
18635
18737
  // recompute encodings since bitrates/etc could have changed
18636
18738
  encodings = computeVideoEncodings(track.source === Track.Source.ScreenShare, req.width, req.height, opts);
@@ -20046,6 +20148,16 @@ class Room extends eventsExports.EventEmitter {
20046
20148
  // also emit on the participant
20047
20149
  participant === null || participant === void 0 ? void 0 : participant.emit(ParticipantEvent.DataReceived, userPacket.payload, kind);
20048
20150
  };
20151
+ this.bufferedSegments = new Map();
20152
+ this.handleTranscription = transcription => {
20153
+ // find the participant
20154
+ const participant = transcription.participantIdentity === this.localParticipant.identity ? this.localParticipant : this.remoteParticipants.get(transcription.participantIdentity);
20155
+ const publication = participant === null || participant === void 0 ? void 0 : participant.trackPublications.get(transcription.trackId);
20156
+ const segments = extractTranscriptionSegments(transcription);
20157
+ publication === null || publication === void 0 ? void 0 : publication.emit(TrackEvent.TranscriptionReceived, segments);
20158
+ participant === null || participant === void 0 ? void 0 : participant.emit(ParticipantEvent.TranscriptionReceived, segments, publication);
20159
+ this.emit(RoomEvent.TranscriptionReceived, segments, participant, publication);
20160
+ };
20049
20161
  this.handleAudioPlaybackStarted = () => {
20050
20162
  if (this.canPlaybackAudio) {
20051
20163
  return;
@@ -20271,7 +20383,7 @@ class Room extends eventsExports.EventEmitter {
20271
20383
  this.onTrackAdded(mediaTrack, stream, receiver);
20272
20384
  }).on(EngineEvent.Disconnected, reason => {
20273
20385
  this.handleDisconnect(this.options.stopLocalTrackOnUnpublish, reason);
20274
- }).on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate).on(EngineEvent.DataPacketReceived, this.handleDataPacket).on(EngineEvent.Resuming, () => {
20386
+ }).on(EngineEvent.ActiveSpeakersUpdate, this.handleActiveSpeakersUpdate).on(EngineEvent.DataPacketReceived, this.handleDataPacket).on(EngineEvent.TranscriptionReceived, this.handleTranscription).on(EngineEvent.Resuming, () => {
20275
20387
  this.clearConnectionReconcile();
20276
20388
  this.isResuming = true;
20277
20389
  this.log.info('Resuming signal connection', this.logContext);