@stream-io/video-client 1.40.3 → 1.41.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/index.browser.es.js +245 -38
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +252 -37
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +245 -38
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +4 -4
- package/dist/src/devices/SpeakerManager.d.ts +3 -2
- package/dist/src/gen/coordinator/index.d.ts +269 -0
- package/dist/src/helpers/DynascaleManager.d.ts +13 -1
- package/dist/src/helpers/types.d.ts +16 -0
- package/dist/src/rtc/helpers/sdp.d.ts +8 -0
- package/dist/src/store/CallState.d.ts +21 -2
- package/dist/src/types.d.ts +29 -1
- package/package.json +2 -2
- package/src/Call.ts +29 -6
- package/src/devices/SpeakerManager.ts +40 -2
- package/src/devices/__tests__/MicrophoneManager.test.ts +34 -23
- package/src/devices/__tests__/mocks.ts +14 -12
- package/src/events/__tests__/call.test.ts +4 -5
- package/src/gen/coordinator/index.ts +293 -0
- package/src/helpers/DynascaleManager.ts +57 -9
- package/src/helpers/__tests__/DynascaleManager.test.ts +8 -1
- package/src/helpers/types.ts +26 -0
- package/src/rtc/Publisher.ts +1 -1
- package/src/rtc/__tests__/mocks/webrtc.mocks.ts +1 -0
- package/src/rtc/helpers/__tests__/sdp.startBitrate.test.ts +105 -0
- package/src/rtc/helpers/sdp.ts +30 -12
- package/src/store/CallState.ts +72 -9
- package/src/store/__tests__/CallState.test.ts +462 -106
- package/src/types.ts +42 -0
package/src/Call.ts
CHANGED
|
@@ -109,9 +109,11 @@ import {
|
|
|
109
109
|
AudioTrackType,
|
|
110
110
|
CallConstructor,
|
|
111
111
|
CallLeaveOptions,
|
|
112
|
+
CallRecordingType,
|
|
112
113
|
ClientPublishOptions,
|
|
113
114
|
ClosedCaptionsSettings,
|
|
114
115
|
JoinCallData,
|
|
116
|
+
StartCallRecordingFnType,
|
|
115
117
|
TrackMuteType,
|
|
116
118
|
VideoTrackType,
|
|
117
119
|
} from './types';
|
|
@@ -337,7 +339,11 @@ export class Call {
|
|
|
337
339
|
this.microphone = new MicrophoneManager(this);
|
|
338
340
|
this.speaker = new SpeakerManager(this);
|
|
339
341
|
this.screenShare = new ScreenShareManager(this);
|
|
340
|
-
this.dynascaleManager = new DynascaleManager(
|
|
342
|
+
this.dynascaleManager = new DynascaleManager(
|
|
343
|
+
this.state,
|
|
344
|
+
this.speaker,
|
|
345
|
+
this.tracer,
|
|
346
|
+
);
|
|
341
347
|
}
|
|
342
348
|
|
|
343
349
|
/**
|
|
@@ -662,6 +668,8 @@ export class Call {
|
|
|
662
668
|
this.cancelAutoDrop();
|
|
663
669
|
this.clientStore.unregisterCall(this);
|
|
664
670
|
|
|
671
|
+
globalThis.streamRNVideoSDK?.callManager.stop();
|
|
672
|
+
|
|
665
673
|
this.camera.dispose();
|
|
666
674
|
this.microphone.dispose();
|
|
667
675
|
this.screenShare.dispose();
|
|
@@ -1113,6 +1121,7 @@ export class Call {
|
|
|
1113
1121
|
// re-apply them on later reconnections or server-side data fetches
|
|
1114
1122
|
if (!this.deviceSettingsAppliedOnce && this.state.settings) {
|
|
1115
1123
|
await this.applyDeviceConfig(this.state.settings, true);
|
|
1124
|
+
globalThis.streamRNVideoSDK?.callManager.start();
|
|
1116
1125
|
this.deviceSettingsAppliedOnce = true;
|
|
1117
1126
|
}
|
|
1118
1127
|
|
|
@@ -2096,20 +2105,33 @@ export class Call {
|
|
|
2096
2105
|
/**
|
|
2097
2106
|
* Starts recording the call
|
|
2098
2107
|
*/
|
|
2099
|
-
startRecording = async (
|
|
2108
|
+
startRecording: StartCallRecordingFnType = async (
|
|
2109
|
+
dataOrType?: StartRecordingRequest | CallRecordingType,
|
|
2110
|
+
type?: CallRecordingType,
|
|
2111
|
+
): Promise<StartRecordingResponse> => {
|
|
2112
|
+
type = typeof dataOrType === 'string' ? dataOrType : type;
|
|
2113
|
+
dataOrType = typeof dataOrType === 'string' ? undefined : dataOrType;
|
|
2114
|
+
|
|
2115
|
+
const endpoint = !type
|
|
2116
|
+
? `/start_recording`
|
|
2117
|
+
: `/recordings/${encodeURIComponent(type)}/start`;
|
|
2118
|
+
|
|
2100
2119
|
return this.streamClient.post<
|
|
2101
2120
|
StartRecordingResponse,
|
|
2102
2121
|
StartRecordingRequest
|
|
2103
|
-
>(`${this.streamClientBasePath}
|
|
2122
|
+
>(`${this.streamClientBasePath}${endpoint}`, dataOrType);
|
|
2104
2123
|
};
|
|
2105
2124
|
|
|
2106
2125
|
/**
|
|
2107
2126
|
* Stops recording the call
|
|
2108
2127
|
*/
|
|
2109
|
-
stopRecording = async () => {
|
|
2128
|
+
stopRecording = async (type?: CallRecordingType) => {
|
|
2129
|
+
const endpoint = !type
|
|
2130
|
+
? `/stop_recording`
|
|
2131
|
+
: `/recordings/${encodeURIComponent(type)}/stop`;
|
|
2132
|
+
|
|
2110
2133
|
return this.streamClient.post<StopRecordingResponse>(
|
|
2111
|
-
`${this.streamClientBasePath}
|
|
2112
|
-
{},
|
|
2134
|
+
`${this.streamClientBasePath}${endpoint}`,
|
|
2113
2135
|
);
|
|
2114
2136
|
};
|
|
2115
2137
|
|
|
@@ -2676,6 +2698,7 @@ export class Call {
|
|
|
2676
2698
|
settings: CallSettingsResponse,
|
|
2677
2699
|
publish: boolean,
|
|
2678
2700
|
) => {
|
|
2701
|
+
this.speaker.apply(settings);
|
|
2679
2702
|
await this.camera.apply(settings.video, publish).catch((err) => {
|
|
2680
2703
|
this.logger.warn('Camera init failed', err);
|
|
2681
2704
|
});
|
|
@@ -3,12 +3,17 @@ import { Call } from '../Call';
|
|
|
3
3
|
import { isReactNative } from '../helpers/platforms';
|
|
4
4
|
import { SpeakerState } from './SpeakerState';
|
|
5
5
|
import { deviceIds$, getAudioOutputDevices } from './devices';
|
|
6
|
+
import {
|
|
7
|
+
CallSettingsResponse,
|
|
8
|
+
AudioSettingsRequestDefaultDeviceEnum,
|
|
9
|
+
} from '../gen/coordinator';
|
|
6
10
|
|
|
7
11
|
export class SpeakerManager {
|
|
8
12
|
readonly state: SpeakerState;
|
|
9
13
|
private subscriptions: Subscription[] = [];
|
|
10
14
|
private areSubscriptionsSetUp = false;
|
|
11
15
|
private readonly call: Call;
|
|
16
|
+
private defaultDevice?: AudioSettingsRequestDefaultDeviceEnum;
|
|
12
17
|
|
|
13
18
|
constructor(call: Call) {
|
|
14
19
|
this.call = call;
|
|
@@ -16,6 +21,41 @@ export class SpeakerManager {
|
|
|
16
21
|
this.setup();
|
|
17
22
|
}
|
|
18
23
|
|
|
24
|
+
apply(settings: CallSettingsResponse) {
|
|
25
|
+
if (!isReactNative()) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
/// Determines if the speaker should be enabled based on a priority hierarchy of
|
|
29
|
+
/// settings.
|
|
30
|
+
///
|
|
31
|
+
/// The priority order is as follows:
|
|
32
|
+
/// 1. If video camera is set to be on by default, speaker is enabled
|
|
33
|
+
/// 2. If audio speaker is set to be on by default, speaker is enabled
|
|
34
|
+
/// 3. If the default audio device is set to speaker, speaker is enabled
|
|
35
|
+
///
|
|
36
|
+
/// This ensures that the speaker state aligns with the most important user
|
|
37
|
+
/// preference or system requirement.
|
|
38
|
+
const speakerOnWithSettingsPriority =
|
|
39
|
+
settings.video.camera_default_on ||
|
|
40
|
+
settings.audio.speaker_default_on ||
|
|
41
|
+
settings.audio.default_device ===
|
|
42
|
+
AudioSettingsRequestDefaultDeviceEnum.SPEAKER;
|
|
43
|
+
|
|
44
|
+
const defaultDevice = speakerOnWithSettingsPriority
|
|
45
|
+
? AudioSettingsRequestDefaultDeviceEnum.SPEAKER
|
|
46
|
+
: AudioSettingsRequestDefaultDeviceEnum.EARPIECE;
|
|
47
|
+
|
|
48
|
+
if (this.defaultDevice !== defaultDevice) {
|
|
49
|
+
this.call.logger.debug('SpeakerManager: setting default device', {
|
|
50
|
+
defaultDevice,
|
|
51
|
+
});
|
|
52
|
+
this.defaultDevice = defaultDevice;
|
|
53
|
+
globalThis.streamRNVideoSDK?.callManager.setup({
|
|
54
|
+
defaultDevice,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
19
59
|
setup() {
|
|
20
60
|
if (this.areSubscriptionsSetUp) {
|
|
21
61
|
return;
|
|
@@ -107,8 +147,6 @@ export class SpeakerManager {
|
|
|
107
147
|
/**
|
|
108
148
|
* Set the volume of a participant.
|
|
109
149
|
*
|
|
110
|
-
* Note: This method is not supported in React Native.
|
|
111
|
-
*
|
|
112
150
|
* @param sessionId the participant's session id.
|
|
113
151
|
* @param volume a number between 0 and 1. Set it to `undefined` to use the default volume.
|
|
114
152
|
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest';
|
|
2
|
+
import { fromPartial } from '@total-typescript/shoehorn';
|
|
2
3
|
import { NoiseCancellationStub } from './NoiseCancellationStub';
|
|
3
4
|
import { Call } from '../../Call';
|
|
4
5
|
import { StreamClient } from '../../coordinator/connection/client';
|
|
@@ -263,14 +264,18 @@ describe('MicrophoneManager', () => {
|
|
|
263
264
|
|
|
264
265
|
it('should throw when noise cancellation is disabled in call settings', async () => {
|
|
265
266
|
call.state.setOwnCapabilities([OwnCapability.ENABLE_NOISE_CANCELLATION]);
|
|
266
|
-
call.state.updateFromCallResponse(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
267
|
+
call.state.updateFromCallResponse(
|
|
268
|
+
fromPartial({
|
|
269
|
+
egress: {},
|
|
270
|
+
settings: {
|
|
271
|
+
audio: {
|
|
272
|
+
noise_cancellation: {
|
|
273
|
+
mode: NoiseCancellationSettingsModeEnum.DISABLED,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
271
276
|
},
|
|
272
|
-
},
|
|
273
|
-
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
274
279
|
await expect(() =>
|
|
275
280
|
manager.enableNoiseCancellation(new NoiseCancellationStub()),
|
|
276
281
|
).rejects.toThrow();
|
|
@@ -278,16 +283,18 @@ describe('MicrophoneManager', () => {
|
|
|
278
283
|
|
|
279
284
|
it('should automatically enable noise noise suppression after joining a call', async () => {
|
|
280
285
|
call.state.setCallingState(CallingState.IDLE); // reset state
|
|
281
|
-
call.state.updateFromCallResponse(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
286
|
+
call.state.updateFromCallResponse(
|
|
287
|
+
fromPartial({
|
|
288
|
+
egress: {},
|
|
289
|
+
settings: {
|
|
290
|
+
audio: {
|
|
291
|
+
noise_cancellation: {
|
|
292
|
+
mode: NoiseCancellationSettingsModeEnum.AUTO_ON,
|
|
293
|
+
},
|
|
287
294
|
},
|
|
288
295
|
},
|
|
289
|
-
},
|
|
290
|
-
|
|
296
|
+
}),
|
|
297
|
+
);
|
|
291
298
|
|
|
292
299
|
const noiseCancellation = new NoiseCancellationStub();
|
|
293
300
|
const noiseCancellationEnable = vi.spyOn(noiseCancellation, 'enable');
|
|
@@ -366,10 +373,12 @@ describe('MicrophoneManager', () => {
|
|
|
366
373
|
|
|
367
374
|
describe('Hi-Fi Audio', () => {
|
|
368
375
|
it('enables hi-fi audio', async () => {
|
|
369
|
-
call.state.updateFromCallResponse(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
376
|
+
call.state.updateFromCallResponse(
|
|
377
|
+
fromPartial({
|
|
378
|
+
egress: {},
|
|
379
|
+
settings: { audio: { hifi_audio_enabled: true } },
|
|
380
|
+
}),
|
|
381
|
+
);
|
|
373
382
|
|
|
374
383
|
await manager.enable();
|
|
375
384
|
|
|
@@ -388,10 +397,12 @@ describe('MicrophoneManager', () => {
|
|
|
388
397
|
});
|
|
389
398
|
|
|
390
399
|
it('throws an error when enabling hi-fi audio if not allowed', async () => {
|
|
391
|
-
call.state.updateFromCallResponse(
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
400
|
+
call.state.updateFromCallResponse(
|
|
401
|
+
fromPartial({
|
|
402
|
+
egress: {},
|
|
403
|
+
settings: { audio: { hifi_audio_enabled: false } },
|
|
404
|
+
}),
|
|
405
|
+
);
|
|
395
406
|
|
|
396
407
|
await manager.enable();
|
|
397
408
|
await expect(() =>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { vi } from 'vitest';
|
|
2
|
+
import { fromPartial } from '@total-typescript/shoehorn';
|
|
2
3
|
import { CallingState, CallState } from '../../store';
|
|
3
4
|
import {
|
|
4
5
|
NoiseCancellationSettingsModeEnum,
|
|
@@ -78,20 +79,21 @@ export const mockCall = (): Partial<Call> => {
|
|
|
78
79
|
OwnCapability.SEND_VIDEO,
|
|
79
80
|
OwnCapability.ENABLE_NOISE_CANCELLATION,
|
|
80
81
|
]);
|
|
81
|
-
callState.updateFromCallResponse(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
callState.updateFromCallResponse(
|
|
83
|
+
fromPartial({
|
|
84
|
+
egress: {},
|
|
85
|
+
settings: {
|
|
86
|
+
audio: {
|
|
87
|
+
noise_cancellation: {
|
|
88
|
+
mode: NoiseCancellationSettingsModeEnum.AVAILABLE,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
screensharing: {
|
|
92
|
+
target_resolution: undefined,
|
|
87
93
|
},
|
|
88
94
|
},
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
target_resolution: undefined,
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
95
97
|
return {
|
|
96
98
|
state: callState,
|
|
97
99
|
publish: vi.fn(),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { fromPartial } from '@total-typescript/shoehorn';
|
|
2
3
|
import { CallingState, StreamVideoWriteableStateStore } from '../../store';
|
|
3
4
|
import {
|
|
4
5
|
watchCallAccepted,
|
|
@@ -376,17 +377,16 @@ const fakeCall = ({ ring = true, currentUserId = 'test-user-id' } = {}) => {
|
|
|
376
377
|
};
|
|
377
378
|
|
|
378
379
|
const fakeMetadata = (): CallResponse => {
|
|
379
|
-
return {
|
|
380
|
+
return fromPartial({
|
|
380
381
|
id: '12345',
|
|
381
382
|
type: 'development',
|
|
382
383
|
cid: 'development:12345',
|
|
383
384
|
|
|
384
|
-
// @ts-expect-error type issue
|
|
385
385
|
created_by: {
|
|
386
386
|
id: 'test-user-id',
|
|
387
387
|
},
|
|
388
|
-
own_capabilities: [],
|
|
389
388
|
blocked_user_ids: [],
|
|
389
|
+
egress: {},
|
|
390
390
|
|
|
391
391
|
settings: {
|
|
392
392
|
ring: {
|
|
@@ -394,10 +394,9 @@ const fakeMetadata = (): CallResponse => {
|
|
|
394
394
|
incoming_call_timeout_ms: 30000,
|
|
395
395
|
missed_call_timeout_ms: 30000,
|
|
396
396
|
},
|
|
397
|
-
// @ts-expect-error type issue
|
|
398
397
|
screensharing: {
|
|
399
398
|
target_resolution: undefined,
|
|
400
399
|
},
|
|
401
400
|
},
|
|
402
|
-
};
|
|
401
|
+
});
|
|
403
402
|
};
|