livekit-client 2.1.0 → 2.1.1

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