@stream-io/video-client 1.57.0 → 1.58.0
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 +13 -0
- package/dist/e2ee-worker.browser.es.js +1342 -0
- package/dist/e2ee-worker.browser.es.js.map +1 -0
- package/dist/index.browser.es.js +831 -55
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +875 -54
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.es.js +864 -55
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +52 -6
- package/dist/src/coordinator/connection/connection.d.ts +1 -1
- package/dist/src/devices/CameraManager.d.ts +6 -0
- package/dist/src/devices/DeviceManager.d.ts +1 -0
- package/dist/src/devices/MicrophoneManager.d.ts +6 -0
- package/dist/src/devices/SpeakerManager.d.ts +2 -1
- package/dist/src/events/participant.d.ts +10 -3
- package/dist/src/gen/coordinator/index.d.ts +1363 -243
- package/dist/src/gen/video/sfu/models/models.d.ts +8 -0
- package/dist/src/helpers/TypedEventEmitter.d.ts +23 -0
- package/dist/src/helpers/ensureExhausted.d.ts +1 -1
- package/dist/src/permissions/PermissionsContext.d.ts +2 -2
- package/dist/src/rtc/BasePeerConnection.d.ts +3 -1
- package/dist/src/rtc/e2ee/E2EEManager.d.ts +12 -0
- package/dist/src/rtc/e2ee/EncryptionManager.d.ts +148 -0
- package/dist/src/rtc/e2ee/e2ee-worker.d.ts +25 -0
- package/dist/src/rtc/e2ee/events.d.ts +163 -0
- package/dist/src/rtc/e2ee/transformSupport.d.ts +21 -0
- package/dist/src/rtc/helpers/rtcConfiguration.d.ts +2 -2
- package/dist/src/rtc/index.d.ts +1 -0
- package/dist/src/rtc/mediaEngine.d.ts +23 -0
- package/dist/src/rtc/types.d.ts +2 -0
- package/dist/src/store/CallState.d.ts +21 -0
- package/dist/src/types.d.ts +15 -6
- package/index.ts +3 -0
- package/package.json +1 -1
- package/src/Call.ts +160 -26
- package/src/__tests__/Call.test.ts +15 -0
- package/src/coordinator/connection/connection.ts +6 -3
- package/src/devices/AudioDeviceManager.ts +6 -0
- package/src/devices/CameraManager.ts +51 -4
- package/src/devices/DeviceManager.ts +17 -0
- package/src/devices/MicrophoneManager.ts +55 -5
- package/src/devices/ScreenShareManager.ts +3 -0
- package/src/devices/SpeakerManager.ts +42 -12
- package/src/devices/__tests__/MicrophoneManagerRN.test.ts +1 -20
- package/src/devices/__tests__/SpeakerManager.test.ts +138 -42
- package/src/devices/__tests__/mocks.ts +36 -3
- package/src/events/__tests__/participant.test.ts +134 -1
- package/src/events/callEventHandlers.ts +9 -3
- package/src/events/participant.ts +42 -7
- package/src/gen/coordinator/index.ts +1384 -244
- package/src/gen/video/sfu/models/models.ts +15 -0
- package/src/helpers/TypedEventEmitter.ts +71 -0
- package/src/helpers/__tests__/TypedEventEmitter.test.ts +133 -0
- package/src/helpers/ensureExhausted.ts +4 -2
- package/src/permissions/PermissionsContext.ts +10 -5
- package/src/rtc/BasePeerConnection.ts +12 -1
- package/src/rtc/Publisher.ts +8 -0
- package/src/rtc/Subscriber.ts +5 -0
- package/src/rtc/__tests__/Publisher.test.ts +101 -0
- package/src/rtc/__tests__/Subscriber.test.ts +89 -0
- package/src/rtc/__tests__/mocks/webrtc.mocks.ts +31 -0
- package/src/rtc/e2ee/E2EEManager.ts +12 -0
- package/src/rtc/e2ee/EncryptionManager.ts +336 -0
- package/src/rtc/e2ee/SPEC.md +586 -0
- package/src/rtc/e2ee/__tests__/E2EEManager.contract.test.ts +39 -0
- package/src/rtc/e2ee/__tests__/EncryptionManager.test.ts +490 -0
- package/src/rtc/e2ee/__tests__/codec.test.ts +247 -0
- package/src/rtc/e2ee/__tests__/conformance.test.ts +184 -0
- package/src/rtc/e2ee/__tests__/failureTracker.test.ts +45 -0
- package/src/rtc/e2ee/__tests__/frameCounter.test.ts +90 -0
- package/src/rtc/e2ee/__tests__/keyStore.test.ts +221 -0
- package/src/rtc/e2ee/__tests__/notifications.test.ts +166 -0
- package/src/rtc/e2ee/__tests__/perf.test.ts +139 -0
- package/src/rtc/e2ee/__tests__/queue.test.ts +61 -0
- package/src/rtc/e2ee/__tests__/replayWindow.test.ts +133 -0
- package/src/rtc/e2ee/__tests__/trailer.test.ts +143 -0
- package/src/rtc/e2ee/__tests__/transform-pipeline.test.ts +839 -0
- package/src/rtc/e2ee/__tests__/transformSupport.test.ts +65 -0
- package/src/rtc/e2ee/e2ee-worker/codec.ts +220 -0
- package/src/rtc/e2ee/e2ee-worker/constants.ts +47 -0
- package/src/rtc/e2ee/e2ee-worker/decode.ts +159 -0
- package/src/rtc/e2ee/e2ee-worker/e2ee-worker-impl.ts +137 -0
- package/src/rtc/e2ee/e2ee-worker/encode.ts +147 -0
- package/src/rtc/e2ee/e2ee-worker/failureTracker.ts +26 -0
- package/src/rtc/e2ee/e2ee-worker/frameCounter.ts +37 -0
- package/src/rtc/e2ee/e2ee-worker/keyStore.ts +211 -0
- package/src/rtc/e2ee/e2ee-worker/notifications.ts +190 -0
- package/src/rtc/e2ee/e2ee-worker/perf.ts +129 -0
- package/src/rtc/e2ee/e2ee-worker/queue.ts +12 -0
- package/src/rtc/e2ee/e2ee-worker/replayWindow.ts +131 -0
- package/src/rtc/e2ee/e2ee-worker/trailer.ts +155 -0
- package/src/rtc/e2ee/e2ee-worker/tsconfig.json +8 -0
- package/src/rtc/e2ee/e2ee-worker/types.ts +29 -0
- package/src/rtc/e2ee/e2ee-worker.ts +27 -0
- package/src/rtc/e2ee/events.ts +179 -0
- package/src/rtc/e2ee/transformSupport.ts +35 -0
- package/src/rtc/helpers/rtcConfiguration.ts +4 -2
- package/src/rtc/index.ts +1 -0
- package/src/rtc/mediaEngine.ts +44 -0
- package/src/rtc/types.ts +2 -0
- package/src/store/CallState.ts +33 -1
- package/src/store/__tests__/CallState.test.ts +82 -1
- package/src/types.ts +23 -5
package/dist/index.cjs.js
CHANGED
|
@@ -87,6 +87,22 @@ const CreateDeviceRequestPushProviderEnum = {
|
|
|
87
87
|
HUAWEI: 'huawei',
|
|
88
88
|
XIAOMI: 'xiaomi',
|
|
89
89
|
};
|
|
90
|
+
/**
|
|
91
|
+
* @export
|
|
92
|
+
*/
|
|
93
|
+
const EncryptionSettingsRequestModeEnum = {
|
|
94
|
+
AVAILABLE: 'available',
|
|
95
|
+
DISABLED: 'disabled',
|
|
96
|
+
AUTO_ON: 'auto-on',
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* @export
|
|
100
|
+
*/
|
|
101
|
+
const EncryptionSettingsResponseModeEnum = {
|
|
102
|
+
AVAILABLE: 'available',
|
|
103
|
+
DISABLED: 'disabled',
|
|
104
|
+
AUTO_ON: 'auto-on',
|
|
105
|
+
};
|
|
90
106
|
/**
|
|
91
107
|
* @export
|
|
92
108
|
*/
|
|
@@ -113,6 +129,21 @@ const FrameRecordingSettingsResponseModeEnum = {
|
|
|
113
129
|
DISABLED: 'disabled',
|
|
114
130
|
AUTO_ON: 'auto-on',
|
|
115
131
|
};
|
|
132
|
+
/**
|
|
133
|
+
* @export
|
|
134
|
+
*/
|
|
135
|
+
const HLSSettingsRequestQualityTracksEnum = {
|
|
136
|
+
_360P: '360p',
|
|
137
|
+
_480P: '480p',
|
|
138
|
+
_720P: '720p',
|
|
139
|
+
_1080P: '1080p',
|
|
140
|
+
_1440P: '1440p',
|
|
141
|
+
PORTRAIT_360X640: 'portrait-360x640',
|
|
142
|
+
PORTRAIT_480X854: 'portrait-480x854',
|
|
143
|
+
PORTRAIT_720X1280: 'portrait-720x1280',
|
|
144
|
+
PORTRAIT_1080X1920: 'portrait-1080x1920',
|
|
145
|
+
PORTRAIT_1440X2560: 'portrait-1440x2560',
|
|
146
|
+
};
|
|
116
147
|
/**
|
|
117
148
|
* @export
|
|
118
149
|
*/
|
|
@@ -121,6 +152,17 @@ const IndividualRecordingSettingsRequestModeEnum = {
|
|
|
121
152
|
DISABLED: 'disabled',
|
|
122
153
|
AUTO_ON: 'auto-on',
|
|
123
154
|
};
|
|
155
|
+
/**
|
|
156
|
+
* @export
|
|
157
|
+
*/
|
|
158
|
+
const IndividualRecordingSettingsRequestOutputTypesEnum = {
|
|
159
|
+
AUDIO_ONLY: 'audio_only',
|
|
160
|
+
VIDEO_ONLY: 'video_only',
|
|
161
|
+
AUDIO_VIDEO: 'audio_video',
|
|
162
|
+
SCREENSHARE_AUDIO_ONLY: 'screenshare_audio_only',
|
|
163
|
+
SCREENSHARE_VIDEO_ONLY: 'screenshare_video_only',
|
|
164
|
+
SCREENSHARE_AUDIO_VIDEO: 'screenshare_audio_video',
|
|
165
|
+
};
|
|
124
166
|
/**
|
|
125
167
|
* @export
|
|
126
168
|
*/
|
|
@@ -279,6 +321,14 @@ const RecordSettingsRequestQualityEnum = {
|
|
|
279
321
|
PORTRAIT_1080X1920: 'portrait-1080x1920',
|
|
280
322
|
PORTRAIT_1440X2560: 'portrait-1440x2560',
|
|
281
323
|
};
|
|
324
|
+
/**
|
|
325
|
+
* @export
|
|
326
|
+
*/
|
|
327
|
+
const RequestPermissionRequestPermissionsEnum = {
|
|
328
|
+
SCREENSHARE: 'screenshare',
|
|
329
|
+
SEND_AUDIO: 'send-audio',
|
|
330
|
+
SEND_VIDEO: 'send-video',
|
|
331
|
+
};
|
|
282
332
|
/**
|
|
283
333
|
* @export
|
|
284
334
|
*/
|
|
@@ -479,6 +529,59 @@ const TranscriptionSettingsResponseModeEnum = {
|
|
|
479
529
|
DISABLED: 'disabled',
|
|
480
530
|
AUTO_ON: 'auto-on',
|
|
481
531
|
};
|
|
532
|
+
/**
|
|
533
|
+
* @export
|
|
534
|
+
*/
|
|
535
|
+
const TranslationSettingsLanguagesEnum = {
|
|
536
|
+
EN: 'en',
|
|
537
|
+
FR: 'fr',
|
|
538
|
+
ES: 'es',
|
|
539
|
+
DE: 'de',
|
|
540
|
+
IT: 'it',
|
|
541
|
+
NL: 'nl',
|
|
542
|
+
PT: 'pt',
|
|
543
|
+
PL: 'pl',
|
|
544
|
+
CA: 'ca',
|
|
545
|
+
CS: 'cs',
|
|
546
|
+
DA: 'da',
|
|
547
|
+
EL: 'el',
|
|
548
|
+
FI: 'fi',
|
|
549
|
+
ID: 'id',
|
|
550
|
+
JA: 'ja',
|
|
551
|
+
RU: 'ru',
|
|
552
|
+
SV: 'sv',
|
|
553
|
+
TA: 'ta',
|
|
554
|
+
TH: 'th',
|
|
555
|
+
TR: 'tr',
|
|
556
|
+
HU: 'hu',
|
|
557
|
+
RO: 'ro',
|
|
558
|
+
ZH: 'zh',
|
|
559
|
+
AR: 'ar',
|
|
560
|
+
TL: 'tl',
|
|
561
|
+
HE: 'he',
|
|
562
|
+
HI: 'hi',
|
|
563
|
+
HR: 'hr',
|
|
564
|
+
KO: 'ko',
|
|
565
|
+
MS: 'ms',
|
|
566
|
+
NO: 'no',
|
|
567
|
+
UK: 'uk',
|
|
568
|
+
};
|
|
569
|
+
/**
|
|
570
|
+
* @export
|
|
571
|
+
*/
|
|
572
|
+
const UpdateUserPermissionsRequestGrantPermissionsEnum = {
|
|
573
|
+
SCREENSHARE: 'screenshare',
|
|
574
|
+
SEND_AUDIO: 'send-audio',
|
|
575
|
+
SEND_VIDEO: 'send-video',
|
|
576
|
+
};
|
|
577
|
+
/**
|
|
578
|
+
* @export
|
|
579
|
+
*/
|
|
580
|
+
const UpdateUserPermissionsRequestRevokePermissionsEnum = {
|
|
581
|
+
SCREENSHARE: 'screenshare',
|
|
582
|
+
SEND_AUDIO: 'send-audio',
|
|
583
|
+
SEND_VIDEO: 'send-video',
|
|
584
|
+
};
|
|
482
585
|
/**
|
|
483
586
|
* @export
|
|
484
587
|
*/
|
|
@@ -495,6 +598,14 @@ const VideoSettingsResponseCameraFacingEnum = {
|
|
|
495
598
|
BACK: 'back',
|
|
496
599
|
EXTERNAL: 'external',
|
|
497
600
|
};
|
|
601
|
+
/**
|
|
602
|
+
* @export
|
|
603
|
+
*/
|
|
604
|
+
const WSAuthMessageProductsEnum = {
|
|
605
|
+
CHAT: 'chat',
|
|
606
|
+
VIDEO: 'video',
|
|
607
|
+
FEEDS: 'feeds',
|
|
608
|
+
};
|
|
498
609
|
|
|
499
610
|
class ErrorFromResponse extends Error {
|
|
500
611
|
code;
|
|
@@ -1492,6 +1603,13 @@ class CallState$Type extends runtime.MessageType {
|
|
|
1492
1603
|
repeat: 2 /*RepeatType.UNPACKED*/,
|
|
1493
1604
|
T: () => Pin,
|
|
1494
1605
|
},
|
|
1606
|
+
{
|
|
1607
|
+
no: 5,
|
|
1608
|
+
name: 'e2ee_enabled',
|
|
1609
|
+
kind: 'scalar',
|
|
1610
|
+
jsonName: 'e2eeEnabled',
|
|
1611
|
+
T: 8 /*ScalarType.BOOL*/,
|
|
1612
|
+
},
|
|
1495
1613
|
]);
|
|
1496
1614
|
}
|
|
1497
1615
|
}
|
|
@@ -4614,6 +4732,19 @@ const isSvcCodec = (codecOrMimeType) => {
|
|
|
4614
4732
|
codecOrMimeType === 'video/av1');
|
|
4615
4733
|
};
|
|
4616
4734
|
|
|
4735
|
+
/**
|
|
4736
|
+
* The default engine: a thin, stateless wrapper over the WebRTC globals.
|
|
4737
|
+
*/
|
|
4738
|
+
const defaultGlobalsEngine = {
|
|
4739
|
+
dispose: () => Promise.resolve(true),
|
|
4740
|
+
};
|
|
4741
|
+
const defaultGlobalsEngineProvider = () => defaultGlobalsEngine;
|
|
4742
|
+
let provider = defaultGlobalsEngineProvider;
|
|
4743
|
+
const setCallMediaEngineProvider = (newProvider) => {
|
|
4744
|
+
provider = newProvider;
|
|
4745
|
+
};
|
|
4746
|
+
const getCallMediaEngineProvider = () => provider;
|
|
4747
|
+
|
|
4617
4748
|
const sfuEventKinds = {
|
|
4618
4749
|
subscriberOffer: undefined,
|
|
4619
4750
|
publisherAnswer: undefined,
|
|
@@ -4821,7 +4952,9 @@ const parseUfragFromCandidate = (candidate) => {
|
|
|
4821
4952
|
};
|
|
4822
4953
|
|
|
4823
4954
|
const ensureExhausted = (x, message) => {
|
|
4824
|
-
|
|
4955
|
+
if (message) {
|
|
4956
|
+
videoLoggerSystem.getLogger('helpers').warn(message, x);
|
|
4957
|
+
}
|
|
4825
4958
|
};
|
|
4826
4959
|
|
|
4827
4960
|
/**
|
|
@@ -4946,7 +5079,7 @@ const toIceCandidate = (iceTrickle) => {
|
|
|
4946
5079
|
}
|
|
4947
5080
|
};
|
|
4948
5081
|
|
|
4949
|
-
const version = "1.
|
|
5082
|
+
const version = "1.58.0";
|
|
4950
5083
|
const [major, minor, patch] = version.split('.');
|
|
4951
5084
|
let sdkInfo = {
|
|
4952
5085
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -5929,6 +6062,7 @@ class CallState {
|
|
|
5929
6062
|
settingsSubject = new rxjs.BehaviorSubject(undefined);
|
|
5930
6063
|
transcribingSubject = new rxjs.BehaviorSubject(false);
|
|
5931
6064
|
captioningSubject = new rxjs.BehaviorSubject(false);
|
|
6065
|
+
e2eeEnabledSubject = new rxjs.BehaviorSubject(false);
|
|
5932
6066
|
endedBySubject = new rxjs.BehaviorSubject(undefined);
|
|
5933
6067
|
thumbnailsSubject = new rxjs.BehaviorSubject(undefined);
|
|
5934
6068
|
membersSubject = new rxjs.BehaviorSubject([]);
|
|
@@ -6087,6 +6221,11 @@ class CallState {
|
|
|
6087
6221
|
* Will provide the closed captioning state of this call.
|
|
6088
6222
|
*/
|
|
6089
6223
|
captioning$;
|
|
6224
|
+
/**
|
|
6225
|
+
* Whether end-to-end encryption is active for this call, as reported by the
|
|
6226
|
+
* SFU in the join response.
|
|
6227
|
+
*/
|
|
6228
|
+
e2eeEnabled$;
|
|
6090
6229
|
/**
|
|
6091
6230
|
* Will provide the user who ended this call.
|
|
6092
6231
|
*/
|
|
@@ -6188,6 +6327,7 @@ class CallState {
|
|
|
6188
6327
|
this.rawRecording$ = duc(this.rawRecordingSubject);
|
|
6189
6328
|
this.transcribing$ = duc(this.transcribingSubject);
|
|
6190
6329
|
this.captioning$ = duc(this.captioningSubject);
|
|
6330
|
+
this.e2eeEnabled$ = duc(this.e2eeEnabledSubject);
|
|
6191
6331
|
this.eventHandlers = {
|
|
6192
6332
|
// these events are not updating the call state:
|
|
6193
6333
|
'call.frame_recording_ready': undefined,
|
|
@@ -6290,6 +6430,7 @@ class CallState {
|
|
|
6290
6430
|
clearTimeout(taskId);
|
|
6291
6431
|
this.closedCaptionsTasks.delete(ccKey);
|
|
6292
6432
|
}
|
|
6433
|
+
this.removeAllOrphanedTracks();
|
|
6293
6434
|
};
|
|
6294
6435
|
/**
|
|
6295
6436
|
* Sets the list of criteria that are used to sort the participants.
|
|
@@ -6639,6 +6780,12 @@ class CallState {
|
|
|
6639
6780
|
get transcribing() {
|
|
6640
6781
|
return this.getCurrentValue(this.transcribing$);
|
|
6641
6782
|
}
|
|
6783
|
+
/**
|
|
6784
|
+
* Whether end-to-end encryption is active for this call.
|
|
6785
|
+
*/
|
|
6786
|
+
get e2eeEnabled() {
|
|
6787
|
+
return this.getCurrentValue(this.e2eeEnabled$);
|
|
6788
|
+
}
|
|
6642
6789
|
/**
|
|
6643
6790
|
* Will provide the user who ended this call.
|
|
6644
6791
|
*/
|
|
@@ -6859,6 +7006,18 @@ class CallState {
|
|
|
6859
7006
|
removeOrphanedTrack = (id) => {
|
|
6860
7007
|
this.orphanedTracks = this.orphanedTracks.filter((o) => o.id !== id);
|
|
6861
7008
|
};
|
|
7009
|
+
/**
|
|
7010
|
+
* Drops every orphaned track. Call this when the peer connections that own
|
|
7011
|
+
* the stored receivers go away (full leave, reconnect, or migration):
|
|
7012
|
+
* `pc.close()` does not raise the track `ended` event, so the per-track
|
|
7013
|
+
* cleanup never fires and the receivers + their closed PCs would otherwise
|
|
7014
|
+
* leak for the call's lifetime.
|
|
7015
|
+
*
|
|
7016
|
+
* @internal
|
|
7017
|
+
*/
|
|
7018
|
+
removeAllOrphanedTracks = () => {
|
|
7019
|
+
this.orphanedTracks = [];
|
|
7020
|
+
};
|
|
6862
7021
|
/**
|
|
6863
7022
|
* Takes all orphaned tracks with the given track lookup prefix.
|
|
6864
7023
|
* All orphaned tracks with the given track lookup prefix are removed from the call state.
|
|
@@ -6921,7 +7080,7 @@ class CallState {
|
|
|
6921
7080
|
* @param reconnectDetails optional reconnect details.
|
|
6922
7081
|
*/
|
|
6923
7082
|
updateFromSfuCallState = (callState, currentSessionId, reconnectDetails) => {
|
|
6924
|
-
const { participants, participantCount, startedAt, pins } = callState;
|
|
7083
|
+
const { participants, participantCount, startedAt, pins, e2EeEnabled } = callState;
|
|
6925
7084
|
const localPublishedTracks = reconnectDetails?.announcedTracks.map((t) => t.trackType) ?? [];
|
|
6926
7085
|
this.setParticipants(() => {
|
|
6927
7086
|
const participantLookup = this.getParticipantLookupBySessionId();
|
|
@@ -6947,6 +7106,7 @@ class CallState {
|
|
|
6947
7106
|
this.setAnonymousParticipantCount(participantCount?.anonymous || 0);
|
|
6948
7107
|
this.setStartedAt(startedAt ? Timestamp.toDate(startedAt) : new Date());
|
|
6949
7108
|
this.setServerSidePins(pins);
|
|
7109
|
+
this.setCurrentValue(this.e2eeEnabledSubject, e2EeEnabled);
|
|
6950
7110
|
};
|
|
6951
7111
|
updateFromMemberRemoved = (event) => {
|
|
6952
7112
|
this.updateFromCallResponse(event.call);
|
|
@@ -7144,6 +7304,37 @@ class CallState {
|
|
|
7144
7304
|
};
|
|
7145
7305
|
}
|
|
7146
7306
|
|
|
7307
|
+
/**
|
|
7308
|
+
* Detection and selection policy for the two WebRTC Encoded Transform APIs.
|
|
7309
|
+
* Internal: which one the SDK attaches is an RTC-layer detail. Consumers want
|
|
7310
|
+
* `EncryptionManager.isSupported` instead.
|
|
7311
|
+
*/
|
|
7312
|
+
/**
|
|
7313
|
+
* Chrome only, and the reason an RTCPeerConnection carrying E2EE needs the
|
|
7314
|
+
* non-standard `encodedInsertableStreams` flag.
|
|
7315
|
+
*/
|
|
7316
|
+
const hasInsertableStreams = () => typeof RTCRtpSender !== 'undefined' &&
|
|
7317
|
+
'createEncodedStreams' in RTCRtpSender.prototype;
|
|
7318
|
+
/** Whether the standard `RTCRtpScriptTransform` API exists. */
|
|
7319
|
+
const hasScriptTransform = () => typeof RTCRtpScriptTransform !== 'undefined';
|
|
7320
|
+
/**
|
|
7321
|
+
* Which Encoded Transform API to attach E2EE with here.
|
|
7322
|
+
*
|
|
7323
|
+
* - `'insertable'`: legacy Insertable Streams. Used on Chrome, where
|
|
7324
|
+
* `RTCRtpScriptTransform` is still unreliable for E2EE.
|
|
7325
|
+
* - `'script'`: the standard API. Used everywhere else.
|
|
7326
|
+
* - `undefined`: neither exists, so E2EE cannot run.
|
|
7327
|
+
*/
|
|
7328
|
+
const preferredTransform = () => {
|
|
7329
|
+
const insertable = hasInsertableStreams();
|
|
7330
|
+
// Chrome's RTCRtpScriptTransform is still unreliable for E2EE.
|
|
7331
|
+
if (isChrome() && insertable)
|
|
7332
|
+
return 'insertable';
|
|
7333
|
+
if (hasScriptTransform())
|
|
7334
|
+
return 'script';
|
|
7335
|
+
return insertable ? 'insertable' : undefined;
|
|
7336
|
+
};
|
|
7337
|
+
|
|
7147
7338
|
/**
|
|
7148
7339
|
* Flatten the stats report into an array of stats objects.
|
|
7149
7340
|
*
|
|
@@ -8252,6 +8443,7 @@ class BasePeerConnection {
|
|
|
8252
8443
|
state;
|
|
8253
8444
|
dispatcher;
|
|
8254
8445
|
clientPublishOptions;
|
|
8446
|
+
e2ee;
|
|
8255
8447
|
tag;
|
|
8256
8448
|
sfuClient;
|
|
8257
8449
|
onReconnectionNeeded;
|
|
@@ -8273,13 +8465,14 @@ class BasePeerConnection {
|
|
|
8273
8465
|
/**
|
|
8274
8466
|
* Constructs a new `BasePeerConnection` instance.
|
|
8275
8467
|
*/
|
|
8276
|
-
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, onIceConnected, onPeerConnectionStateChange, onRemoteTrackUnmute, tag, enableTracing, clientPublishOptions, iceRestartDelay = 2500, statsTimestampDriftThresholdMs = 0, }) {
|
|
8468
|
+
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, onIceConnected, onPeerConnectionStateChange, onRemoteTrackUnmute, tag, enableTracing, clientPublishOptions, e2ee, iceRestartDelay = 2500, statsTimestampDriftThresholdMs = 0, }) {
|
|
8277
8469
|
this.peerType = peerType;
|
|
8278
8470
|
this.sfuClient = sfuClient;
|
|
8279
8471
|
this.state = state;
|
|
8280
8472
|
this.dispatcher = dispatcher;
|
|
8281
8473
|
this.iceRestartDelay = iceRestartDelay;
|
|
8282
8474
|
this.clientPublishOptions = clientPublishOptions;
|
|
8475
|
+
this.e2ee = e2ee;
|
|
8283
8476
|
this.tag = tag;
|
|
8284
8477
|
this.onReconnectionNeeded = onReconnectionNeeded;
|
|
8285
8478
|
this.onIceConnected = onIceConnected;
|
|
@@ -8298,7 +8491,13 @@ class BasePeerConnection {
|
|
|
8298
8491
|
}
|
|
8299
8492
|
}
|
|
8300
8493
|
createPeerConnection = (connectionConfig) => {
|
|
8301
|
-
const
|
|
8494
|
+
const config = { ...connectionConfig };
|
|
8495
|
+
// The legacy Insertable Streams path requires this non-standard flag.
|
|
8496
|
+
if (this.e2ee && hasInsertableStreams()) {
|
|
8497
|
+
// @ts-expect-error not part of the standard lib yet
|
|
8498
|
+
config.encodedInsertableStreams = true;
|
|
8499
|
+
}
|
|
8500
|
+
const pc = new RTCPeerConnection(config);
|
|
8302
8501
|
pc.addEventListener('icecandidate', this.onIceCandidate);
|
|
8303
8502
|
pc.addEventListener('icecandidateerror', this.onIceCandidateError);
|
|
8304
8503
|
pc.addEventListener('iceconnectionstatechange', this.onIceConnectionStateChange);
|
|
@@ -9052,6 +9251,10 @@ class Publisher extends BasePeerConnection {
|
|
|
9052
9251
|
toRTCDegradationPreference(publishOption.degradationPreference) ??
|
|
9053
9252
|
'maintain-framerate';
|
|
9054
9253
|
await transceiver.sender.setParameters(params);
|
|
9254
|
+
if (this.e2ee) {
|
|
9255
|
+
this.e2ee.encrypt(transceiver.sender, publishOption.codec?.name.toLowerCase(), TrackType[publishOption.trackType]);
|
|
9256
|
+
this.logger.debug('E2EE encryptor attached to sender');
|
|
9257
|
+
}
|
|
9055
9258
|
await this.negotiate();
|
|
9056
9259
|
};
|
|
9057
9260
|
/**
|
|
@@ -9663,9 +9866,15 @@ class Subscriber extends BasePeerConnection {
|
|
|
9663
9866
|
trackLookupPrefix: trackId,
|
|
9664
9867
|
track: primaryStream,
|
|
9665
9868
|
trackType,
|
|
9869
|
+
receiver: this.e2ee ? e.receiver : undefined,
|
|
9666
9870
|
});
|
|
9667
9871
|
return;
|
|
9668
9872
|
}
|
|
9873
|
+
else if (this.e2ee) {
|
|
9874
|
+
const { userId } = participantToUpdate;
|
|
9875
|
+
this.e2ee.decrypt(e.receiver, userId, TrackType[trackType]);
|
|
9876
|
+
this.logger.debug('E2EE decryptor attached to receiver', userId);
|
|
9877
|
+
}
|
|
9669
9878
|
const streamKindProp = trackTypeToParticipantStreamKey(trackType);
|
|
9670
9879
|
if (!streamKindProp) {
|
|
9671
9880
|
this.logger.error(`Unknown track type: ${rawTrackType}`);
|
|
@@ -10637,7 +10846,7 @@ const handleRemoteSoftMute = (call) => {
|
|
|
10637
10846
|
/**
|
|
10638
10847
|
* An event responder which handles the `participantJoined` event.
|
|
10639
10848
|
*/
|
|
10640
|
-
const watchParticipantJoined = (state) => {
|
|
10849
|
+
const watchParticipantJoined = (state, e2ee = undefined) => {
|
|
10641
10850
|
return function onParticipantJoined(e) {
|
|
10642
10851
|
const { participant } = e;
|
|
10643
10852
|
if (!participant)
|
|
@@ -10650,7 +10859,7 @@ const watchParticipantJoined = (state) => {
|
|
|
10650
10859
|
// The SFU would send participant info as part of the `join`
|
|
10651
10860
|
// response and then follow up with a `participantJoined` event for
|
|
10652
10861
|
// already announced participants.
|
|
10653
|
-
const orphanedTracks = reconcileOrphanedTracks(state, participant);
|
|
10862
|
+
const orphanedTracks = reconcileOrphanedTracks(state, participant, e2ee);
|
|
10654
10863
|
state.updateOrAddParticipant(participant.sessionId, Object.assign(participant, orphanedTracks, {
|
|
10655
10864
|
...(e.isPinned && { pin: { isLocalPin: false, pinnedAt: Date.now() } }),
|
|
10656
10865
|
viewportVisibilityState: {
|
|
@@ -10686,7 +10895,7 @@ const watchParticipantUpdated = (state) => {
|
|
|
10686
10895
|
* An event responder which handles the `trackPublished` event.
|
|
10687
10896
|
* The SFU will send this event when a participant publishes a track.
|
|
10688
10897
|
*/
|
|
10689
|
-
const watchTrackPublished = (state) => {
|
|
10898
|
+
const watchTrackPublished = (state, e2ee = undefined) => {
|
|
10690
10899
|
return function onTrackPublished(e) {
|
|
10691
10900
|
const { type, sessionId } = e;
|
|
10692
10901
|
// An optimization for large calls.
|
|
@@ -10694,7 +10903,7 @@ const watchTrackPublished = (state) => {
|
|
|
10694
10903
|
// events, and instead, it would only provide the participant's information
|
|
10695
10904
|
// once they start publishing a track.
|
|
10696
10905
|
if (e.participant) {
|
|
10697
|
-
const orphanedTracks = reconcileOrphanedTracks(state, e.participant);
|
|
10906
|
+
const orphanedTracks = reconcileOrphanedTracks(state, e.participant, e2ee);
|
|
10698
10907
|
const participant = Object.assign(e.participant, orphanedTracks);
|
|
10699
10908
|
state.updateOrAddParticipant(sessionId, participant);
|
|
10700
10909
|
}
|
|
@@ -10709,12 +10918,12 @@ const watchTrackPublished = (state) => {
|
|
|
10709
10918
|
* An event responder which handles the `trackUnpublished` event.
|
|
10710
10919
|
* The SFU will send this event when a participant unpublishes a track.
|
|
10711
10920
|
*/
|
|
10712
|
-
const watchTrackUnpublished = (state) => {
|
|
10921
|
+
const watchTrackUnpublished = (state, e2ee = undefined) => {
|
|
10713
10922
|
return function onTrackUnpublished(e) {
|
|
10714
10923
|
const { type, sessionId } = e;
|
|
10715
10924
|
// An optimization for large calls. See `watchTrackPublished`.
|
|
10716
10925
|
if (e.participant) {
|
|
10717
|
-
const orphanedTracks = reconcileOrphanedTracks(state, e.participant);
|
|
10926
|
+
const orphanedTracks = reconcileOrphanedTracks(state, e.participant, e2ee);
|
|
10718
10927
|
const participant = Object.assign(e.participant, orphanedTracks);
|
|
10719
10928
|
state.updateOrAddParticipant(sessionId, participant, (p) => ({
|
|
10720
10929
|
pausedTracks: p.pausedTracks?.filter((t) => t !== type),
|
|
@@ -10733,17 +10942,22 @@ const watchTrackUnpublished = (state) => {
|
|
|
10733
10942
|
*
|
|
10734
10943
|
* @param state the call state.
|
|
10735
10944
|
* @param participant the participant.
|
|
10945
|
+
* @param e2ee accessor for the call's encryption manager, if any.
|
|
10736
10946
|
*/
|
|
10737
|
-
const reconcileOrphanedTracks = (state, participant) => {
|
|
10947
|
+
const reconcileOrphanedTracks = (state, participant, e2ee) => {
|
|
10738
10948
|
const orphanTracks = state.takeOrphanedTracks(participant.trackLookupPrefix);
|
|
10739
10949
|
if (!orphanTracks.length)
|
|
10740
10950
|
return;
|
|
10951
|
+
const manager = e2ee?.();
|
|
10741
10952
|
const reconciledTracks = {};
|
|
10742
10953
|
for (const orphan of orphanTracks) {
|
|
10743
10954
|
const key = trackTypeToParticipantStreamKey(orphan.trackType);
|
|
10744
10955
|
if (!key)
|
|
10745
10956
|
continue;
|
|
10746
10957
|
reconciledTracks[key] = orphan.track;
|
|
10958
|
+
if (manager && orphan.receiver) {
|
|
10959
|
+
manager.decrypt(orphan.receiver, participant.userId, TrackType[orphan.trackType]);
|
|
10960
|
+
}
|
|
10747
10961
|
}
|
|
10748
10962
|
return reconciledTracks;
|
|
10749
10963
|
};
|
|
@@ -10799,6 +11013,11 @@ const watchAudioLevelChanged = (dispatcher, state) => {
|
|
|
10799
11013
|
*/
|
|
10800
11014
|
const registerEventHandlers = (call, dispatcher) => {
|
|
10801
11015
|
const state = call.state;
|
|
11016
|
+
// Read lazily on each event: setE2EEManager can run after setup() (an app that
|
|
11017
|
+
// inspects call settings via get()/getOrCreate() before deciding to encrypt has
|
|
11018
|
+
// already triggered it), so a value captured here would be a stale undefined
|
|
11019
|
+
// for the whole call and orphaned tracks would never get a decryptor.
|
|
11020
|
+
const e2ee = () => call.e2eeManager;
|
|
10802
11021
|
const eventHandlers = [
|
|
10803
11022
|
call.on('call.ended', watchCallEnded(call)),
|
|
10804
11023
|
watchSfuCallEnded(call),
|
|
@@ -10806,11 +11025,11 @@ const registerEventHandlers = (call, dispatcher) => {
|
|
|
10806
11025
|
watchSfuErrorReports(dispatcher),
|
|
10807
11026
|
watchConnectionQualityChanged(dispatcher, state),
|
|
10808
11027
|
watchParticipantCountChanged(dispatcher, state),
|
|
10809
|
-
call.on('participantJoined', watchParticipantJoined(state)),
|
|
11028
|
+
call.on('participantJoined', watchParticipantJoined(state, e2ee)),
|
|
10810
11029
|
call.on('participantLeft', watchParticipantLeft(state)),
|
|
10811
11030
|
call.on('participantUpdated', watchParticipantUpdated(state)),
|
|
10812
|
-
call.on('trackPublished', watchTrackPublished(state)),
|
|
10813
|
-
call.on('trackUnpublished', watchTrackUnpublished(state)),
|
|
11031
|
+
call.on('trackPublished', watchTrackPublished(state, e2ee)),
|
|
11032
|
+
call.on('trackUnpublished', watchTrackUnpublished(state, e2ee)),
|
|
10814
11033
|
watchAudioLevelChanged(dispatcher, state),
|
|
10815
11034
|
watchDominantSpeakerChanged(dispatcher, state),
|
|
10816
11035
|
call.on('callGrantsUpdated', watchCallGrantsUpdated(state)),
|
|
@@ -11938,13 +12157,14 @@ class PermissionsContext {
|
|
|
11938
12157
|
return false;
|
|
11939
12158
|
const { audio, video, screensharing } = settings;
|
|
11940
12159
|
switch (permission) {
|
|
11941
|
-
case
|
|
12160
|
+
case RequestPermissionRequestPermissionsEnum.SEND_AUDIO:
|
|
11942
12161
|
return audio.access_request_enabled;
|
|
11943
|
-
case
|
|
12162
|
+
case RequestPermissionRequestPermissionsEnum.SEND_VIDEO:
|
|
11944
12163
|
return video.access_request_enabled;
|
|
11945
|
-
case
|
|
12164
|
+
case RequestPermissionRequestPermissionsEnum.SCREENSHARE:
|
|
11946
12165
|
return screensharing.access_request_enabled;
|
|
11947
12166
|
default:
|
|
12167
|
+
ensureExhausted(permission);
|
|
11948
12168
|
return false;
|
|
11949
12169
|
}
|
|
11950
12170
|
};
|
|
@@ -12951,6 +13171,27 @@ class DeviceManager {
|
|
|
12951
13171
|
this.filters.forEach((entry) => entry.stop?.());
|
|
12952
13172
|
}
|
|
12953
13173
|
}
|
|
13174
|
+
reconcileOptimisticStatus = async () => {
|
|
13175
|
+
const target = this.state.optimisticStatus;
|
|
13176
|
+
await withCancellation(this.statusChangeConcurrencyTag, async (signal) => {
|
|
13177
|
+
try {
|
|
13178
|
+
if (target === 'enabled' && this.state.status !== 'enabled') {
|
|
13179
|
+
await this.unmuteStream();
|
|
13180
|
+
if (!signal.aborted)
|
|
13181
|
+
this.state.setStatus('enabled');
|
|
13182
|
+
}
|
|
13183
|
+
else if (target === 'disabled' && this.state.status === 'enabled') {
|
|
13184
|
+
// mirror whatever disable() does to stop/pause the track per disableMode
|
|
13185
|
+
if (!signal.aborted)
|
|
13186
|
+
this.state.setStatus('disabled');
|
|
13187
|
+
}
|
|
13188
|
+
}
|
|
13189
|
+
finally {
|
|
13190
|
+
if (!signal.aborted)
|
|
13191
|
+
this.state.setPendingStatus(this.state.status);
|
|
13192
|
+
}
|
|
13193
|
+
});
|
|
13194
|
+
};
|
|
12954
13195
|
disableTracks() {
|
|
12955
13196
|
this.getTracks().forEach((track) => {
|
|
12956
13197
|
if (track.enabled)
|
|
@@ -13584,6 +13825,33 @@ class CameraManager extends DeviceManager {
|
|
|
13584
13825
|
}
|
|
13585
13826
|
}
|
|
13586
13827
|
}
|
|
13828
|
+
enable() {
|
|
13829
|
+
if (isReactNative() && !this.call.hasMediaEngine) {
|
|
13830
|
+
this.state.setPendingStatus('enabled');
|
|
13831
|
+
return Promise.resolve();
|
|
13832
|
+
}
|
|
13833
|
+
return super.enable();
|
|
13834
|
+
}
|
|
13835
|
+
async disable(forceStopOrOptions) {
|
|
13836
|
+
if (isReactNative() && !this.call.hasMediaEngine) {
|
|
13837
|
+
this.state.setPendingStatus('disabled');
|
|
13838
|
+
return;
|
|
13839
|
+
}
|
|
13840
|
+
// forward verbatim to the base, narrowing so the right overload is selected
|
|
13841
|
+
if (forceStopOrOptions === undefined)
|
|
13842
|
+
return super.disable();
|
|
13843
|
+
if (typeof forceStopOrOptions === 'boolean') {
|
|
13844
|
+
return super.disable(forceStopOrOptions);
|
|
13845
|
+
}
|
|
13846
|
+
return super.disable(forceStopOrOptions);
|
|
13847
|
+
}
|
|
13848
|
+
toggle() {
|
|
13849
|
+
if (isReactNative() && !this.call.hasMediaEngine) {
|
|
13850
|
+
this.state.setPendingStatus(this.state.optimisticStatus === 'enabled' ? 'disabled' : 'enabled');
|
|
13851
|
+
return Promise.resolve();
|
|
13852
|
+
}
|
|
13853
|
+
return super.toggle();
|
|
13854
|
+
}
|
|
13587
13855
|
/**
|
|
13588
13856
|
* Applies the video settings to the camera.
|
|
13589
13857
|
*
|
|
@@ -13617,9 +13885,16 @@ class CameraManager extends DeviceManager {
|
|
|
13617
13885
|
await this.enable();
|
|
13618
13886
|
}
|
|
13619
13887
|
}
|
|
13620
|
-
|
|
13621
|
-
|
|
13622
|
-
|
|
13888
|
+
if (isReactNative() && publish && canPublish) {
|
|
13889
|
+
// On RN the camera is enabled/disabled optimistically before JOINED. Reconcile now
|
|
13890
|
+
// acquires the track and publishes it, so it fully owns the publish.
|
|
13891
|
+
await this.reconcileOptimisticStatus();
|
|
13892
|
+
}
|
|
13893
|
+
else {
|
|
13894
|
+
const { mediaStream } = this.state;
|
|
13895
|
+
if (canPublish && publish && this.enabled && mediaStream) {
|
|
13896
|
+
await this.publishStream(mediaStream);
|
|
13897
|
+
}
|
|
13623
13898
|
}
|
|
13624
13899
|
}
|
|
13625
13900
|
getDevices() {
|
|
@@ -13638,7 +13913,10 @@ class CameraManager extends DeviceManager {
|
|
|
13638
13913
|
}
|
|
13639
13914
|
return constraints;
|
|
13640
13915
|
}
|
|
13641
|
-
getStream(constraints) {
|
|
13916
|
+
async getStream(constraints) {
|
|
13917
|
+
// Ensure the call's media factory exists before capture so the resulting
|
|
13918
|
+
// track is owned by it (the WebRTC globals resolve to the live factory).
|
|
13919
|
+
await this.call.ensureMediaFactory();
|
|
13642
13920
|
return getVideoStream(constraints, this.call.tracer);
|
|
13643
13921
|
}
|
|
13644
13922
|
}
|
|
@@ -13654,6 +13932,9 @@ class AudioDeviceManager extends DeviceManager {
|
|
|
13654
13932
|
if (!this.call.state.settings?.audio.hifi_audio_enabled) {
|
|
13655
13933
|
throw new Error('High Fidelity audio is not enabled for this call');
|
|
13656
13934
|
}
|
|
13935
|
+
if (isReactNative() && this.call.hasMediaEngine) {
|
|
13936
|
+
throw new Error('setAudioBitrateProfile must be called before joining the call.');
|
|
13937
|
+
}
|
|
13657
13938
|
this.doSetAudioBitrateProfile(profile);
|
|
13658
13939
|
this.state.setAudioBitrateProfile(profile);
|
|
13659
13940
|
if (this.enabled) {
|
|
@@ -13926,7 +14207,10 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
13926
14207
|
]), async ([callingState, ownCapabilities, deviceId, status, permissionState,]) => {
|
|
13927
14208
|
try {
|
|
13928
14209
|
if (callingState === exports.CallingState.LEFT) {
|
|
13929
|
-
|
|
14210
|
+
// The muted-recording-prepared mode is reset in `callManager.stop()`
|
|
14211
|
+
// (during leave, while the call factory is still alive), not here —
|
|
14212
|
+
// this subscription fires asynchronously and could land after the
|
|
14213
|
+
// factory is disposed, forcing a default-ADM rebuild.
|
|
13930
14214
|
await this.stopSpeakingWhileMutedDetection();
|
|
13931
14215
|
}
|
|
13932
14216
|
if (callingState !== exports.CallingState.JOINED)
|
|
@@ -14026,6 +14310,33 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
14026
14310
|
this.subscriptions.push(unsubscribe);
|
|
14027
14311
|
}
|
|
14028
14312
|
}
|
|
14313
|
+
enable() {
|
|
14314
|
+
if (isReactNative() && !this.call.hasMediaEngine) {
|
|
14315
|
+
this.state.setPendingStatus('enabled');
|
|
14316
|
+
return Promise.resolve();
|
|
14317
|
+
}
|
|
14318
|
+
return super.enable();
|
|
14319
|
+
}
|
|
14320
|
+
async disable(forceStopOrOptions) {
|
|
14321
|
+
if (isReactNative() && !this.call.hasMediaEngine) {
|
|
14322
|
+
this.state.setPendingStatus('disabled');
|
|
14323
|
+
return;
|
|
14324
|
+
}
|
|
14325
|
+
// forward verbatim to the base, narrowing so the right overload is selected
|
|
14326
|
+
if (forceStopOrOptions === undefined)
|
|
14327
|
+
return super.disable();
|
|
14328
|
+
if (typeof forceStopOrOptions === 'boolean') {
|
|
14329
|
+
return super.disable(forceStopOrOptions);
|
|
14330
|
+
}
|
|
14331
|
+
return super.disable(forceStopOrOptions);
|
|
14332
|
+
}
|
|
14333
|
+
toggle() {
|
|
14334
|
+
if (isReactNative() && !this.call.hasMediaEngine) {
|
|
14335
|
+
this.state.setPendingStatus(this.state.optimisticStatus === 'enabled' ? 'disabled' : 'enabled');
|
|
14336
|
+
return Promise.resolve();
|
|
14337
|
+
}
|
|
14338
|
+
return super.toggle();
|
|
14339
|
+
}
|
|
14029
14340
|
/**
|
|
14030
14341
|
* Enables noise cancellation for the microphone.
|
|
14031
14342
|
*
|
|
@@ -14186,15 +14497,25 @@ class MicrophoneManager extends AudioDeviceManager {
|
|
|
14186
14497
|
await this.enable();
|
|
14187
14498
|
}
|
|
14188
14499
|
}
|
|
14189
|
-
|
|
14190
|
-
|
|
14191
|
-
|
|
14500
|
+
if (isReactNative() && publish && canPublish) {
|
|
14501
|
+
// On RN the microphone is enabled/disabled optimistically before JOINED. Reconcile now
|
|
14502
|
+
// acquires the track and publishes it, so it fully owns the publish.
|
|
14503
|
+
await this.reconcileOptimisticStatus();
|
|
14504
|
+
}
|
|
14505
|
+
else {
|
|
14506
|
+
const { mediaStream } = this.state;
|
|
14507
|
+
if (canPublish && publish && this.enabled && mediaStream) {
|
|
14508
|
+
await this.publishStream(mediaStream);
|
|
14509
|
+
}
|
|
14192
14510
|
}
|
|
14193
14511
|
}
|
|
14194
14512
|
getDevices() {
|
|
14195
14513
|
return getAudioDevices(this.call.tracer);
|
|
14196
14514
|
}
|
|
14197
|
-
getStream(constraints) {
|
|
14515
|
+
async getStream(constraints) {
|
|
14516
|
+
// Ensure the call's media factory exists before capture so the resulting
|
|
14517
|
+
// track is owned by it (the WebRTC globals resolve to the live factory).
|
|
14518
|
+
await this.call.ensureMediaFactory();
|
|
14198
14519
|
return getAudioStream(constraints, this.call.tracer);
|
|
14199
14520
|
}
|
|
14200
14521
|
doSetAudioBitrateProfile(profile) {
|
|
@@ -14403,6 +14724,9 @@ class ScreenShareManager extends AudioDeviceManager {
|
|
|
14403
14724
|
if (!this.state.audioEnabled) {
|
|
14404
14725
|
constraints.audio = false;
|
|
14405
14726
|
}
|
|
14727
|
+
// Ensure the call's media factory exists before capture so the resulting
|
|
14728
|
+
// track is owned by it (the WebRTC globals resolve to the live factory).
|
|
14729
|
+
await this.call.ensureMediaFactory();
|
|
14406
14730
|
const stream = await getScreenShareStream(constraints, this.call.tracer);
|
|
14407
14731
|
const [track] = stream.getVideoTracks();
|
|
14408
14732
|
const { contentHint } = this.state.settings || {};
|
|
@@ -14507,10 +14831,14 @@ class SpeakerManager {
|
|
|
14507
14831
|
this.devicePersistence = devicePreferences;
|
|
14508
14832
|
this.setup();
|
|
14509
14833
|
}
|
|
14510
|
-
apply(settings) {
|
|
14511
|
-
|
|
14834
|
+
async apply(settings) {
|
|
14835
|
+
if (isReactNative()) {
|
|
14836
|
+
this.applyRN(settings);
|
|
14837
|
+
return;
|
|
14838
|
+
}
|
|
14839
|
+
await this.applyWeb();
|
|
14512
14840
|
}
|
|
14513
|
-
applyWeb() {
|
|
14841
|
+
async applyWeb() {
|
|
14514
14842
|
const { enabled, storageKey } = this.devicePersistence;
|
|
14515
14843
|
if (!enabled)
|
|
14516
14844
|
return;
|
|
@@ -14522,8 +14850,22 @@ class SpeakerManager {
|
|
|
14522
14850
|
const nextDeviceId = preference.selectedDeviceId === defaultDeviceId
|
|
14523
14851
|
? ''
|
|
14524
14852
|
: preference.selectedDeviceId;
|
|
14525
|
-
if (
|
|
14526
|
-
this.
|
|
14853
|
+
if (!nextDeviceId) {
|
|
14854
|
+
if (this.state.selectedDevice !== nextDeviceId) {
|
|
14855
|
+
this.select(nextDeviceId);
|
|
14856
|
+
}
|
|
14857
|
+
return;
|
|
14858
|
+
}
|
|
14859
|
+
const permissionState = await rxjs.firstValueFrom(getAudioBrowserPermission(this.call.tracer).asStateObservable());
|
|
14860
|
+
if (permissionState !== 'granted')
|
|
14861
|
+
return;
|
|
14862
|
+
const devices = await rxjs.firstValueFrom(this.listDevices());
|
|
14863
|
+
const device = this.findDevice(devices, nextDeviceId) ??
|
|
14864
|
+
(preference.selectedDeviceLabel
|
|
14865
|
+
? devices.find((d) => d.label === preference.selectedDeviceLabel)
|
|
14866
|
+
: undefined);
|
|
14867
|
+
if (device && this.state.selectedDevice !== device.deviceId) {
|
|
14868
|
+
this.select(device.deviceId);
|
|
14527
14869
|
}
|
|
14528
14870
|
}
|
|
14529
14871
|
applyRN(settings) {
|
|
@@ -14552,6 +14894,7 @@ class SpeakerManager {
|
|
|
14552
14894
|
globalThis.streamRNVideoSDK?.callManager.setup({
|
|
14553
14895
|
defaultDevice,
|
|
14554
14896
|
isRingingTypeCall: this.call.ringing,
|
|
14897
|
+
cid: this.call.cid,
|
|
14555
14898
|
});
|
|
14556
14899
|
}
|
|
14557
14900
|
}
|
|
@@ -14560,11 +14903,15 @@ class SpeakerManager {
|
|
|
14560
14903
|
return;
|
|
14561
14904
|
this.areSubscriptionsSetUp = true;
|
|
14562
14905
|
if (deviceIds$ && !isReactNative()) {
|
|
14563
|
-
this.subscriptions.push(createSubscription(rxjs.combineLatest([
|
|
14906
|
+
this.subscriptions.push(createSubscription(rxjs.combineLatest([
|
|
14907
|
+
deviceIds$.pipe(rxjs.pairwise()),
|
|
14908
|
+
this.state.selectedDevice$,
|
|
14909
|
+
]), ([[prevDevices, currentDevices], deviceId]) => {
|
|
14564
14910
|
if (!deviceId)
|
|
14565
14911
|
return;
|
|
14566
|
-
const
|
|
14567
|
-
|
|
14912
|
+
const isDisconnected = this.findDevice(prevDevices, deviceId) &&
|
|
14913
|
+
!this.findDevice(currentDevices, deviceId);
|
|
14914
|
+
if (isDisconnected)
|
|
14568
14915
|
this.select('');
|
|
14569
14916
|
}));
|
|
14570
14917
|
}
|
|
@@ -14611,6 +14958,7 @@ class SpeakerManager {
|
|
|
14611
14958
|
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
14612
14959
|
this.subscriptions = [];
|
|
14613
14960
|
this.areSubscriptionsSetUp = false;
|
|
14961
|
+
this.defaultDevice = undefined;
|
|
14614
14962
|
};
|
|
14615
14963
|
/**
|
|
14616
14964
|
* Set the volume of the audio elements
|
|
@@ -14645,6 +14993,7 @@ class SpeakerManager {
|
|
|
14645
14993
|
return { audioVolume: volume };
|
|
14646
14994
|
});
|
|
14647
14995
|
}
|
|
14996
|
+
findDevice = (devices, deviceId) => devices.find((d) => d.deviceId === deviceId && d.kind === 'audiooutput');
|
|
14648
14997
|
persistSpeakerDevicePreference(selectedDevice) {
|
|
14649
14998
|
const { storageKey } = this.devicePersistence;
|
|
14650
14999
|
const devices = getCurrentValue(this.listDevices()) || [];
|
|
@@ -14727,6 +15076,7 @@ class Call {
|
|
|
14727
15076
|
blockedAudioTracker;
|
|
14728
15077
|
subscriber;
|
|
14729
15078
|
publisher;
|
|
15079
|
+
e2eeManager;
|
|
14730
15080
|
/**
|
|
14731
15081
|
* Flag telling whether this call is a "ringing" call.
|
|
14732
15082
|
*/
|
|
@@ -14782,6 +15132,8 @@ class Call {
|
|
|
14782
15132
|
allowOwnTracksLoopback = false;
|
|
14783
15133
|
hasJoinedOnce = false;
|
|
14784
15134
|
deviceSettingsAppliedOnce = false;
|
|
15135
|
+
callManagerStarted = false;
|
|
15136
|
+
leaveGeneration = 0;
|
|
14785
15137
|
credentials;
|
|
14786
15138
|
initialized = false;
|
|
14787
15139
|
acceptRejectConcurrencyTag = Symbol('acceptRejectTag');
|
|
@@ -14800,6 +15152,13 @@ class Call {
|
|
|
14800
15152
|
clientCapabilities = new Set([
|
|
14801
15153
|
ClientCapability.SUBSCRIBER_VIDEO_PAUSE,
|
|
14802
15154
|
]);
|
|
15155
|
+
/**
|
|
15156
|
+
* The in-flight per-call media engine. On web/React this resolves to a thin
|
|
15157
|
+
* globals-backed engine (no provider registered); React Native registers a
|
|
15158
|
+
* provider that owns a per-call native factory.
|
|
15159
|
+
* @internal
|
|
15160
|
+
*/
|
|
15161
|
+
mediaEnginePromise;
|
|
14803
15162
|
/**
|
|
14804
15163
|
* Constructs a new `Call` instance.
|
|
14805
15164
|
*
|
|
@@ -15069,6 +15428,7 @@ class Call {
|
|
|
15069
15428
|
if (callingState === exports.CallingState.LEFT) {
|
|
15070
15429
|
return;
|
|
15071
15430
|
}
|
|
15431
|
+
this.leaveGeneration += 1;
|
|
15072
15432
|
if (callingState === exports.CallingState.JOINING) {
|
|
15073
15433
|
const waitUntilCallJoined = () => {
|
|
15074
15434
|
return new Promise((resolve) => {
|
|
@@ -15155,12 +15515,14 @@ class Call {
|
|
|
15155
15515
|
this.clientStore.unregisterCall(this);
|
|
15156
15516
|
globalThis.streamRNVideoSDK?.callManager.stop({
|
|
15157
15517
|
isRingingTypeCall: this.ringing,
|
|
15518
|
+
shouldStopCallManager: this.callManagerStarted,
|
|
15158
15519
|
});
|
|
15159
15520
|
this.camera.dispose();
|
|
15160
15521
|
this.microphone.dispose();
|
|
15161
15522
|
this.screenShare.dispose();
|
|
15162
15523
|
this.speaker.dispose();
|
|
15163
15524
|
this.deviceSettingsAppliedOnce = false;
|
|
15525
|
+
this.callManagerStarted = false;
|
|
15164
15526
|
const stopOnLeavePromises = [];
|
|
15165
15527
|
if (this.camera.stopOnLeave) {
|
|
15166
15528
|
stopOnLeavePromises.push(this.camera.disable(true));
|
|
@@ -15172,6 +15534,22 @@ class Call {
|
|
|
15172
15534
|
stopOnLeavePromises.push(this.screenShare.disable(true));
|
|
15173
15535
|
}
|
|
15174
15536
|
await Promise.all(stopOnLeavePromises);
|
|
15537
|
+
// Dispose the per-call media engine last — after peer connections and
|
|
15538
|
+
// local tracks are gone — so the backing factory tears down with no
|
|
15539
|
+
// owned PCs/tracks. A fresh `join()` builds a new engine.
|
|
15540
|
+
if (this.mediaEnginePromise) {
|
|
15541
|
+
const enginePromise = this.mediaEnginePromise;
|
|
15542
|
+
this.mediaEnginePromise = undefined;
|
|
15543
|
+
this.logger.debug('Disposing per-call media factory');
|
|
15544
|
+
await enginePromise
|
|
15545
|
+
.then((engine) => {
|
|
15546
|
+
globalThis.streamRNVideoSDK?.callingX?.unwireAudioEngineSubscription();
|
|
15547
|
+
return engine.dispose();
|
|
15548
|
+
})
|
|
15549
|
+
.catch((err) => {
|
|
15550
|
+
this.logger.warn('Failed to dispose media engine', err);
|
|
15551
|
+
});
|
|
15552
|
+
}
|
|
15175
15553
|
});
|
|
15176
15554
|
};
|
|
15177
15555
|
/**
|
|
@@ -15272,11 +15650,7 @@ class Call {
|
|
|
15272
15650
|
this.clientStore.registerOrUpdateCall(this);
|
|
15273
15651
|
}
|
|
15274
15652
|
// Skip speaker setup on RN if ringing was requested or the call is already ringing
|
|
15275
|
-
const skipSpeakerApply = isReactNative()
|
|
15276
|
-
? params?.ring === true
|
|
15277
|
-
? true
|
|
15278
|
-
: this.ringing
|
|
15279
|
-
: false;
|
|
15653
|
+
const skipSpeakerApply = isReactNative();
|
|
15280
15654
|
await this.applyDeviceConfig(response.call.settings, false, skipSpeakerApply);
|
|
15281
15655
|
return response;
|
|
15282
15656
|
};
|
|
@@ -15299,11 +15673,7 @@ class Call {
|
|
|
15299
15673
|
this.clientStore.registerOrUpdateCall(this);
|
|
15300
15674
|
}
|
|
15301
15675
|
// Skip speaker setup on RN if ringing was requested or the call is already ringing
|
|
15302
|
-
const skipSpeakerApply = isReactNative()
|
|
15303
|
-
? data?.ring === true
|
|
15304
|
-
? true
|
|
15305
|
-
: this.ringing
|
|
15306
|
-
: false;
|
|
15676
|
+
const skipSpeakerApply = isReactNative();
|
|
15307
15677
|
await this.applyDeviceConfig(response.call.settings, false, skipSpeakerApply);
|
|
15308
15678
|
return response;
|
|
15309
15679
|
};
|
|
@@ -15452,9 +15822,20 @@ class Call {
|
|
|
15452
15822
|
doJoin = async (data) => {
|
|
15453
15823
|
const connectStartTime = Date.now();
|
|
15454
15824
|
const callingState = this.state.callingState;
|
|
15825
|
+
const joinLeaveGeneration = this.leaveGeneration;
|
|
15826
|
+
const supersededByLeave = () => this.leaveGeneration !== joinLeaveGeneration;
|
|
15455
15827
|
this.joinCallData = data;
|
|
15456
15828
|
this.logger.debug('Starting join flow');
|
|
15457
15829
|
this.state.setCallingState(exports.CallingState.JOINING);
|
|
15830
|
+
// Ensure the per-call media engine exists before any peer connection
|
|
15831
|
+
// (codec probe, subscriber, publisher) or capture happens, so the WebRTC
|
|
15832
|
+
// globals resolve to the call's factory. Idempotent across
|
|
15833
|
+
// reconnect/migration attempts.
|
|
15834
|
+
await this.ensureMediaFactory();
|
|
15835
|
+
const callingX = globalThis.streamRNVideoSDK?.callingX;
|
|
15836
|
+
if (callingX) {
|
|
15837
|
+
callingX.wireAudioEngineSubscription();
|
|
15838
|
+
}
|
|
15458
15839
|
const performingMigration = this.reconnectStrategy === WebsocketReconnectStrategy.MIGRATE;
|
|
15459
15840
|
const performingRejoin = this.reconnectStrategy === WebsocketReconnectStrategy.REJOIN;
|
|
15460
15841
|
const performingFastReconnect = this.reconnectStrategy === WebsocketReconnectStrategy.FAST;
|
|
@@ -15509,6 +15890,11 @@ class Call {
|
|
|
15509
15890
|
// these are throw-away SDPs that the SFU will use to determine
|
|
15510
15891
|
// the capabilities of the client (codec support, etc.)
|
|
15511
15892
|
const { dangerouslyForceCodec, fmtpLine, subscriberFmtpLine } = this.clientPublishOptions || {};
|
|
15893
|
+
// skip if a leave superseded this join so codec detection doesn't resolve to a default factory.
|
|
15894
|
+
if (supersededByLeave()) {
|
|
15895
|
+
this.logger.debug('Join superseded by leave; skipping codec detection');
|
|
15896
|
+
return;
|
|
15897
|
+
}
|
|
15512
15898
|
const [subscriberSdp, publisherSdp] = await Promise.all([
|
|
15513
15899
|
getGenericSdp('recvonly', dangerouslyForceCodec, subscriberFmtpLine),
|
|
15514
15900
|
getGenericSdp('sendonly', dangerouslyForceCodec, fmtpLine),
|
|
@@ -15552,6 +15938,12 @@ class Call {
|
|
|
15552
15938
|
throw error;
|
|
15553
15939
|
}
|
|
15554
15940
|
}
|
|
15941
|
+
// If the user left while this join was in flight, bail before re-setting JOINED and before
|
|
15942
|
+
// peer-connection setup below (both run synchronously after this, so one check covers them).
|
|
15943
|
+
if (supersededByLeave()) {
|
|
15944
|
+
this.logger.debug('Join superseded by leave; aborting join flow');
|
|
15945
|
+
return;
|
|
15946
|
+
}
|
|
15555
15947
|
if (!performingMigration) {
|
|
15556
15948
|
// in MIGRATION, `JOINED` state is set in `this.reconnectMigrate()`
|
|
15557
15949
|
this.state.setCallingState(exports.CallingState.JOINED);
|
|
@@ -15589,12 +15981,18 @@ class Call {
|
|
|
15589
15981
|
}
|
|
15590
15982
|
// device settings should be applied only once, we don't have to
|
|
15591
15983
|
// re-apply them on later reconnections or server-side data fetches
|
|
15592
|
-
if (!this.deviceSettingsAppliedOnce &&
|
|
15984
|
+
if (!this.deviceSettingsAppliedOnce &&
|
|
15985
|
+
this.state.settings &&
|
|
15986
|
+
!supersededByLeave()) {
|
|
15593
15987
|
await this.applyDeviceConfig(this.state.settings, true, false);
|
|
15988
|
+
this.deviceSettingsAppliedOnce = true;
|
|
15989
|
+
}
|
|
15990
|
+
if (!this.callManagerStarted && !supersededByLeave()) {
|
|
15594
15991
|
globalThis.streamRNVideoSDK?.callManager.start({
|
|
15595
15992
|
isRingingTypeCall: this.ringing,
|
|
15993
|
+
cid: this.cid,
|
|
15596
15994
|
});
|
|
15597
|
-
this.
|
|
15995
|
+
this.callManagerStarted = true;
|
|
15598
15996
|
}
|
|
15599
15997
|
// We shouldn't persist the `ring` and `notify` state after joining the call
|
|
15600
15998
|
// as it's a one-time event and clashes with the potential reconnection attempts.
|
|
@@ -15721,6 +16119,7 @@ class Call {
|
|
|
15721
16119
|
this.sfuStatsReporter = undefined;
|
|
15722
16120
|
if (closePreviousInstances && this.subscriber) {
|
|
15723
16121
|
await this.subscriber.dispose();
|
|
16122
|
+
this.state.removeAllOrphanedTracks();
|
|
15724
16123
|
}
|
|
15725
16124
|
const basePeerConnectionOptions = {
|
|
15726
16125
|
sfuClient,
|
|
@@ -15731,6 +16130,7 @@ class Call {
|
|
|
15731
16130
|
enableTracing,
|
|
15732
16131
|
statsTimestampDriftThresholdMs: reportingIntervalMs / 2,
|
|
15733
16132
|
clientPublishOptions: this.clientPublishOptions,
|
|
16133
|
+
e2ee: this.e2eeManager,
|
|
15734
16134
|
onReconnectionNeeded: (kind, reason, peerType) => {
|
|
15735
16135
|
this.reconnect(kind, reason).catch((err) => {
|
|
15736
16136
|
const message = `[Reconnect] Error reconnecting, after a ${PeerType[peerType]} error: ${reason}`;
|
|
@@ -15796,12 +16196,12 @@ class Call {
|
|
|
15796
16196
|
* Retrieves credentials for joining the call.
|
|
15797
16197
|
*
|
|
15798
16198
|
* @internal
|
|
15799
|
-
*
|
|
15800
16199
|
* @param data the join call data.
|
|
15801
16200
|
*/
|
|
15802
16201
|
doJoinRequest = async (data) => {
|
|
15803
16202
|
const location = await this.streamClient.getLocationHint();
|
|
15804
|
-
const
|
|
16203
|
+
const e2ee = !!this.e2eeManager;
|
|
16204
|
+
const request = { ...data, location, e2ee };
|
|
15805
16205
|
const joinResponse = await this.streamClient.post(`${this.streamClientBasePath}/join`, request);
|
|
15806
16206
|
this.state.updateFromCallResponse(joinResponse.call);
|
|
15807
16207
|
this.state.setMembers(joinResponse.members);
|
|
@@ -15820,6 +16220,36 @@ class Call {
|
|
|
15820
16220
|
}
|
|
15821
16221
|
return joinResponse;
|
|
15822
16222
|
};
|
|
16223
|
+
/**
|
|
16224
|
+
* Whether the per-call media engine currently exists. True from join until leave.
|
|
16225
|
+
*
|
|
16226
|
+
* @internal an internal getter and should not be used outside the SDK.
|
|
16227
|
+
*/
|
|
16228
|
+
get hasMediaEngine() {
|
|
16229
|
+
return !!this.mediaEnginePromise;
|
|
16230
|
+
}
|
|
16231
|
+
/**
|
|
16232
|
+
* Ensures a {@link CallMediaEngine} exists for this call's media session and
|
|
16233
|
+
* returns it. Idempotent: the engine is created once via the registered
|
|
16234
|
+
* provider (see `setCallMediaEngineProvider`) and cached until `leave()`
|
|
16235
|
+
* disposes it. Concurrent callers (e.g. camera + microphone enabling in
|
|
16236
|
+
* parallel) share the same engine because the in-flight creation promise is
|
|
16237
|
+
* cached, never the unresolved result.
|
|
16238
|
+
*
|
|
16239
|
+
* @internal
|
|
16240
|
+
*/
|
|
16241
|
+
ensureMediaFactory = async () => {
|
|
16242
|
+
if (!this.mediaEnginePromise) {
|
|
16243
|
+
const provider = getCallMediaEngineProvider();
|
|
16244
|
+
this.logger.debug(`Requesting per-call media factory creation`);
|
|
16245
|
+
this.mediaEnginePromise = Promise.resolve(provider()).catch((err) => {
|
|
16246
|
+
// Drop the cached rejection so a retried join() can rebuild the engine
|
|
16247
|
+
this.mediaEnginePromise = undefined;
|
|
16248
|
+
throw err;
|
|
16249
|
+
});
|
|
16250
|
+
}
|
|
16251
|
+
return this.mediaEnginePromise;
|
|
16252
|
+
};
|
|
15823
16253
|
/**
|
|
15824
16254
|
* Handles the closing of the SFU signal connection.
|
|
15825
16255
|
*
|
|
@@ -16381,6 +16811,30 @@ class Call {
|
|
|
16381
16811
|
this.tracer.trace('updatePublishOptions', options);
|
|
16382
16812
|
this.clientPublishOptions = { ...this.clientPublishOptions, ...options };
|
|
16383
16813
|
};
|
|
16814
|
+
/**
|
|
16815
|
+
* Set the E2EE (end-to-end encryption) manager for this call.
|
|
16816
|
+
*
|
|
16817
|
+
* Must be called before {@link join} so the RTCPeerConnection can be
|
|
16818
|
+
* configured for E2EE.
|
|
16819
|
+
*
|
|
16820
|
+
* The manager is kept across {@link leave} so a rejoin of this same instance
|
|
16821
|
+
* stays encrypted: do not dispose it while this call may be joined again.
|
|
16822
|
+
* A disposed manager throws from `encrypt`/`decrypt` rather than silently
|
|
16823
|
+
* publishing nothing, so re-attach a fresh one instead of reusing it.
|
|
16824
|
+
*
|
|
16825
|
+
* @param e2ee - Any `E2EEManager`. Use `EncryptionManager.create()` for the
|
|
16826
|
+
* built-in AES-GCM scheme, or pass your own implementation.
|
|
16827
|
+
* @throws if called after the peer connections have been built (i.e. after
|
|
16828
|
+
* `join`): those PCs were already configured without an encryptor, so
|
|
16829
|
+
* adopting a manager now would silently publish/receive cleartext for
|
|
16830
|
+
* the live session.
|
|
16831
|
+
*/
|
|
16832
|
+
setE2EEManager = (e2ee) => {
|
|
16833
|
+
if (this.publisher || this.subscriber) {
|
|
16834
|
+
throw new Error('setE2EEManager must be called before join()');
|
|
16835
|
+
}
|
|
16836
|
+
this.e2eeManager = e2ee;
|
|
16837
|
+
};
|
|
16384
16838
|
/**
|
|
16385
16839
|
* Notifies the SFU that a noise cancellation process has started.
|
|
16386
16840
|
*
|
|
@@ -17017,7 +17471,9 @@ class Call {
|
|
|
17017
17471
|
*/
|
|
17018
17472
|
applyDeviceConfig = async (settings, publish, skipSpeakerApply) => {
|
|
17019
17473
|
if (!skipSpeakerApply) {
|
|
17020
|
-
this.speaker.apply(settings)
|
|
17474
|
+
await this.speaker.apply(settings).catch((err) => {
|
|
17475
|
+
this.logger.warn('Speaker init failed', err);
|
|
17476
|
+
});
|
|
17021
17477
|
}
|
|
17022
17478
|
await this.camera.apply(settings.video, publish).catch((err) => {
|
|
17023
17479
|
this.logger.warn('Camera init failed', err);
|
|
@@ -17696,15 +18152,17 @@ class StableWSConnection {
|
|
|
17696
18152
|
this.client.logger.error(`Token not set, can't connect authenticate`);
|
|
17697
18153
|
return;
|
|
17698
18154
|
}
|
|
17699
|
-
const
|
|
18155
|
+
const wsAuthMessage = {
|
|
17700
18156
|
token,
|
|
18157
|
+
products: [WSAuthMessageProductsEnum.VIDEO],
|
|
17701
18158
|
user_details: {
|
|
17702
18159
|
id: user.id,
|
|
17703
18160
|
name: user.name,
|
|
17704
18161
|
image: user.image,
|
|
17705
18162
|
custom: user.custom,
|
|
17706
18163
|
},
|
|
17707
|
-
}
|
|
18164
|
+
};
|
|
18165
|
+
const authMessage = JSON.stringify(wsAuthMessage);
|
|
17708
18166
|
this._log(`onopen() - Sending auth message ${authMessage}`, {}, 'trace');
|
|
17709
18167
|
this.ws?.send(authMessage);
|
|
17710
18168
|
this._log('onopen() - onopen callback', { wsID });
|
|
@@ -18530,11 +18988,11 @@ class StreamClient {
|
|
|
18530
18988
|
return await this.wsConnection.connect(this.defaultWSTimeout);
|
|
18531
18989
|
};
|
|
18532
18990
|
getSdkVersion = () => this.options.clientAppIdentifier?.sdkVersion ||
|
|
18533
|
-
"1.
|
|
18991
|
+
"1.58.0";
|
|
18534
18992
|
getUserAgent = () => {
|
|
18535
18993
|
if (!this.cachedUserAgent) {
|
|
18536
18994
|
const { clientAppIdentifier = {} } = this.options;
|
|
18537
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
18995
|
+
const { sdkName = 'js', sdkVersion = "1.58.0", ...extras } = clientAppIdentifier;
|
|
18538
18996
|
this.cachedUserAgent = [
|
|
18539
18997
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
18540
18998
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -19767,6 +20225,357 @@ const humanize = (n) => {
|
|
|
19767
20225
|
return String(n);
|
|
19768
20226
|
};
|
|
19769
20227
|
|
|
20228
|
+
/**
|
|
20229
|
+
* Tiny, type-safe event emitter.
|
|
20230
|
+
*
|
|
20231
|
+
* Usage styles:
|
|
20232
|
+
* - compose: `private readonly events = new TypedEventEmitter<MyMap>()`
|
|
20233
|
+
* - extend: `class Foo extends TypedEventEmitter<MyMap> {}`
|
|
20234
|
+
*
|
|
20235
|
+
* Listener exceptions (sync throw or rejected promise) are caught and logged,
|
|
20236
|
+
* so one bad listener cannot break dispatch for the rest.
|
|
20237
|
+
*/
|
|
20238
|
+
class TypedEventEmitter {
|
|
20239
|
+
logger;
|
|
20240
|
+
byEvent = new Map();
|
|
20241
|
+
constructor(logger = 'TypedEventEmitter') {
|
|
20242
|
+
this.logger =
|
|
20243
|
+
typeof logger === 'string' ? videoLoggerSystem.getLogger(logger) : logger;
|
|
20244
|
+
}
|
|
20245
|
+
on = (event, fn) => {
|
|
20246
|
+
let listeners = this.byEvent.get(event);
|
|
20247
|
+
if (!listeners) {
|
|
20248
|
+
listeners = new Set();
|
|
20249
|
+
this.byEvent.set(event, listeners);
|
|
20250
|
+
}
|
|
20251
|
+
listeners.add(fn);
|
|
20252
|
+
return () => this.off(event, fn);
|
|
20253
|
+
};
|
|
20254
|
+
off = (event, fn) => {
|
|
20255
|
+
const listeners = this.byEvent.get(event);
|
|
20256
|
+
if (!listeners)
|
|
20257
|
+
return;
|
|
20258
|
+
listeners.delete(fn);
|
|
20259
|
+
if (listeners.size === 0)
|
|
20260
|
+
this.byEvent.delete(event);
|
|
20261
|
+
};
|
|
20262
|
+
emit = (event, payload) => {
|
|
20263
|
+
const listeners = this.byEvent.get(event);
|
|
20264
|
+
if (!listeners || listeners.size === 0)
|
|
20265
|
+
return;
|
|
20266
|
+
for (const listener of [...listeners]) {
|
|
20267
|
+
this.invoke(() => listener(payload), event);
|
|
20268
|
+
}
|
|
20269
|
+
};
|
|
20270
|
+
removeAllListeners = (event) => {
|
|
20271
|
+
if (event === undefined) {
|
|
20272
|
+
this.byEvent.clear();
|
|
20273
|
+
}
|
|
20274
|
+
else {
|
|
20275
|
+
this.byEvent.delete(event);
|
|
20276
|
+
}
|
|
20277
|
+
};
|
|
20278
|
+
invoke = (run, event) => {
|
|
20279
|
+
try {
|
|
20280
|
+
const result = run();
|
|
20281
|
+
if (result && typeof result.then === 'function') {
|
|
20282
|
+
result.catch((err) => {
|
|
20283
|
+
this.logger.warn(`Listener for '${String(event)}' rejected`, err);
|
|
20284
|
+
});
|
|
20285
|
+
}
|
|
20286
|
+
}
|
|
20287
|
+
catch (err) {
|
|
20288
|
+
this.logger.warn(`Listener for '${String(event)}' threw`, err);
|
|
20289
|
+
}
|
|
20290
|
+
};
|
|
20291
|
+
}
|
|
20292
|
+
|
|
20293
|
+
/**
|
|
20294
|
+
* Distributes keys to the E2EE Web Worker and attaches encrypt/decrypt
|
|
20295
|
+
* transforms to RTCRtpSenders and RTCRtpReceivers.
|
|
20296
|
+
*/
|
|
20297
|
+
class EncryptionManager extends TypedEventEmitter {
|
|
20298
|
+
algorithm;
|
|
20299
|
+
transform;
|
|
20300
|
+
disposed = false;
|
|
20301
|
+
piped;
|
|
20302
|
+
userId;
|
|
20303
|
+
worker;
|
|
20304
|
+
workerUrl;
|
|
20305
|
+
constructor(userId, worker, workerUrl, algorithm, transform) {
|
|
20306
|
+
super('EncryptionManager');
|
|
20307
|
+
this.userId = userId;
|
|
20308
|
+
this.worker = worker;
|
|
20309
|
+
this.workerUrl = workerUrl;
|
|
20310
|
+
this.algorithm = algorithm;
|
|
20311
|
+
this.transform = transform;
|
|
20312
|
+
this.worker.addEventListener('message', this.handleWorkerMessage);
|
|
20313
|
+
this.worker.addEventListener('error', this.handleWorkerError);
|
|
20314
|
+
}
|
|
20315
|
+
/**
|
|
20316
|
+
* Whether this browser has WebRTC Encoded Transforms, which E2EE requires.
|
|
20317
|
+
* Use it to guard UI, or to avoid calling {@link create} where it would throw.
|
|
20318
|
+
*/
|
|
20319
|
+
static isSupported = () => {
|
|
20320
|
+
return preferredTransform() !== undefined;
|
|
20321
|
+
};
|
|
20322
|
+
/**
|
|
20323
|
+
* Create an EncryptionManager instance and initialize the E2EE Web Worker.
|
|
20324
|
+
*
|
|
20325
|
+
* @param userId - The local user's ID, normally `call.currentUserId`.
|
|
20326
|
+
* @param options - the create options.
|
|
20327
|
+
* @throws {Error} If the browser lacks Encoded Transforms.
|
|
20328
|
+
*
|
|
20329
|
+
* @example
|
|
20330
|
+
* ```ts
|
|
20331
|
+
* if (EncryptionManager.isSupported()) {
|
|
20332
|
+
* const e2ee = await EncryptionManager.create(call.currentUserId);
|
|
20333
|
+
* call.setE2EEManager(e2ee);
|
|
20334
|
+
* e2ee.setSharedKey(0, keyBytes);
|
|
20335
|
+
* }
|
|
20336
|
+
*/
|
|
20337
|
+
static create = async (userId, options) => {
|
|
20338
|
+
const transform = preferredTransform();
|
|
20339
|
+
if (!transform) {
|
|
20340
|
+
throw new Error(`E2EE is not supported in this browser`);
|
|
20341
|
+
}
|
|
20342
|
+
const { e2eeWorker } = await Promise.resolve().then(function () { return e2eeWorker$1; });
|
|
20343
|
+
const blob = new Blob([`(${e2eeWorker.toString()})()`], {
|
|
20344
|
+
type: 'application/javascript',
|
|
20345
|
+
});
|
|
20346
|
+
const url = URL.createObjectURL(blob);
|
|
20347
|
+
let worker;
|
|
20348
|
+
try {
|
|
20349
|
+
worker = new Worker(url, { name: 'stream-video-e2ee' });
|
|
20350
|
+
}
|
|
20351
|
+
catch (err) {
|
|
20352
|
+
// e.g. a CSP `worker-src` without `blob:`. Don't leak the object URL.
|
|
20353
|
+
URL.revokeObjectURL(url);
|
|
20354
|
+
throw err;
|
|
20355
|
+
}
|
|
20356
|
+
const algorithm = options?.algorithm ?? 'AES-128-GCM';
|
|
20357
|
+
return new EncryptionManager(userId, worker, url, algorithm, transform);
|
|
20358
|
+
};
|
|
20359
|
+
/**
|
|
20360
|
+
* {@link dispose} terminates the worker, so `postMessage` becomes a silent
|
|
20361
|
+
* no-op and an attached transform points at a dead worker: frames would stall
|
|
20362
|
+
* forever with no error and no event. Throwing is also fail-closed, since a
|
|
20363
|
+
* caller that swallows it still publishes nothing rather than cleartext.
|
|
20364
|
+
*/
|
|
20365
|
+
assertUsable = () => {
|
|
20366
|
+
if (this.disposed)
|
|
20367
|
+
throw new Error(`EncryptionManager is disposed`);
|
|
20368
|
+
};
|
|
20369
|
+
/**
|
|
20370
|
+
* Terminate the worker and release all resources.
|
|
20371
|
+
*
|
|
20372
|
+
* The manager is unusable afterwards and every other method throws. Call
|
|
20373
|
+
* {@link create} for a new one. Safe to call more than once.
|
|
20374
|
+
*/
|
|
20375
|
+
dispose = () => {
|
|
20376
|
+
if (this.disposed)
|
|
20377
|
+
return;
|
|
20378
|
+
this.disposed = true;
|
|
20379
|
+
this.piped = undefined;
|
|
20380
|
+
this.worker.removeEventListener('message', this.handleWorkerMessage);
|
|
20381
|
+
this.worker.removeEventListener('error', this.handleWorkerError);
|
|
20382
|
+
this.worker.terminate();
|
|
20383
|
+
URL.revokeObjectURL(this.workerUrl);
|
|
20384
|
+
this.removeAllListeners();
|
|
20385
|
+
};
|
|
20386
|
+
/**
|
|
20387
|
+
* Set a per-user AES-GCM encryption key in the worker's key store.
|
|
20388
|
+
*
|
|
20389
|
+
* Use it when each participant has their own key from a central authority.
|
|
20390
|
+
* The receiver picks the right one by the `keyIndex` in the frame trailer.
|
|
20391
|
+
*
|
|
20392
|
+
* @param userId - The key owner.
|
|
20393
|
+
* @param keyIndex - Increases with each rotation.
|
|
20394
|
+
* @param rawKey - 16 bytes for AES-128-GCM, 32 for AES-256-GCM.
|
|
20395
|
+
*/
|
|
20396
|
+
setKey = (userId, keyIndex, rawKey) => {
|
|
20397
|
+
this.assertUsable();
|
|
20398
|
+
this.validateKeyIndex(keyIndex);
|
|
20399
|
+
this.validateKeyLength(rawKey);
|
|
20400
|
+
this.worker.postMessage({ type: 'cmd.set_key', userId, keyIndex, rawKey });
|
|
20401
|
+
};
|
|
20402
|
+
/**
|
|
20403
|
+
* Fallback key for any user without a per-user key. The simplest E2EE mode:
|
|
20404
|
+
* one key for everyone, usually passphrase-derived, no distribution needed.
|
|
20405
|
+
* Setting an epoch makes it active for encryption while older epochs remain
|
|
20406
|
+
* available to decrypt in-flight frames until {@link removeSharedKey}.
|
|
20407
|
+
*
|
|
20408
|
+
* @param keyIndex - An integer 0-255, since one trailer byte carries it.
|
|
20409
|
+
* @param rawKey - 16 bytes for AES-128-GCM, 32 for AES-256-GCM.
|
|
20410
|
+
*/
|
|
20411
|
+
setSharedKey = (keyIndex, rawKey) => {
|
|
20412
|
+
this.assertUsable();
|
|
20413
|
+
this.validateKeyIndex(keyIndex);
|
|
20414
|
+
this.validateKeyLength(rawKey);
|
|
20415
|
+
this.worker.postMessage({ type: 'cmd.set_shared_key', keyIndex, rawKey });
|
|
20416
|
+
};
|
|
20417
|
+
/**
|
|
20418
|
+
* Remove one shared-key epoch from the worker's receive key ring.
|
|
20419
|
+
*
|
|
20420
|
+
* If this is the active epoch, shared-key encryption stops until
|
|
20421
|
+
* {@link setSharedKey} succeeds again. An older epoch is not reactivated.
|
|
20422
|
+
*
|
|
20423
|
+
* @param keyIndex - The exact shared-key epoch to remove.
|
|
20424
|
+
*/
|
|
20425
|
+
removeSharedKey = (keyIndex) => {
|
|
20426
|
+
this.assertUsable();
|
|
20427
|
+
this.validateKeyIndex(keyIndex);
|
|
20428
|
+
this.worker.postMessage({ type: 'cmd.remove_shared_key', keyIndex });
|
|
20429
|
+
};
|
|
20430
|
+
/**
|
|
20431
|
+
* Retire one of a user's key epochs, leaving their other epochs usable.
|
|
20432
|
+
*
|
|
20433
|
+
* @param userId - The key owner.
|
|
20434
|
+
* @param keyIndex - The exact epoch to remove. Absent epochs are a no-op.
|
|
20435
|
+
*/
|
|
20436
|
+
removeKey = (userId, keyIndex) => {
|
|
20437
|
+
this.assertUsable();
|
|
20438
|
+
this.validateKeyIndex(keyIndex);
|
|
20439
|
+
this.worker.postMessage({ type: 'cmd.remove_key', userId, keyIndex });
|
|
20440
|
+
};
|
|
20441
|
+
/**
|
|
20442
|
+
* Drop every key a user holds, revoking their ability to decrypt later
|
|
20443
|
+
* frames. Call it when a participant leaves.
|
|
20444
|
+
*
|
|
20445
|
+
* To retire one rotated epoch instead, use {@link removeKey}.
|
|
20446
|
+
*/
|
|
20447
|
+
removeAllKeys = (userId) => {
|
|
20448
|
+
this.assertUsable();
|
|
20449
|
+
this.worker.postMessage({ type: 'cmd.remove_all_keys', userId });
|
|
20450
|
+
};
|
|
20451
|
+
/**
|
|
20452
|
+
* Called by the Publisher when it adds a transceiver.
|
|
20453
|
+
*
|
|
20454
|
+
* @param sender - The sender to encrypt.
|
|
20455
|
+
* @param codec - Codec name, e.g. 'vp8', selecting the clear-byte rules.
|
|
20456
|
+
* @param trackType - Optional label; only groups perf stats.
|
|
20457
|
+
* @internal
|
|
20458
|
+
*/
|
|
20459
|
+
encrypt = (sender, codec, trackType) => {
|
|
20460
|
+
this.assertUsable();
|
|
20461
|
+
this.pipe(sender, {
|
|
20462
|
+
operation: 'encode',
|
|
20463
|
+
userId: this.userId,
|
|
20464
|
+
codec,
|
|
20465
|
+
trackType,
|
|
20466
|
+
});
|
|
20467
|
+
};
|
|
20468
|
+
/**
|
|
20469
|
+
* Called by the Subscriber when a remote track arrives.
|
|
20470
|
+
*
|
|
20471
|
+
* @param receiver - The receiver to decrypt.
|
|
20472
|
+
* @param userId - The remote user, for key lookup in the worker.
|
|
20473
|
+
* @param trackType - Optional label; only groups perf stats.
|
|
20474
|
+
* @internal
|
|
20475
|
+
*/
|
|
20476
|
+
decrypt = (receiver, userId, trackType) => {
|
|
20477
|
+
this.assertUsable();
|
|
20478
|
+
this.pipe(receiver, { operation: 'decode', userId, trackType });
|
|
20479
|
+
};
|
|
20480
|
+
/** Pipe through the worker's transform, tracking targets to avoid double-piping. */
|
|
20481
|
+
pipe = (target, options) => {
|
|
20482
|
+
if (this.transform === 'script') {
|
|
20483
|
+
target.transform = new RTCRtpScriptTransform(this.worker, options);
|
|
20484
|
+
return;
|
|
20485
|
+
}
|
|
20486
|
+
if ((this.piped ??= new WeakSet()).has(target))
|
|
20487
|
+
return;
|
|
20488
|
+
this.piped.add(target);
|
|
20489
|
+
// @ts-expect-error createEncodedStreams is not in the standard typedefs
|
|
20490
|
+
const { readable, writable } = target.createEncodedStreams();
|
|
20491
|
+
this.worker.postMessage({ type: 'cmd.setup_transform', ...options, readable, writable }, [readable, writable]);
|
|
20492
|
+
};
|
|
20493
|
+
/**
|
|
20494
|
+
* Toggle periodic performance reporting from the E2EE worker.
|
|
20495
|
+
*
|
|
20496
|
+
* While on, the worker emits `e2ee.perf_report` once per second with per-track
|
|
20497
|
+
* FPS and crypto timings. Useful for debugging throughput.
|
|
20498
|
+
*/
|
|
20499
|
+
enablePerformanceReporting = (enabled) => {
|
|
20500
|
+
this.assertUsable();
|
|
20501
|
+
this.worker.postMessage({
|
|
20502
|
+
type: 'cmd.enable_performance_reporting',
|
|
20503
|
+
enabled,
|
|
20504
|
+
});
|
|
20505
|
+
};
|
|
20506
|
+
/**
|
|
20507
|
+
* Request a snapshot of the worker's keys. It arrives later as the
|
|
20508
|
+
* `e2ee.key_state` event, listing fingerprints only, never key material.
|
|
20509
|
+
*/
|
|
20510
|
+
requestKeyState = () => {
|
|
20511
|
+
this.assertUsable();
|
|
20512
|
+
this.worker.postMessage({ type: 'cmd.request_key_state' });
|
|
20513
|
+
};
|
|
20514
|
+
handleWorkerMessage = (e) => {
|
|
20515
|
+
const { type, ...payload } = e.data ?? {};
|
|
20516
|
+
if (type === 'e2ee.error') {
|
|
20517
|
+
this.logger.error(e.data.message);
|
|
20518
|
+
return;
|
|
20519
|
+
}
|
|
20520
|
+
const event = type;
|
|
20521
|
+
this.logger.debug('Dispatching', event, payload);
|
|
20522
|
+
this.emit(event, payload);
|
|
20523
|
+
};
|
|
20524
|
+
handleWorkerError = (e) => {
|
|
20525
|
+
this.logger.error('Unhandled worker error:', e.message);
|
|
20526
|
+
};
|
|
20527
|
+
validateKeyLength = (rawKey) => {
|
|
20528
|
+
const is256 = this.algorithm === 'AES-256-GCM';
|
|
20529
|
+
const expected = is256 ? 32 : 16;
|
|
20530
|
+
if (rawKey.byteLength !== expected) {
|
|
20531
|
+
throw new Error(`Key must be exactly ${expected} bytes (${is256 ? 'AES-256' : 'AES-128'})`);
|
|
20532
|
+
}
|
|
20533
|
+
};
|
|
20534
|
+
/**
|
|
20535
|
+
* One trailer byte carries the keyIndex. A larger value would truncate to
|
|
20536
|
+
* `keyIndex & 0xFF`, so the receiver would look up the wrong key and fail
|
|
20537
|
+
* every decrypt. Reject it rather than ship a silently broken key epoch.
|
|
20538
|
+
*/
|
|
20539
|
+
validateKeyIndex = (keyIndex) => {
|
|
20540
|
+
if (!Number.isInteger(keyIndex) || keyIndex < 0 || keyIndex > 255) {
|
|
20541
|
+
throw new Error(`keyIndex must be an integer between 0 and 255, got ${keyIndex}`);
|
|
20542
|
+
}
|
|
20543
|
+
};
|
|
20544
|
+
}
|
|
20545
|
+
|
|
20546
|
+
/**
|
|
20547
|
+
* E2EE worker. The build populates it.
|
|
20548
|
+
*
|
|
20549
|
+
* The actual implementation lives in `e2ee-worker/e2ee-worker-impl.ts` and its
|
|
20550
|
+
* sibling modules (`constants.ts`, `codec.ts`, `crypto.ts`, `utils.ts`,
|
|
20551
|
+
* `types.ts`), all written as real TypeScript with full type checking
|
|
20552
|
+
* and import support.
|
|
20553
|
+
*
|
|
20554
|
+
* ## How it works
|
|
20555
|
+
*
|
|
20556
|
+
* 1. You edit files in `e2ee-worker/` as normal TypeScript
|
|
20557
|
+
* 2. `yarn build` runs Rollup, which invokes `rollup-plugin-inline-worker`
|
|
20558
|
+
* 3. The plugin bundles `e2ee-worker/e2ee-worker-impl.ts` (and its imports)
|
|
20559
|
+
* into a single function via a nested Rollup + TypeScript build
|
|
20560
|
+
* 4. The function replaces this file's export at build time
|
|
20561
|
+
* 5. `EncryptionManager.create()` creates a Worker from `e2eeWorker.toString()`
|
|
20562
|
+
*
|
|
20563
|
+
* In tests, this module is mocked (see `__tests__/EncryptionManager.test.ts`),
|
|
20564
|
+
* so the placeholder is never evaluated.
|
|
20565
|
+
*
|
|
20566
|
+
* @see e2ee-worker/e2ee-worker-impl.ts - the entry point
|
|
20567
|
+
* @see ../../plugins/rollup-plugin-inline-worker.ts - the Rollup plugin
|
|
20568
|
+
* @see EncryptionManager.ts - consumes e2eeWorker
|
|
20569
|
+
*/
|
|
20570
|
+
function e2eeWorker() {
|
|
20571
|
+
// will be populated at build time
|
|
20572
|
+
}
|
|
20573
|
+
|
|
20574
|
+
var e2eeWorker$1 = /*#__PURE__*/Object.freeze({
|
|
20575
|
+
__proto__: null,
|
|
20576
|
+
e2eeWorker: e2eeWorker
|
|
20577
|
+
});
|
|
20578
|
+
|
|
19770
20579
|
Object.defineProperty(exports, "AxiosError", {
|
|
19771
20580
|
enumerable: true,
|
|
19772
20581
|
get: function () { return axios.AxiosError; }
|
|
@@ -19792,11 +20601,16 @@ exports.CreateDeviceRequestPushProviderEnum = CreateDeviceRequestPushProviderEnu
|
|
|
19792
20601
|
exports.DeviceManager = DeviceManager;
|
|
19793
20602
|
exports.DeviceManagerState = DeviceManagerState;
|
|
19794
20603
|
exports.DynascaleManager = DynascaleManager;
|
|
20604
|
+
exports.EncryptionManager = EncryptionManager;
|
|
20605
|
+
exports.EncryptionSettingsRequestModeEnum = EncryptionSettingsRequestModeEnum;
|
|
20606
|
+
exports.EncryptionSettingsResponseModeEnum = EncryptionSettingsResponseModeEnum;
|
|
19795
20607
|
exports.ErrorFromResponse = ErrorFromResponse;
|
|
19796
20608
|
exports.FrameRecordingSettingsRequestModeEnum = FrameRecordingSettingsRequestModeEnum;
|
|
19797
20609
|
exports.FrameRecordingSettingsRequestQualityEnum = FrameRecordingSettingsRequestQualityEnum;
|
|
19798
20610
|
exports.FrameRecordingSettingsResponseModeEnum = FrameRecordingSettingsResponseModeEnum;
|
|
20611
|
+
exports.HLSSettingsRequestQualityTracksEnum = HLSSettingsRequestQualityTracksEnum;
|
|
19799
20612
|
exports.IndividualRecordingSettingsRequestModeEnum = IndividualRecordingSettingsRequestModeEnum;
|
|
20613
|
+
exports.IndividualRecordingSettingsRequestOutputTypesEnum = IndividualRecordingSettingsRequestOutputTypesEnum;
|
|
19800
20614
|
exports.IndividualRecordingSettingsResponseModeEnum = IndividualRecordingSettingsResponseModeEnum;
|
|
19801
20615
|
exports.IngressAudioEncodingOptionsRequestChannelsEnum = IngressAudioEncodingOptionsRequestChannelsEnum;
|
|
19802
20616
|
exports.IngressSourceRequestFpsEnum = IngressSourceRequestFpsEnum;
|
|
@@ -19812,6 +20626,7 @@ exports.RawRecordingSettingsRequestModeEnum = RawRecordingSettingsRequestModeEnu
|
|
|
19812
20626
|
exports.RawRecordingSettingsResponseModeEnum = RawRecordingSettingsResponseModeEnum;
|
|
19813
20627
|
exports.RecordSettingsRequestModeEnum = RecordSettingsRequestModeEnum;
|
|
19814
20628
|
exports.RecordSettingsRequestQualityEnum = RecordSettingsRequestQualityEnum;
|
|
20629
|
+
exports.RequestPermissionRequestPermissionsEnum = RequestPermissionRequestPermissionsEnum;
|
|
19815
20630
|
exports.RxUtils = rxUtils;
|
|
19816
20631
|
exports.ScreenShareManager = ScreenShareManager;
|
|
19817
20632
|
exports.ScreenShareState = ScreenShareState;
|
|
@@ -19833,9 +20648,13 @@ exports.TranscriptionSettingsRequestModeEnum = TranscriptionSettingsRequestModeE
|
|
|
19833
20648
|
exports.TranscriptionSettingsResponseClosedCaptionModeEnum = TranscriptionSettingsResponseClosedCaptionModeEnum;
|
|
19834
20649
|
exports.TranscriptionSettingsResponseLanguageEnum = TranscriptionSettingsResponseLanguageEnum;
|
|
19835
20650
|
exports.TranscriptionSettingsResponseModeEnum = TranscriptionSettingsResponseModeEnum;
|
|
20651
|
+
exports.TranslationSettingsLanguagesEnum = TranslationSettingsLanguagesEnum;
|
|
20652
|
+
exports.UpdateUserPermissionsRequestGrantPermissionsEnum = UpdateUserPermissionsRequestGrantPermissionsEnum;
|
|
20653
|
+
exports.UpdateUserPermissionsRequestRevokePermissionsEnum = UpdateUserPermissionsRequestRevokePermissionsEnum;
|
|
19836
20654
|
exports.VideoSettingsRequestCameraFacingEnum = VideoSettingsRequestCameraFacingEnum;
|
|
19837
20655
|
exports.VideoSettingsResponseCameraFacingEnum = VideoSettingsResponseCameraFacingEnum;
|
|
19838
20656
|
exports.ViewportTracker = ViewportTracker;
|
|
20657
|
+
exports.WSAuthMessageProductsEnum = WSAuthMessageProductsEnum;
|
|
19839
20658
|
exports.checkIfAudioOutputChangeSupported = checkIfAudioOutputChangeSupported;
|
|
19840
20659
|
exports.combineComparators = combineComparators;
|
|
19841
20660
|
exports.conditional = conditional;
|
|
@@ -19849,6 +20668,7 @@ exports.getAudioBrowserPermission = getAudioBrowserPermission;
|
|
|
19849
20668
|
exports.getAudioDevices = getAudioDevices;
|
|
19850
20669
|
exports.getAudioOutputDevices = getAudioOutputDevices;
|
|
19851
20670
|
exports.getAudioStream = getAudioStream;
|
|
20671
|
+
exports.getCallMediaEngineProvider = getCallMediaEngineProvider;
|
|
19852
20672
|
exports.getClientDetails = getClientDetails;
|
|
19853
20673
|
exports.getDeviceState = getDeviceState;
|
|
19854
20674
|
exports.getScreenShareStream = getScreenShareStream;
|
|
@@ -19877,6 +20697,7 @@ exports.reactionType = reactionType;
|
|
|
19877
20697
|
exports.resolveDeviceId = resolveDeviceId;
|
|
19878
20698
|
exports.role = role;
|
|
19879
20699
|
exports.screenSharing = screenSharing;
|
|
20700
|
+
exports.setCallMediaEngineProvider = setCallMediaEngineProvider;
|
|
19880
20701
|
exports.setDeviceInfo = setDeviceInfo;
|
|
19881
20702
|
exports.setOSInfo = setOSInfo;
|
|
19882
20703
|
exports.setPowerState = setPowerState;
|