livekit-client 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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);