@stream-io/video-client 1.56.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 +23 -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 +867 -71
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +911 -70
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.es.js +900 -71
- 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/MediaPlaybackWatchdog.d.ts +4 -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 +11 -11
- 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 +2 -22
- package/src/devices/__tests__/SpeakerManager.test.ts +138 -42
- package/src/devices/__tests__/mocks.ts +36 -3
- package/src/devices/devicePersistence.ts +1 -2
- 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/MediaPlaybackWatchdog.ts +35 -16
- package/src/helpers/TypedEventEmitter.ts +71 -0
- package/src/helpers/__tests__/MediaPlaybackWatchdog.test.ts +137 -8
- package/src/helpers/__tests__/TypedEventEmitter.test.ts +133 -0
- package/src/helpers/__tests__/ViewportTracker.test.ts +1 -2
- package/src/helpers/ensureExhausted.ts +4 -2
- package/src/permissions/PermissionsContext.ts +10 -5
- package/src/reporting/ClientEventReporter.ts +2 -8
- 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 +3 -2
- package/src/stats/rtc/types.ts +8 -2
- package/src/store/CallState.ts +34 -3
- package/src/store/__tests__/CallState.test.ts +82 -1
- package/src/types.ts +24 -9
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { isChrome } from '../../helpers/browsers';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Detection and selection policy for the two WebRTC Encoded Transform APIs.
|
|
5
|
+
* Internal: which one the SDK attaches is an RTC-layer detail. Consumers want
|
|
6
|
+
* `EncryptionManager.isSupported` instead.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Chrome only, and the reason an RTCPeerConnection carrying E2EE needs the
|
|
11
|
+
* non-standard `encodedInsertableStreams` flag.
|
|
12
|
+
*/
|
|
13
|
+
export const hasInsertableStreams = (): boolean =>
|
|
14
|
+
typeof RTCRtpSender !== 'undefined' &&
|
|
15
|
+
'createEncodedStreams' in RTCRtpSender.prototype;
|
|
16
|
+
|
|
17
|
+
/** Whether the standard `RTCRtpScriptTransform` API exists. */
|
|
18
|
+
export const hasScriptTransform = (): boolean =>
|
|
19
|
+
typeof RTCRtpScriptTransform !== 'undefined';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Which Encoded Transform API to attach E2EE with here.
|
|
23
|
+
*
|
|
24
|
+
* - `'insertable'`: legacy Insertable Streams. Used on Chrome, where
|
|
25
|
+
* `RTCRtpScriptTransform` is still unreliable for E2EE.
|
|
26
|
+
* - `'script'`: the standard API. Used everywhere else.
|
|
27
|
+
* - `undefined`: neither exists, so E2EE cannot run.
|
|
28
|
+
*/
|
|
29
|
+
export const preferredTransform = (): 'script' | 'insertable' | undefined => {
|
|
30
|
+
const insertable = hasInsertableStreams();
|
|
31
|
+
// Chrome's RTCRtpScriptTransform is still unreliable for E2EE.
|
|
32
|
+
if (isChrome() && insertable) return 'insertable';
|
|
33
|
+
if (hasScriptTransform()) return 'script';
|
|
34
|
+
return insertable ? 'insertable' : undefined;
|
|
35
|
+
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ICEServerResponse } from '../../gen/coordinator';
|
|
2
2
|
|
|
3
|
-
export const toRtcConfiguration = (
|
|
3
|
+
export const toRtcConfiguration = (
|
|
4
|
+
config: ICEServerResponse[],
|
|
5
|
+
): RTCConfiguration => {
|
|
4
6
|
return {
|
|
5
7
|
bundlePolicy: 'max-bundle',
|
|
6
8
|
iceServers: config.map((ice) => ({
|
package/src/rtc/index.ts
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A per-call media engine. On React Native it owns the call's native
|
|
3
|
+
* `PeerConnectionFactory` (built with the call's audio configuration); on web it
|
|
4
|
+
* is a no-op. Capture (`getUserMedia`/`getDisplayMedia`) and peer-connection creation
|
|
5
|
+
* always go through the WebRTC globals, which resolve to this factory while it
|
|
6
|
+
* is the live call factory — so the engine only needs to manage its lifecycle.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export interface CallMediaEngine {
|
|
11
|
+
dispose(): Promise<boolean>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates a per-call {@link CallMediaEngine}. Registered once at SDK startup
|
|
16
|
+
* via {@link setCallMediaEngineProvider}. May return the engine synchronously
|
|
17
|
+
* (the default globals engine) or asynchronously (React Native, where allocating
|
|
18
|
+
* the native per-call factory is an async bridge call).
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export type CallMediaEngineProvider = () =>
|
|
23
|
+
CallMediaEngine | Promise<CallMediaEngine>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The default engine: a thin, stateless wrapper over the WebRTC globals.
|
|
27
|
+
*/
|
|
28
|
+
const defaultGlobalsEngine: CallMediaEngine = {
|
|
29
|
+
dispose: () => Promise.resolve(true),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const defaultGlobalsEngineProvider: CallMediaEngineProvider = () =>
|
|
33
|
+
defaultGlobalsEngine;
|
|
34
|
+
|
|
35
|
+
let provider: CallMediaEngineProvider = defaultGlobalsEngineProvider;
|
|
36
|
+
|
|
37
|
+
export const setCallMediaEngineProvider = (
|
|
38
|
+
newProvider: CallMediaEngineProvider,
|
|
39
|
+
): void => {
|
|
40
|
+
provider = newProvider;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const getCallMediaEngineProvider = (): CallMediaEngineProvider =>
|
|
44
|
+
provider;
|
package/src/rtc/types.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { CallState } from '../store';
|
|
|
10
10
|
import { Dispatcher } from './Dispatcher';
|
|
11
11
|
import type { OptimalVideoLayer } from './layers';
|
|
12
12
|
import type { ClientPublishOptions } from '../types';
|
|
13
|
+
import type { E2EEManager } from './e2ee/E2EEManager';
|
|
13
14
|
import type { VideoSender } from '../gen/video/sfu/event/events';
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -39,8 +40,7 @@ export const ReconnectReason = {
|
|
|
39
40
|
} as const;
|
|
40
41
|
|
|
41
42
|
export type ReconnectReason =
|
|
42
|
-
|
|
43
|
-
| (string & {});
|
|
43
|
+
(typeof ReconnectReason)[keyof typeof ReconnectReason] | (string & {});
|
|
44
44
|
|
|
45
45
|
export type OnReconnectionNeeded = (
|
|
46
46
|
kind: WebsocketReconnectStrategy,
|
|
@@ -103,6 +103,7 @@ export type BasePeerConnectionOpts = {
|
|
|
103
103
|
iceRestartDelay?: number;
|
|
104
104
|
clientPublishOptions?: ClientPublishOptions;
|
|
105
105
|
statsTimestampDriftThresholdMs?: number;
|
|
106
|
+
e2ee?: E2EEManager;
|
|
106
107
|
};
|
|
107
108
|
|
|
108
109
|
export type TrackPublishOptions = {
|
package/src/stats/rtc/types.ts
CHANGED
|
@@ -12,9 +12,15 @@ export type RTCStatsDataType =
|
|
|
12
12
|
| boolean
|
|
13
13
|
| RTCOfferOptions
|
|
14
14
|
| [string | RTCDataChannelInit | undefined] // createDataChannel
|
|
15
|
-
| (
|
|
15
|
+
| (
|
|
16
|
+
| RTCOfferOptions
|
|
17
|
+
| undefined // createOffer | createAnswer
|
|
18
|
+
)
|
|
16
19
|
| RTCSessionDescriptionInit
|
|
17
|
-
| (
|
|
20
|
+
| (
|
|
21
|
+
| RTCIceCandidateInit
|
|
22
|
+
| RTCIceCandidate // addIceCandidate
|
|
23
|
+
)
|
|
18
24
|
| object
|
|
19
25
|
| number
|
|
20
26
|
| null
|
package/src/store/CallState.ts
CHANGED
|
@@ -74,6 +74,7 @@ type OrphanedTrack = {
|
|
|
74
74
|
trackLookupPrefix: string;
|
|
75
75
|
trackType: TrackType;
|
|
76
76
|
track: MediaStream;
|
|
77
|
+
receiver?: RTCRtpReceiver;
|
|
77
78
|
};
|
|
78
79
|
|
|
79
80
|
/**
|
|
@@ -108,6 +109,7 @@ export class CallState {
|
|
|
108
109
|
>(undefined);
|
|
109
110
|
private transcribingSubject = new BehaviorSubject<boolean>(false);
|
|
110
111
|
private captioningSubject = new BehaviorSubject<boolean>(false);
|
|
112
|
+
private e2eeEnabledSubject = new BehaviorSubject<boolean>(false);
|
|
111
113
|
private endedBySubject = new BehaviorSubject<UserResponse | undefined>(
|
|
112
114
|
undefined,
|
|
113
115
|
);
|
|
@@ -311,6 +313,12 @@ export class CallState {
|
|
|
311
313
|
*/
|
|
312
314
|
captioning$: Observable<boolean>;
|
|
313
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Whether end-to-end encryption is active for this call, as reported by the
|
|
318
|
+
* SFU in the join response.
|
|
319
|
+
*/
|
|
320
|
+
e2eeEnabled$: Observable<boolean>;
|
|
321
|
+
|
|
314
322
|
/**
|
|
315
323
|
* Will provide the user who ended this call.
|
|
316
324
|
*/
|
|
@@ -341,8 +349,7 @@ export class CallState {
|
|
|
341
349
|
|
|
342
350
|
private readonly eventHandlers: {
|
|
343
351
|
[EventType in VideoEvent['type']]:
|
|
344
|
-
|
|
345
|
-
| undefined;
|
|
352
|
+
((event: Extract<VideoEvent, { type: EventType }>) => void) | undefined;
|
|
346
353
|
};
|
|
347
354
|
|
|
348
355
|
/**
|
|
@@ -468,6 +475,7 @@ export class CallState {
|
|
|
468
475
|
this.rawRecording$ = duc(this.rawRecordingSubject);
|
|
469
476
|
this.transcribing$ = duc(this.transcribingSubject);
|
|
470
477
|
this.captioning$ = duc(this.captioningSubject);
|
|
478
|
+
this.e2eeEnabled$ = duc(this.e2eeEnabledSubject);
|
|
471
479
|
|
|
472
480
|
this.eventHandlers = {
|
|
473
481
|
// these events are not updating the call state:
|
|
@@ -575,6 +583,7 @@ export class CallState {
|
|
|
575
583
|
clearTimeout(taskId);
|
|
576
584
|
this.closedCaptionsTasks.delete(ccKey);
|
|
577
585
|
}
|
|
586
|
+
this.removeAllOrphanedTracks();
|
|
578
587
|
};
|
|
579
588
|
|
|
580
589
|
/**
|
|
@@ -974,6 +983,13 @@ export class CallState {
|
|
|
974
983
|
return this.getCurrentValue(this.transcribing$);
|
|
975
984
|
}
|
|
976
985
|
|
|
986
|
+
/**
|
|
987
|
+
* Whether end-to-end encryption is active for this call.
|
|
988
|
+
*/
|
|
989
|
+
get e2eeEnabled() {
|
|
990
|
+
return this.getCurrentValue(this.e2eeEnabled$);
|
|
991
|
+
}
|
|
992
|
+
|
|
977
993
|
/**
|
|
978
994
|
* Will provide the user who ended this call.
|
|
979
995
|
*/
|
|
@@ -1250,6 +1266,19 @@ export class CallState {
|
|
|
1250
1266
|
this.orphanedTracks = this.orphanedTracks.filter((o) => o.id !== id);
|
|
1251
1267
|
};
|
|
1252
1268
|
|
|
1269
|
+
/**
|
|
1270
|
+
* Drops every orphaned track. Call this when the peer connections that own
|
|
1271
|
+
* the stored receivers go away (full leave, reconnect, or migration):
|
|
1272
|
+
* `pc.close()` does not raise the track `ended` event, so the per-track
|
|
1273
|
+
* cleanup never fires and the receivers + their closed PCs would otherwise
|
|
1274
|
+
* leak for the call's lifetime.
|
|
1275
|
+
*
|
|
1276
|
+
* @internal
|
|
1277
|
+
*/
|
|
1278
|
+
removeAllOrphanedTracks = () => {
|
|
1279
|
+
this.orphanedTracks = [];
|
|
1280
|
+
};
|
|
1281
|
+
|
|
1253
1282
|
/**
|
|
1254
1283
|
* Takes all orphaned tracks with the given track lookup prefix.
|
|
1255
1284
|
* All orphaned tracks with the given track lookup prefix are removed from the call state.
|
|
@@ -1337,7 +1366,8 @@ export class CallState {
|
|
|
1337
1366
|
currentSessionId: string,
|
|
1338
1367
|
reconnectDetails?: ReconnectDetails,
|
|
1339
1368
|
) => {
|
|
1340
|
-
const { participants, participantCount, startedAt, pins } =
|
|
1369
|
+
const { participants, participantCount, startedAt, pins, e2EeEnabled } =
|
|
1370
|
+
callState;
|
|
1341
1371
|
const localPublishedTracks =
|
|
1342
1372
|
reconnectDetails?.announcedTracks.map((t) => t.trackType) ?? [];
|
|
1343
1373
|
this.setParticipants(() => {
|
|
@@ -1366,6 +1396,7 @@ export class CallState {
|
|
|
1366
1396
|
this.setAnonymousParticipantCount(participantCount?.anonymous || 0);
|
|
1367
1397
|
this.setStartedAt(startedAt ? Timestamp.toDate(startedAt) : new Date());
|
|
1368
1398
|
this.setServerSidePins(pins);
|
|
1399
|
+
this.setCurrentValue(this.e2eeEnabledSubject, e2EeEnabled);
|
|
1369
1400
|
};
|
|
1370
1401
|
|
|
1371
1402
|
private updateFromMemberRemoved = (event: CallMemberRemovedEvent) => {
|
|
@@ -5,7 +5,10 @@ import { fromPartial } from '@total-typescript/shoehorn';
|
|
|
5
5
|
import { StreamVideoParticipant, VisibilityState } from '../../types';
|
|
6
6
|
import { CallingState } from '../CallingState';
|
|
7
7
|
import { CallState } from '../CallState';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
type CallState as SfuCallState,
|
|
10
|
+
TrackType,
|
|
11
|
+
} from '../../gen/video/sfu/models/models';
|
|
9
12
|
import {
|
|
10
13
|
combineComparators,
|
|
11
14
|
conditional,
|
|
@@ -387,6 +390,48 @@ describe('CallState', () => {
|
|
|
387
390
|
});
|
|
388
391
|
});
|
|
389
392
|
|
|
393
|
+
describe('e2ee', () => {
|
|
394
|
+
const sfuCallState = (e2EeEnabled: boolean) =>
|
|
395
|
+
fromPartial<SfuCallState>({
|
|
396
|
+
participants: [],
|
|
397
|
+
pins: [],
|
|
398
|
+
e2EeEnabled,
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('is disabled until the SFU reports otherwise', () => {
|
|
402
|
+
const state = new CallState();
|
|
403
|
+
expect(state.e2eeEnabled).toBe(false);
|
|
404
|
+
|
|
405
|
+
state.updateFromSfuCallState(sfuCallState(false), 'session-id');
|
|
406
|
+
expect(state.e2eeEnabled).toBe(false);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('is enabled when the SFU call state says so', () => {
|
|
410
|
+
const state = new CallState();
|
|
411
|
+
state.updateFromSfuCallState(sfuCallState(true), 'session-id');
|
|
412
|
+
expect(state.e2eeEnabled).toBe(true);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('follows the SFU across a rejoin into a plain call', () => {
|
|
416
|
+
const state = new CallState();
|
|
417
|
+
state.updateFromSfuCallState(sfuCallState(true), 'session-id');
|
|
418
|
+
state.updateFromSfuCallState(sfuCallState(false), 'session-id');
|
|
419
|
+
expect(state.e2eeEnabled).toBe(false);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it(`doesn't emit when the value didn't change`, () => {
|
|
423
|
+
const state = new CallState();
|
|
424
|
+
const listener = vi.fn();
|
|
425
|
+
state.e2eeEnabled$.subscribe(listener);
|
|
426
|
+
expect(listener).toHaveBeenCalledTimes(1); // initial value
|
|
427
|
+
|
|
428
|
+
state.updateFromSfuCallState(sfuCallState(true), 'session-id');
|
|
429
|
+
state.updateFromSfuCallState(sfuCallState(true), 'session-id');
|
|
430
|
+
expect(listener).toHaveBeenCalledTimes(2);
|
|
431
|
+
expect(listener).toHaveBeenLastCalledWith(true);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
390
435
|
describe('events', () => {
|
|
391
436
|
describe('call.live and backstage events', () => {
|
|
392
437
|
it('handles call.live_started events', () => {
|
|
@@ -1291,6 +1336,42 @@ describe('CallState', () => {
|
|
|
1291
1336
|
state.removeOrphanedTrack(id);
|
|
1292
1337
|
expect(state['orphanedTracks'].length).toBe(0);
|
|
1293
1338
|
});
|
|
1339
|
+
|
|
1340
|
+
it('removes all orphaned tracks at once', () => {
|
|
1341
|
+
const state = new CallState();
|
|
1342
|
+
state.registerOrphanedTrack({
|
|
1343
|
+
id: 'a',
|
|
1344
|
+
track: new MediaStream(),
|
|
1345
|
+
trackLookupPrefix: '1',
|
|
1346
|
+
trackType: TrackType.VIDEO,
|
|
1347
|
+
});
|
|
1348
|
+
state.registerOrphanedTrack({
|
|
1349
|
+
id: 'b',
|
|
1350
|
+
track: new MediaStream(),
|
|
1351
|
+
trackLookupPrefix: '2',
|
|
1352
|
+
trackType: TrackType.AUDIO,
|
|
1353
|
+
});
|
|
1354
|
+
expect(state['orphanedTracks'].length).toBe(2);
|
|
1355
|
+
state.removeAllOrphanedTracks();
|
|
1356
|
+
expect(state['orphanedTracks'].length).toBe(0);
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
it('purges orphaned tracks on dispose (no receiver/PC leak)', () => {
|
|
1360
|
+
// Orphaned tracks can hold an RTCRtpReceiver tied to a now-closed peer
|
|
1361
|
+
// connection; pc.close() does not raise the track `ended` event, so they
|
|
1362
|
+
// are never purged otherwise and leak for the call's lifetime (finding
|
|
1363
|
+
// 15).
|
|
1364
|
+
const state = new CallState();
|
|
1365
|
+
state.registerOrphanedTrack({
|
|
1366
|
+
id: '123:TRACK_TYPE_VIDEO',
|
|
1367
|
+
track: new MediaStream(),
|
|
1368
|
+
trackLookupPrefix: '123',
|
|
1369
|
+
trackType: TrackType.VIDEO,
|
|
1370
|
+
});
|
|
1371
|
+
expect(state['orphanedTracks'].length).toBe(1);
|
|
1372
|
+
state.dispose();
|
|
1373
|
+
expect(state['orphanedTracks'].length).toBe(0);
|
|
1374
|
+
});
|
|
1294
1375
|
});
|
|
1295
1376
|
|
|
1296
1377
|
describe('closed captions', () => {
|
package/src/types.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
JoinCallRequest,
|
|
10
10
|
MemberResponse,
|
|
11
11
|
OwnCapability,
|
|
12
|
-
|
|
12
|
+
VideoReactionResponse,
|
|
13
13
|
StartRecordingRequest,
|
|
14
14
|
StartRecordingResponse,
|
|
15
15
|
} from './gen/coordinator';
|
|
@@ -27,7 +27,7 @@ import { AxiosError } from 'axios';
|
|
|
27
27
|
import type { Call } from './Call';
|
|
28
28
|
|
|
29
29
|
export type StreamReaction = Pick<
|
|
30
|
-
|
|
30
|
+
VideoReactionResponse,
|
|
31
31
|
'type' | 'emoji_code' | 'custom'
|
|
32
32
|
>;
|
|
33
33
|
|
|
@@ -143,10 +143,7 @@ export interface StreamVideoParticipant extends Participant {
|
|
|
143
143
|
export type VideoTrackType = 'videoTrack' | 'screenShareTrack';
|
|
144
144
|
export type AudioTrackType = 'audioTrack' | 'screenShareAudioTrack';
|
|
145
145
|
export type TrackMuteType =
|
|
146
|
-
| '
|
|
147
|
-
| 'video'
|
|
148
|
-
| 'screenshare'
|
|
149
|
-
| 'screenshare_audio';
|
|
146
|
+
'audio' | 'video' | 'screenshare' | 'screenshare_audio';
|
|
150
147
|
|
|
151
148
|
/**
|
|
152
149
|
* Represents a participant's pin state.
|
|
@@ -424,9 +421,20 @@ type StreamRNVideoSDKCallManagerRingingParams = {
|
|
|
424
421
|
|
|
425
422
|
type StreamRNVideoSDKCallManagerSetupParams =
|
|
426
423
|
StreamRNVideoSDKCallManagerRingingParams & {
|
|
424
|
+
cid: string;
|
|
427
425
|
defaultDevice: AudioSettingsRequestDefaultDeviceEnum;
|
|
428
426
|
};
|
|
429
427
|
|
|
428
|
+
type StreamRNVideoSDKCallManagerStartParams =
|
|
429
|
+
StreamRNVideoSDKCallManagerRingingParams & {
|
|
430
|
+
cid: string;
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
type StreamRNVideoSDKCallManagerStopParams =
|
|
434
|
+
StreamRNVideoSDKCallManagerRingingParams & {
|
|
435
|
+
shouldStopCallManager: boolean;
|
|
436
|
+
};
|
|
437
|
+
|
|
430
438
|
type StreamRNVideoSDKEndCallReason =
|
|
431
439
|
/** Call ended by the local user (e.g., hanging up). */
|
|
432
440
|
| 'local'
|
|
@@ -456,6 +464,8 @@ type StreamRNVideoSDKCallingX = {
|
|
|
456
464
|
reason?: StreamRNVideoSDKEndCallReason,
|
|
457
465
|
) => Promise<void>;
|
|
458
466
|
registerOutgoingCall: (call: Call) => Promise<void>;
|
|
467
|
+
wireAudioEngineSubscription: () => void;
|
|
468
|
+
unwireAudioEngineSubscription: () => void;
|
|
459
469
|
};
|
|
460
470
|
|
|
461
471
|
export type StreamRNVideoSDKGlobals = {
|
|
@@ -467,6 +477,7 @@ export type StreamRNVideoSDKGlobals = {
|
|
|
467
477
|
setup({
|
|
468
478
|
defaultDevice,
|
|
469
479
|
isRingingTypeCall,
|
|
480
|
+
cid,
|
|
470
481
|
}: StreamRNVideoSDKCallManagerSetupParams): void;
|
|
471
482
|
|
|
472
483
|
/**
|
|
@@ -474,12 +485,16 @@ export type StreamRNVideoSDKGlobals = {
|
|
|
474
485
|
*/
|
|
475
486
|
start({
|
|
476
487
|
isRingingTypeCall,
|
|
477
|
-
|
|
488
|
+
cid,
|
|
489
|
+
}: StreamRNVideoSDKCallManagerStartParams): void;
|
|
478
490
|
|
|
479
491
|
/**
|
|
480
492
|
* Stops the in call manager.
|
|
481
493
|
*/
|
|
482
|
-
stop({
|
|
494
|
+
stop({
|
|
495
|
+
isRingingTypeCall,
|
|
496
|
+
shouldStopCallManager,
|
|
497
|
+
}: StreamRNVideoSDKCallManagerStopParams): void;
|
|
483
498
|
|
|
484
499
|
/**
|
|
485
500
|
* iOS-only. Keeps the audio engine's microphone-input (voice-processing)
|
|
@@ -517,5 +532,5 @@ declare global {
|
|
|
517
532
|
/**
|
|
518
533
|
* The options to pass to {@link Call.join} method.
|
|
519
534
|
*/
|
|
520
|
-
export type JoinCallData = Omit<JoinCallRequest, 'location'>;
|
|
535
|
+
export type JoinCallData = Omit<JoinCallRequest, 'location' | 'e2ee'>;
|
|
521
536
|
export { AxiosError };
|