livekit-client 2.1.0 → 2.1.2

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