@stream-io/video-client 1.6.2 → 1.6.4
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.
- package/CHANGELOG.md +14 -0
- package/dist/index.browser.es.js +124 -101
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +124 -101
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +124 -101
- package/dist/index.es.js.map +1 -1
- package/dist/src/devices/CameraManager.d.ts +20 -0
- package/dist/src/rtc/codecs.d.ts +4 -0
- package/dist/src/rtc/videoLayers.d.ts +4 -2
- package/dist/src/types.d.ts +1 -0
- package/package.json +1 -1
- package/src/Call.ts +8 -6
- package/src/StreamVideoClient.ts +2 -1
- package/src/devices/CameraManager.ts +31 -3
- package/src/rtc/Publisher.ts +15 -19
- package/src/rtc/__tests__/videoLayers.test.ts +36 -6
- package/src/rtc/codecs.ts +15 -0
- package/src/rtc/videoLayers.ts +18 -4
- package/src/types.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [1.6.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.6.3...@stream-io/video-client-1.6.4) (2024-09-13)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* allow video target bitrate override ([#1487](https://github.com/GetStream/stream-video-js/issues/1487)) ([bfe34a3](https://github.com/GetStream/stream-video-js/commit/bfe34a3609182da5bbb03331978d86569cada098))
|
|
11
|
+
|
|
12
|
+
## [1.6.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.6.2...@stream-io/video-client-1.6.3) (2024-09-11)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* client instance removal used a wrong key ([#1484](https://github.com/GetStream/stream-video-js/issues/1484)) ([edff5d7](https://github.com/GetStream/stream-video-js/commit/edff5d7ca0cc241a3929da3b752073883f29da32))
|
|
18
|
+
|
|
5
19
|
## [1.6.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.6.1...@stream-io/video-client-1.6.2) (2024-09-09)
|
|
6
20
|
|
|
7
21
|
|
package/dist/index.browser.es.js
CHANGED
|
@@ -4,9 +4,9 @@ import { ServiceType, stackIntercept, RpcError } from '@protobuf-ts/runtime-rpc'
|
|
|
4
4
|
import axios, { AxiosHeaders } from 'axios';
|
|
5
5
|
export { AxiosError } from 'axios';
|
|
6
6
|
import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transport';
|
|
7
|
+
import { UAParser } from 'ua-parser-js';
|
|
7
8
|
import { ReplaySubject, combineLatest, BehaviorSubject, map as map$1, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, fromEventPattern, startWith, concatMap, merge, from, fromEvent, debounceTime, pairwise, of, debounce, timer } from 'rxjs';
|
|
8
9
|
import * as SDP from 'sdp-transform';
|
|
9
|
-
import { UAParser } from 'ua-parser-js';
|
|
10
10
|
import WebSocket$1 from 'isomorphic-ws';
|
|
11
11
|
import { fromByteArray } from 'base64-js';
|
|
12
12
|
|
|
@@ -6820,6 +6820,72 @@ const retryable = async (rpc, signal) => {
|
|
|
6820
6820
|
return result;
|
|
6821
6821
|
};
|
|
6822
6822
|
|
|
6823
|
+
const version = "1.6.4" ;
|
|
6824
|
+
const [major, minor, patch] = version.split('.');
|
|
6825
|
+
let sdkInfo = {
|
|
6826
|
+
type: SdkType.PLAIN_JAVASCRIPT,
|
|
6827
|
+
major,
|
|
6828
|
+
minor,
|
|
6829
|
+
patch,
|
|
6830
|
+
};
|
|
6831
|
+
let osInfo;
|
|
6832
|
+
let deviceInfo;
|
|
6833
|
+
let webRtcInfo;
|
|
6834
|
+
const setSdkInfo = (info) => {
|
|
6835
|
+
sdkInfo = info;
|
|
6836
|
+
};
|
|
6837
|
+
const getSdkInfo = () => {
|
|
6838
|
+
return sdkInfo;
|
|
6839
|
+
};
|
|
6840
|
+
const setOSInfo = (info) => {
|
|
6841
|
+
osInfo = info;
|
|
6842
|
+
};
|
|
6843
|
+
const getOSInfo = () => {
|
|
6844
|
+
return osInfo;
|
|
6845
|
+
};
|
|
6846
|
+
const setDeviceInfo = (info) => {
|
|
6847
|
+
deviceInfo = info;
|
|
6848
|
+
};
|
|
6849
|
+
const getDeviceInfo = () => {
|
|
6850
|
+
return deviceInfo;
|
|
6851
|
+
};
|
|
6852
|
+
const getWebRTCInfo = () => {
|
|
6853
|
+
return webRtcInfo;
|
|
6854
|
+
};
|
|
6855
|
+
const setWebRTCInfo = (info) => {
|
|
6856
|
+
webRtcInfo = info;
|
|
6857
|
+
};
|
|
6858
|
+
const getClientDetails = () => {
|
|
6859
|
+
if (isReactNative()) {
|
|
6860
|
+
// Since RN doesn't support web, sharing browser info is not required
|
|
6861
|
+
return {
|
|
6862
|
+
sdk: getSdkInfo(),
|
|
6863
|
+
os: getOSInfo(),
|
|
6864
|
+
device: getDeviceInfo(),
|
|
6865
|
+
};
|
|
6866
|
+
}
|
|
6867
|
+
const userAgent = new UAParser(navigator.userAgent);
|
|
6868
|
+
const { browser, os, device, cpu } = userAgent.getResult();
|
|
6869
|
+
return {
|
|
6870
|
+
sdk: getSdkInfo(),
|
|
6871
|
+
browser: {
|
|
6872
|
+
name: browser.name || navigator.userAgent,
|
|
6873
|
+
version: browser.version || '',
|
|
6874
|
+
},
|
|
6875
|
+
os: {
|
|
6876
|
+
name: os.name || '',
|
|
6877
|
+
version: os.version || '',
|
|
6878
|
+
architecture: cpu.architecture || '',
|
|
6879
|
+
},
|
|
6880
|
+
device: {
|
|
6881
|
+
name: [device.vendor, device.model, device.type]
|
|
6882
|
+
.filter(Boolean)
|
|
6883
|
+
.join(' '),
|
|
6884
|
+
version: '',
|
|
6885
|
+
},
|
|
6886
|
+
};
|
|
6887
|
+
};
|
|
6888
|
+
|
|
6823
6889
|
/**
|
|
6824
6890
|
* Returns back a list of sorted codecs, with the preferred codec first.
|
|
6825
6891
|
*
|
|
@@ -6894,6 +6960,20 @@ const getGenericSdp = async (direction) => {
|
|
|
6894
6960
|
tempPc.close();
|
|
6895
6961
|
return sdp;
|
|
6896
6962
|
};
|
|
6963
|
+
/**
|
|
6964
|
+
* Returns the optimal codec for RN.
|
|
6965
|
+
*/
|
|
6966
|
+
const getRNOptimalCodec = () => {
|
|
6967
|
+
const osName = getOSInfo()?.name.toLowerCase();
|
|
6968
|
+
// in ipads it was noticed that if vp8 codec is used
|
|
6969
|
+
// then the bytes sent is 0 in the outbound-rtp
|
|
6970
|
+
// so we are forcing h264 codec for ipads
|
|
6971
|
+
if (osName === 'ipados')
|
|
6972
|
+
return 'h264';
|
|
6973
|
+
if (osName === 'android')
|
|
6974
|
+
return 'vp8';
|
|
6975
|
+
return undefined;
|
|
6976
|
+
};
|
|
6897
6977
|
|
|
6898
6978
|
const sfuEventKinds = {
|
|
6899
6979
|
subscriberOffer: undefined,
|
|
@@ -7017,12 +7097,13 @@ const defaultBitratePerRid = {
|
|
|
7017
7097
|
*
|
|
7018
7098
|
* @param videoTrack the video track to find optimal layers for.
|
|
7019
7099
|
* @param targetResolution the expected target resolution.
|
|
7100
|
+
* @param preferredBitrate the preferred bitrate for the video track.
|
|
7020
7101
|
*/
|
|
7021
|
-
const findOptimalVideoLayers = (videoTrack, targetResolution = defaultTargetResolution) => {
|
|
7102
|
+
const findOptimalVideoLayers = (videoTrack, targetResolution = defaultTargetResolution, preferredBitrate) => {
|
|
7022
7103
|
const optimalVideoLayers = [];
|
|
7023
7104
|
const settings = videoTrack.getSettings();
|
|
7024
7105
|
const { width: w = 0, height: h = 0 } = settings;
|
|
7025
|
-
const maxBitrate = getComputedMaxBitrate(targetResolution, w, h);
|
|
7106
|
+
const maxBitrate = getComputedMaxBitrate(targetResolution, w, h, preferredBitrate);
|
|
7026
7107
|
let downscaleFactor = 1;
|
|
7027
7108
|
['f', 'h', 'q'].forEach((rid) => {
|
|
7028
7109
|
// Reversing the order [f, h, q] to [q, h, f] as Chrome uses encoding index
|
|
@@ -7053,18 +7134,20 @@ const findOptimalVideoLayers = (videoTrack, targetResolution = defaultTargetReso
|
|
|
7053
7134
|
* @param targetResolution the target resolution.
|
|
7054
7135
|
* @param currentWidth the current width of the track.
|
|
7055
7136
|
* @param currentHeight the current height of the track.
|
|
7137
|
+
* @param preferredBitrate the preferred bitrate for the track.
|
|
7056
7138
|
*/
|
|
7057
|
-
const getComputedMaxBitrate = (targetResolution, currentWidth, currentHeight) => {
|
|
7139
|
+
const getComputedMaxBitrate = (targetResolution, currentWidth, currentHeight, preferredBitrate) => {
|
|
7058
7140
|
// if the current resolution is lower than the target resolution,
|
|
7059
7141
|
// we want to proportionally reduce the target bitrate
|
|
7060
|
-
const { width: targetWidth, height: targetHeight } = targetResolution;
|
|
7142
|
+
const { width: targetWidth, height: targetHeight, bitrate: targetBitrate, } = targetResolution;
|
|
7143
|
+
const bitrate = preferredBitrate || targetBitrate;
|
|
7061
7144
|
if (currentWidth < targetWidth || currentHeight < targetHeight) {
|
|
7062
7145
|
const currentPixels = currentWidth * currentHeight;
|
|
7063
7146
|
const targetPixels = targetWidth * targetHeight;
|
|
7064
7147
|
const reductionFactor = currentPixels / targetPixels;
|
|
7065
|
-
return Math.round(
|
|
7148
|
+
return Math.round(bitrate * reductionFactor);
|
|
7066
7149
|
}
|
|
7067
|
-
return
|
|
7150
|
+
return bitrate;
|
|
7068
7151
|
};
|
|
7069
7152
|
/**
|
|
7070
7153
|
* Browsers have different simulcast constraints for different video resolutions.
|
|
@@ -8748,72 +8831,6 @@ const enableHighQualityAudio = (sdp, trackMid, maxBitrate = 510000) => {
|
|
|
8748
8831
|
return SDP.write(parsedSdp);
|
|
8749
8832
|
};
|
|
8750
8833
|
|
|
8751
|
-
const version = "1.6.2" ;
|
|
8752
|
-
const [major, minor, patch] = version.split('.');
|
|
8753
|
-
let sdkInfo = {
|
|
8754
|
-
type: SdkType.PLAIN_JAVASCRIPT,
|
|
8755
|
-
major,
|
|
8756
|
-
minor,
|
|
8757
|
-
patch,
|
|
8758
|
-
};
|
|
8759
|
-
let osInfo;
|
|
8760
|
-
let deviceInfo;
|
|
8761
|
-
let webRtcInfo;
|
|
8762
|
-
const setSdkInfo = (info) => {
|
|
8763
|
-
sdkInfo = info;
|
|
8764
|
-
};
|
|
8765
|
-
const getSdkInfo = () => {
|
|
8766
|
-
return sdkInfo;
|
|
8767
|
-
};
|
|
8768
|
-
const setOSInfo = (info) => {
|
|
8769
|
-
osInfo = info;
|
|
8770
|
-
};
|
|
8771
|
-
const getOSInfo = () => {
|
|
8772
|
-
return osInfo;
|
|
8773
|
-
};
|
|
8774
|
-
const setDeviceInfo = (info) => {
|
|
8775
|
-
deviceInfo = info;
|
|
8776
|
-
};
|
|
8777
|
-
const getDeviceInfo = () => {
|
|
8778
|
-
return deviceInfo;
|
|
8779
|
-
};
|
|
8780
|
-
const getWebRTCInfo = () => {
|
|
8781
|
-
return webRtcInfo;
|
|
8782
|
-
};
|
|
8783
|
-
const setWebRTCInfo = (info) => {
|
|
8784
|
-
webRtcInfo = info;
|
|
8785
|
-
};
|
|
8786
|
-
const getClientDetails = () => {
|
|
8787
|
-
if (isReactNative()) {
|
|
8788
|
-
// Since RN doesn't support web, sharing browser info is not required
|
|
8789
|
-
return {
|
|
8790
|
-
sdk: getSdkInfo(),
|
|
8791
|
-
os: getOSInfo(),
|
|
8792
|
-
device: getDeviceInfo(),
|
|
8793
|
-
};
|
|
8794
|
-
}
|
|
8795
|
-
const userAgent = new UAParser(navigator.userAgent);
|
|
8796
|
-
const { browser, os, device, cpu } = userAgent.getResult();
|
|
8797
|
-
return {
|
|
8798
|
-
sdk: getSdkInfo(),
|
|
8799
|
-
browser: {
|
|
8800
|
-
name: browser.name || navigator.userAgent,
|
|
8801
|
-
version: browser.version || '',
|
|
8802
|
-
},
|
|
8803
|
-
os: {
|
|
8804
|
-
name: os.name || '',
|
|
8805
|
-
version: os.version || '',
|
|
8806
|
-
architecture: cpu.architecture || '',
|
|
8807
|
-
},
|
|
8808
|
-
device: {
|
|
8809
|
-
name: [device.vendor, device.model, device.type]
|
|
8810
|
-
.filter(Boolean)
|
|
8811
|
-
.join(' '),
|
|
8812
|
-
version: '',
|
|
8813
|
-
},
|
|
8814
|
-
};
|
|
8815
|
-
};
|
|
8816
|
-
|
|
8817
8834
|
/**
|
|
8818
8835
|
* The `Publisher` is responsible for publishing/unpublishing media streams to/from the SFU
|
|
8819
8836
|
*
|
|
@@ -8942,24 +8959,12 @@ class Publisher {
|
|
|
8942
8959
|
const targetResolution = settings?.video
|
|
8943
8960
|
.target_resolution;
|
|
8944
8961
|
const screenShareBitrate = settings?.screensharing.target_resolution?.bitrate;
|
|
8962
|
+
const { preferredBitrate, preferredCodec, screenShareSettings } = opts;
|
|
8945
8963
|
const videoEncodings = trackType === TrackType.VIDEO
|
|
8946
|
-
? findOptimalVideoLayers(track, targetResolution)
|
|
8964
|
+
? findOptimalVideoLayers(track, targetResolution, preferredBitrate)
|
|
8947
8965
|
: trackType === TrackType.SCREEN_SHARE
|
|
8948
|
-
? findOptimalScreenSharingLayers(track,
|
|
8966
|
+
? findOptimalScreenSharingLayers(track, screenShareSettings, screenShareBitrate)
|
|
8949
8967
|
: undefined;
|
|
8950
|
-
let preferredCodec = opts.preferredCodec;
|
|
8951
|
-
if (!preferredCodec && trackType === TrackType.VIDEO && isReactNative()) {
|
|
8952
|
-
const osName = getOSInfo()?.name.toLowerCase();
|
|
8953
|
-
if (osName === 'ipados') {
|
|
8954
|
-
// in ipads it was noticed that if vp8 codec is used
|
|
8955
|
-
// then the bytes sent is 0 in the outbound-rtp
|
|
8956
|
-
// so we are forcing h264 codec for ipads
|
|
8957
|
-
preferredCodec = 'H264';
|
|
8958
|
-
}
|
|
8959
|
-
else if (osName === 'android') {
|
|
8960
|
-
preferredCodec = 'VP8';
|
|
8961
|
-
}
|
|
8962
|
-
}
|
|
8963
8968
|
// listen for 'ended' event on the track as it might be ended abruptly
|
|
8964
8969
|
// by an external factor as permission revokes, device disconnected, etc.
|
|
8965
8970
|
// keep in mind that `track.stop()` doesn't trigger this event.
|
|
@@ -8978,8 +8983,11 @@ class Publisher {
|
|
|
8978
8983
|
this.transceiverInitOrder.push(trackType);
|
|
8979
8984
|
this.transceiverRegistry[trackType] = transceiver;
|
|
8980
8985
|
this.publishOptionsPerTrackType.set(trackType, opts);
|
|
8986
|
+
const codec = isReactNative() && trackType === TrackType.VIDEO && !preferredCodec
|
|
8987
|
+
? getRNOptimalCodec()
|
|
8988
|
+
: preferredCodec;
|
|
8981
8989
|
const codecPreferences = 'setCodecPreferences' in transceiver
|
|
8982
|
-
? this.getCodecPreferences(trackType,
|
|
8990
|
+
? this.getCodecPreferences(trackType, codec)
|
|
8983
8991
|
: undefined;
|
|
8984
8992
|
if (codecPreferences) {
|
|
8985
8993
|
this.logger('info', `Setting ${TrackType[trackType]} codec preferences`, codecPreferences);
|
|
@@ -9293,7 +9301,7 @@ class Publisher {
|
|
|
9293
9301
|
const publishOpts = this.publishOptionsPerTrackType.get(trackType);
|
|
9294
9302
|
optimalLayers =
|
|
9295
9303
|
trackType === TrackType.VIDEO
|
|
9296
|
-
? findOptimalVideoLayers(track, targetResolution)
|
|
9304
|
+
? findOptimalVideoLayers(track, targetResolution, publishOpts?.preferredBitrate)
|
|
9297
9305
|
: trackType === TrackType.SCREEN_SHARE
|
|
9298
9306
|
? findOptimalScreenSharingLayers(track, publishOpts?.screenShareSettings)
|
|
9299
9307
|
: [];
|
|
@@ -12167,6 +12175,17 @@ class CameraManager extends InputMediaDeviceManager {
|
|
|
12167
12175
|
height: 720,
|
|
12168
12176
|
};
|
|
12169
12177
|
}
|
|
12178
|
+
/**
|
|
12179
|
+
* The publish options for the camera.
|
|
12180
|
+
*
|
|
12181
|
+
* @internal internal use only, not part of the public API.
|
|
12182
|
+
*/
|
|
12183
|
+
get publishOptions() {
|
|
12184
|
+
return {
|
|
12185
|
+
preferredCodec: this.preferredCodec,
|
|
12186
|
+
preferredBitrate: this.preferredBitrate,
|
|
12187
|
+
};
|
|
12188
|
+
}
|
|
12170
12189
|
/**
|
|
12171
12190
|
* Select the camera direction.
|
|
12172
12191
|
*
|
|
@@ -12223,6 +12242,15 @@ class CameraManager extends InputMediaDeviceManager {
|
|
|
12223
12242
|
setPreferredCodec(codec) {
|
|
12224
12243
|
this.preferredCodec = codec;
|
|
12225
12244
|
}
|
|
12245
|
+
/**
|
|
12246
|
+
* Sets the preferred bitrate for encoding the video.
|
|
12247
|
+
*
|
|
12248
|
+
* @internal internal use only, not part of the public API.
|
|
12249
|
+
* @param bitrate the bitrate to use for encoding the video.
|
|
12250
|
+
*/
|
|
12251
|
+
setPreferredBitrate(bitrate) {
|
|
12252
|
+
this.preferredBitrate = bitrate;
|
|
12253
|
+
}
|
|
12226
12254
|
getDevices() {
|
|
12227
12255
|
return getVideoDevices();
|
|
12228
12256
|
}
|
|
@@ -12238,9 +12266,7 @@ class CameraManager extends InputMediaDeviceManager {
|
|
|
12238
12266
|
return getVideoStream(constraints);
|
|
12239
12267
|
}
|
|
12240
12268
|
publishStream(stream) {
|
|
12241
|
-
return this.call.publishVideoStream(stream,
|
|
12242
|
-
preferredCodec: this.preferredCodec,
|
|
12243
|
-
});
|
|
12269
|
+
return this.call.publishVideoStream(stream, this.publishOptions);
|
|
12244
12270
|
}
|
|
12245
12271
|
stopPublishStream(stopTracks) {
|
|
12246
12272
|
return this.call.stopPublish(TrackType.VIDEO, stopTracks);
|
|
@@ -13633,9 +13659,7 @@ class Call {
|
|
|
13633
13659
|
case TrackType.VIDEO:
|
|
13634
13660
|
const videoStream = this.camera.state.mediaStream;
|
|
13635
13661
|
if (videoStream) {
|
|
13636
|
-
await this.publishVideoStream(videoStream,
|
|
13637
|
-
preferredCodec: this.camera.preferredCodec,
|
|
13638
|
-
});
|
|
13662
|
+
await this.publishVideoStream(videoStream, this.camera.publishOptions);
|
|
13639
13663
|
}
|
|
13640
13664
|
break;
|
|
13641
13665
|
case TrackType.SCREEN_SHARE:
|
|
@@ -14311,9 +14335,7 @@ class Call {
|
|
|
14311
14335
|
if (this.camera.enabled &&
|
|
14312
14336
|
this.camera.state.mediaStream &&
|
|
14313
14337
|
!this.publisher?.isPublishing(TrackType.VIDEO)) {
|
|
14314
|
-
await this.publishVideoStream(this.camera.state.mediaStream,
|
|
14315
|
-
preferredCodec: this.camera.preferredCodec,
|
|
14316
|
-
});
|
|
14338
|
+
await this.publishVideoStream(this.camera.state.mediaStream, this.camera.publishOptions);
|
|
14317
14339
|
}
|
|
14318
14340
|
// Start camera if backend config specifies, and there is no local setting
|
|
14319
14341
|
if (this.camera.state.status === undefined &&
|
|
@@ -16092,7 +16114,7 @@ class StreamClient {
|
|
|
16092
16114
|
});
|
|
16093
16115
|
};
|
|
16094
16116
|
this.getUserAgent = () => {
|
|
16095
|
-
const version = "1.6.
|
|
16117
|
+
const version = "1.6.4" ;
|
|
16096
16118
|
return (this.userAgent ||
|
|
16097
16119
|
`stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
|
|
16098
16120
|
};
|
|
@@ -16338,6 +16360,7 @@ class StreamVideoClient {
|
|
|
16338
16360
|
return;
|
|
16339
16361
|
}
|
|
16340
16362
|
const userId = this.streamClient.user?.id;
|
|
16363
|
+
const apiKey = this.streamClient.key;
|
|
16341
16364
|
const disconnectUser = () => this.streamClient.disconnectUser(timeout);
|
|
16342
16365
|
this.disconnectionPromise = this.connectionPromise
|
|
16343
16366
|
? this.connectionPromise.then(() => disconnectUser())
|
|
@@ -16345,7 +16368,7 @@ class StreamVideoClient {
|
|
|
16345
16368
|
this.disconnectionPromise.finally(() => (this.disconnectionPromise = undefined));
|
|
16346
16369
|
await this.disconnectionPromise;
|
|
16347
16370
|
if (userId) {
|
|
16348
|
-
StreamVideoClient._instanceMap.delete(userId);
|
|
16371
|
+
StreamVideoClient._instanceMap.delete(apiKey + userId);
|
|
16349
16372
|
}
|
|
16350
16373
|
this.eventHandlersToUnregister.forEach((unregister) => unregister());
|
|
16351
16374
|
this.eventHandlersToUnregister = [];
|