@stream-io/video-client 1.33.1 → 1.34.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 +19 -0
- package/dist/index.browser.es.js +15 -8
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +15 -8
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +15 -8
- package/dist/index.es.js.map +1 -1
- package/package.json +20 -21
- package/src/Call.ts +1 -0
- package/src/devices/CameraManager.ts +3 -2
- package/src/devices/SpeakerManager.ts +9 -6
- package/src/devices/__tests__/SpeakerManager.test.ts +8 -5
- package/src/rtc/__tests__/layers.test.ts +99 -74
- package/src/store/__tests__/CallState.test.ts +7 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.1",
|
|
4
4
|
"main": "dist/index.cjs.js",
|
|
5
5
|
"module": "dist/index.es.js",
|
|
6
6
|
"browser": "dist/index.browser.es.js",
|
|
@@ -26,34 +26,33 @@
|
|
|
26
26
|
"CHANGELOG.md"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@protobuf-ts/runtime": "^2.
|
|
30
|
-
"@protobuf-ts/runtime-rpc": "^2.
|
|
31
|
-
"@protobuf-ts/twirp-transport": "^2.
|
|
29
|
+
"@protobuf-ts/runtime": "^2.11.1",
|
|
30
|
+
"@protobuf-ts/runtime-rpc": "^2.11.1",
|
|
31
|
+
"@protobuf-ts/twirp-transport": "^2.11.1",
|
|
32
32
|
"@stream-io/worker-timer": "^1.2.4",
|
|
33
|
-
"axios": "^1.
|
|
34
|
-
"rxjs": "~7.8.
|
|
33
|
+
"axios": "^1.12.2",
|
|
34
|
+
"rxjs": "~7.8.2",
|
|
35
35
|
"sdp-transform": "^2.15.0",
|
|
36
|
-
"ua-parser-js": "^1.0.
|
|
37
|
-
"webrtc-adapter": "^8.2.
|
|
36
|
+
"ua-parser-js": "^1.0.41",
|
|
37
|
+
"webrtc-adapter": "^8.2.4"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@openapitools/openapi-generator-cli": "^2.
|
|
40
|
+
"@openapitools/openapi-generator-cli": "^2.25.0",
|
|
41
41
|
"@rollup/plugin-replace": "^6.0.2",
|
|
42
|
-
"@rollup/plugin-typescript": "^12.1.
|
|
43
|
-
"@stream-io/audio-filters-web": "^0.
|
|
44
|
-
"@stream-io/node-sdk": "^0.
|
|
42
|
+
"@rollup/plugin-typescript": "^12.1.4",
|
|
43
|
+
"@stream-io/audio-filters-web": "^0.6.0",
|
|
44
|
+
"@stream-io/node-sdk": "^0.7.9",
|
|
45
45
|
"@total-typescript/shoehorn": "^0.1.2",
|
|
46
|
-
"@types/sdp-transform": "^2.
|
|
46
|
+
"@types/sdp-transform": "^2.15.0",
|
|
47
47
|
"@types/ua-parser-js": "^0.7.39",
|
|
48
|
-
"@vitest/coverage-v8": "^3.
|
|
49
|
-
"dotenv": "^16.
|
|
50
|
-
"happy-dom": "^
|
|
51
|
-
"prettier": "^3.
|
|
48
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
49
|
+
"dotenv": "^16.6.1",
|
|
50
|
+
"happy-dom": "^20.0.0",
|
|
51
|
+
"prettier": "^3.6.2",
|
|
52
52
|
"rimraf": "^6.0.1",
|
|
53
|
-
"rollup": "^4.
|
|
54
|
-
"typescript": "^5.
|
|
55
|
-
"
|
|
56
|
-
"vitest": "^3.1.3",
|
|
53
|
+
"rollup": "^4.52.4",
|
|
54
|
+
"typescript": "^5.9.3",
|
|
55
|
+
"vitest": "^3.2.4",
|
|
57
56
|
"vitest-mock-extended": "^3.1.0"
|
|
58
57
|
}
|
|
59
58
|
}
|
package/src/Call.ts
CHANGED
|
@@ -1288,6 +1288,7 @@ export class Call {
|
|
|
1288
1288
|
}
|
|
1289
1289
|
|
|
1290
1290
|
this.tracer.setEnabled(enableTracing);
|
|
1291
|
+
this.sfuStatsReporter?.flush();
|
|
1291
1292
|
this.sfuStatsReporter?.stop();
|
|
1292
1293
|
if (statsOptions?.reporting_interval_ms > 0) {
|
|
1293
1294
|
this.sfuStatsReporter = new SfuStatsReporter(sfuClient, {
|
|
@@ -43,12 +43,13 @@ export class CameraManager extends DeviceManager<CameraManagerState> {
|
|
|
43
43
|
await videoTrack?.applyConstraints({
|
|
44
44
|
facingMode: direction === 'front' ? 'user' : 'environment',
|
|
45
45
|
});
|
|
46
|
-
this.state.setDirection(direction);
|
|
47
|
-
return;
|
|
48
46
|
}
|
|
49
47
|
// providing both device id and direction doesn't work, so we deselect the device
|
|
50
48
|
this.state.setDirection(direction);
|
|
51
49
|
this.state.setDevice(undefined);
|
|
50
|
+
if (isReactNative()) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
52
53
|
this.getTracks().forEach((track) => track.stop());
|
|
53
54
|
try {
|
|
54
55
|
await this.unmuteStream();
|
|
@@ -113,14 +113,17 @@ export class SpeakerManager {
|
|
|
113
113
|
* @param volume a number between 0 and 1. Set it to `undefined` to use the default volume.
|
|
114
114
|
*/
|
|
115
115
|
setParticipantVolume(sessionId: string, volume: number | undefined) {
|
|
116
|
-
if (isReactNative()) {
|
|
117
|
-
throw new Error(
|
|
118
|
-
'This feature is not supported in React Native. Please visit https://getstream.io/video/docs/reactnative/core/camera-and-microphone/#speaker-management for more details',
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
116
|
if (volume && (volume < 0 || volume > 1)) {
|
|
122
117
|
throw new Error('Volume must be between 0 and 1, or undefined');
|
|
123
118
|
}
|
|
124
|
-
this.call.state.updateParticipant(sessionId,
|
|
119
|
+
this.call.state.updateParticipant(sessionId, (p) => {
|
|
120
|
+
if (isReactNative() && p.audioStream) {
|
|
121
|
+
for (const track of p.audioStream.getAudioTracks()) {
|
|
122
|
+
// @ts-expect-error track._setVolume is present in react-native-webrtc
|
|
123
|
+
track?._setVolume(volume);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { audioVolume: volume };
|
|
127
|
+
});
|
|
125
128
|
}
|
|
126
129
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { fromPartial } from '@total-typescript/shoehorn';
|
|
2
3
|
import {
|
|
3
4
|
emitDeviceIds,
|
|
4
5
|
mockAudioDevices,
|
|
@@ -80,11 +81,13 @@ describe('SpeakerManager.test', () => {
|
|
|
80
81
|
|
|
81
82
|
it('set participant volume', () => {
|
|
82
83
|
const call = manager['call'];
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
call.state.updateOrAddParticipant(
|
|
85
|
+
'session-id',
|
|
86
|
+
fromPartial({
|
|
87
|
+
audioVolume: undefined,
|
|
88
|
+
sessionId: 'session-id',
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
88
91
|
|
|
89
92
|
manager.setParticipantVolume('session-id', 0.5);
|
|
90
93
|
let participant = call.state.findParticipantBySessionId('session-id');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import './mocks/webrtc.mocks';
|
|
2
2
|
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { fromPartial } from '@total-typescript/shoehorn';
|
|
3
4
|
import {
|
|
4
5
|
AudioBitrateProfile,
|
|
5
6
|
PublishOption,
|
|
@@ -24,13 +25,12 @@ describe('videoLayers', () => {
|
|
|
24
25
|
const targetBitrate = 3000000;
|
|
25
26
|
vi.spyOn(track, 'getSettings').mockReturnValue({ width, height });
|
|
26
27
|
|
|
27
|
-
const publishOption: PublishOption = {
|
|
28
|
+
const publishOption: PublishOption = fromPartial({
|
|
28
29
|
bitrate: targetBitrate,
|
|
29
|
-
// @ts-expect-error - incomplete data
|
|
30
30
|
codec: { name: 'vp8' },
|
|
31
31
|
videoDimension: { width, height },
|
|
32
32
|
fps: 30,
|
|
33
|
-
};
|
|
33
|
+
});
|
|
34
34
|
const layers = computeVideoLayers(track, publishOption);
|
|
35
35
|
expect(layers).toEqual([
|
|
36
36
|
{
|
|
@@ -66,12 +66,13 @@ describe('videoLayers', () => {
|
|
|
66
66
|
it('should return undefined for audio track', () => {
|
|
67
67
|
const track = new MediaStreamTrack();
|
|
68
68
|
expect(
|
|
69
|
-
|
|
70
|
-
computeVideoLayers(track, { trackType: TrackType.AUDIO }),
|
|
69
|
+
computeVideoLayers(track, fromPartial({ trackType: TrackType.AUDIO })),
|
|
71
70
|
).toBeUndefined();
|
|
72
71
|
expect(
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
computeVideoLayers(
|
|
73
|
+
track,
|
|
74
|
+
fromPartial({ trackType: TrackType.SCREEN_SHARE_AUDIO }),
|
|
75
|
+
),
|
|
75
76
|
).toBeUndefined();
|
|
76
77
|
});
|
|
77
78
|
|
|
@@ -79,13 +80,15 @@ describe('videoLayers', () => {
|
|
|
79
80
|
const bitrate = 3000000;
|
|
80
81
|
const track = new MediaStreamTrack();
|
|
81
82
|
vi.spyOn(track, 'getSettings').mockReturnValue({});
|
|
82
|
-
const layers = computeVideoLayers(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
const layers = computeVideoLayers(
|
|
84
|
+
track,
|
|
85
|
+
fromPartial({
|
|
86
|
+
bitrate,
|
|
87
|
+
codec: { name: 'vp8' },
|
|
88
|
+
fps: 30,
|
|
89
|
+
videoDimension: { width: 320, height: 180 },
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
89
92
|
expect(layers).toEqual([
|
|
90
93
|
{
|
|
91
94
|
active: true,
|
|
@@ -104,13 +107,15 @@ describe('videoLayers', () => {
|
|
|
104
107
|
const width = 320;
|
|
105
108
|
const height = 240;
|
|
106
109
|
vi.spyOn(track, 'getSettings').mockReturnValue({ width, height });
|
|
107
|
-
const layers = computeVideoLayers(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
const layers = computeVideoLayers(
|
|
111
|
+
track,
|
|
112
|
+
fromPartial({
|
|
113
|
+
bitrate: 0,
|
|
114
|
+
codec: { name: 'vp8' },
|
|
115
|
+
fps: 30,
|
|
116
|
+
videoDimension: { width, height },
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
114
119
|
expect(layers.length).toBe(1);
|
|
115
120
|
const [q] = layers;
|
|
116
121
|
expect(q.rid).toBe('q');
|
|
@@ -123,13 +128,15 @@ describe('videoLayers', () => {
|
|
|
123
128
|
const width = 640;
|
|
124
129
|
const height = 480;
|
|
125
130
|
vi.spyOn(track, 'getSettings').mockReturnValue({ width, height });
|
|
126
|
-
const layers = computeVideoLayers(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
const layers = computeVideoLayers(
|
|
132
|
+
track,
|
|
133
|
+
fromPartial({
|
|
134
|
+
bitrate: 0,
|
|
135
|
+
codec: { name: 'vp8' },
|
|
136
|
+
fps: 30,
|
|
137
|
+
videoDimension: { width, height },
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
133
140
|
expect(layers.length).toBe(2);
|
|
134
141
|
const [q, h] = layers;
|
|
135
142
|
expect(q.rid).toBe('q');
|
|
@@ -145,13 +152,15 @@ describe('videoLayers', () => {
|
|
|
145
152
|
const width = 1280;
|
|
146
153
|
const height = 720;
|
|
147
154
|
vi.spyOn(track, 'getSettings').mockReturnValue({ width, height });
|
|
148
|
-
const layers = computeVideoLayers(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
const layers = computeVideoLayers(
|
|
156
|
+
track,
|
|
157
|
+
fromPartial({
|
|
158
|
+
bitrate: 0,
|
|
159
|
+
codec: { name: 'vp8' },
|
|
160
|
+
fps: 30,
|
|
161
|
+
videoDimension: { width, height },
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
155
164
|
expect(layers.length).toBe(3);
|
|
156
165
|
const [q, h, f] = layers;
|
|
157
166
|
expect(q.rid).toBe('q');
|
|
@@ -171,13 +180,15 @@ describe('videoLayers', () => {
|
|
|
171
180
|
width: 1280,
|
|
172
181
|
height: 720,
|
|
173
182
|
});
|
|
174
|
-
const layers = computeVideoLayers(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
183
|
+
const layers = computeVideoLayers(
|
|
184
|
+
track,
|
|
185
|
+
fromPartial({
|
|
186
|
+
maxTemporalLayers: 3,
|
|
187
|
+
maxSpatialLayers: 3,
|
|
188
|
+
codec: { name: 'vp9' },
|
|
189
|
+
videoDimension: { width: 1280, height: 720 },
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
181
192
|
expect(layers.length).toBe(3);
|
|
182
193
|
expect(layers[0].scalabilityMode).toBe('L3T3_KEY');
|
|
183
194
|
expect(layers[0].rid).toBe('q');
|
|
@@ -187,8 +198,10 @@ describe('videoLayers', () => {
|
|
|
187
198
|
|
|
188
199
|
it('should activate only a single layer when useSingleLayer is true', () => {
|
|
189
200
|
const track = new MediaStreamTrack();
|
|
190
|
-
|
|
191
|
-
|
|
201
|
+
const layers = computeVideoLayers(
|
|
202
|
+
track,
|
|
203
|
+
fromPartial({ useSingleLayer: true }),
|
|
204
|
+
);
|
|
192
205
|
expect(layers.length).toBe(3);
|
|
193
206
|
expect(layers[0].active).toBe(false);
|
|
194
207
|
expect(layers[0].rid).toBe('q');
|
|
@@ -201,8 +214,10 @@ describe('videoLayers', () => {
|
|
|
201
214
|
it('should activate only a single layer when useSingleLayer is true in single layer mode', () => {
|
|
202
215
|
const track = new MediaStreamTrack();
|
|
203
216
|
vi.spyOn(track, 'getSettings').mockReturnValue({ width: 320, height: 180 });
|
|
204
|
-
|
|
205
|
-
|
|
217
|
+
const layers = computeVideoLayers(
|
|
218
|
+
track,
|
|
219
|
+
fromPartial({ useSingleLayer: true }),
|
|
220
|
+
);
|
|
206
221
|
expect(layers.length).toBe(1);
|
|
207
222
|
expect(layers[0].active).toBe(true);
|
|
208
223
|
expect(layers[0].rid).toBe('q');
|
|
@@ -210,13 +225,15 @@ describe('videoLayers', () => {
|
|
|
210
225
|
|
|
211
226
|
it('should activate only one temporal layer when useSingleLayer is true for SVC', () => {
|
|
212
227
|
const track = new MediaStreamTrack();
|
|
213
|
-
const layers = computeVideoLayers(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
228
|
+
const layers = computeVideoLayers(
|
|
229
|
+
track,
|
|
230
|
+
fromPartial({
|
|
231
|
+
codec: { name: 'vp9' },
|
|
232
|
+
maxSpatialLayers: 3,
|
|
233
|
+
maxTemporalLayers: 3,
|
|
234
|
+
useSingleLayer: true,
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
220
237
|
expect(layers.length).toBe(3);
|
|
221
238
|
expect(layers[0].rid).toBe('q');
|
|
222
239
|
expect(layers[0].active).toBe(false);
|
|
@@ -318,13 +335,15 @@ describe('videoLayers', () => {
|
|
|
318
335
|
|
|
319
336
|
it('should use integer for maxBitrate', () => {
|
|
320
337
|
const track = new MediaStreamTrack();
|
|
321
|
-
const layers = computeVideoLayers(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
338
|
+
const layers = computeVideoLayers(
|
|
339
|
+
track,
|
|
340
|
+
fromPartial({
|
|
341
|
+
bitrate: 2999777,
|
|
342
|
+
codec: { name: 'vp8' },
|
|
343
|
+
videoDimension: { width: 1920, height: 1080 },
|
|
344
|
+
fps: 30,
|
|
345
|
+
}),
|
|
346
|
+
);
|
|
328
347
|
expect(layers).toBeDefined();
|
|
329
348
|
for (const layer of layers!) {
|
|
330
349
|
expect(Number.isInteger(layer.width)).toBe(true);
|
|
@@ -422,22 +441,31 @@ describe('audioLayers', () => {
|
|
|
422
441
|
it('should use predefined bitrates when publish options are missing', () => {
|
|
423
442
|
expect(
|
|
424
443
|
computeAudioLayers(
|
|
425
|
-
|
|
426
|
-
|
|
444
|
+
fromPartial({
|
|
445
|
+
trackType: TrackType.AUDIO,
|
|
446
|
+
id: 1,
|
|
447
|
+
audioBitrateProfiles: [],
|
|
448
|
+
}),
|
|
427
449
|
{ audioBitrateProfile: AudioBitrateProfile.VOICE_STANDARD_UNSPECIFIED },
|
|
428
450
|
),
|
|
429
451
|
).toEqual([{ maxBitrate: 64000 }]);
|
|
430
452
|
expect(
|
|
431
453
|
computeAudioLayers(
|
|
432
|
-
|
|
433
|
-
|
|
454
|
+
fromPartial({
|
|
455
|
+
trackType: TrackType.AUDIO,
|
|
456
|
+
id: 1,
|
|
457
|
+
audioBitrateProfiles: [],
|
|
458
|
+
}),
|
|
434
459
|
{ audioBitrateProfile: AudioBitrateProfile.VOICE_HIGH_QUALITY },
|
|
435
460
|
),
|
|
436
461
|
).toEqual([{ maxBitrate: 128000 }]);
|
|
437
462
|
expect(
|
|
438
463
|
computeAudioLayers(
|
|
439
|
-
|
|
440
|
-
|
|
464
|
+
fromPartial({
|
|
465
|
+
trackType: TrackType.AUDIO,
|
|
466
|
+
id: 1,
|
|
467
|
+
audioBitrateProfiles: [],
|
|
468
|
+
}),
|
|
441
469
|
{ audioBitrateProfile: AudioBitrateProfile.MUSIC_HIGH_QUALITY },
|
|
442
470
|
),
|
|
443
471
|
).toEqual([{ maxBitrate: 128000 }]);
|
|
@@ -445,17 +473,14 @@ describe('audioLayers', () => {
|
|
|
445
473
|
|
|
446
474
|
it('should use the predefined bitrate when the SFU does not provide audioBitrateProfiles', () => {
|
|
447
475
|
expect(
|
|
448
|
-
computeAudioLayers(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
{ audioBitrateProfile: AudioBitrateProfile.VOICE_STANDARD_UNSPECIFIED },
|
|
452
|
-
),
|
|
476
|
+
computeAudioLayers(fromPartial({ trackType: TrackType.AUDIO, id: 1 }), {
|
|
477
|
+
audioBitrateProfile: AudioBitrateProfile.VOICE_STANDARD_UNSPECIFIED,
|
|
478
|
+
}),
|
|
453
479
|
).toEqual([{ maxBitrate: 64000 }]);
|
|
454
480
|
});
|
|
455
481
|
|
|
456
482
|
it('should respect the bitrates provided in publish options', () => {
|
|
457
|
-
|
|
458
|
-
const config: PublishOption = {
|
|
483
|
+
const config: PublishOption = fromPartial({
|
|
459
484
|
trackType: TrackType.AUDIO,
|
|
460
485
|
id: 1,
|
|
461
486
|
audioBitrateProfiles: [
|
|
@@ -472,7 +497,7 @@ describe('audioLayers', () => {
|
|
|
472
497
|
bitrate: 192000,
|
|
473
498
|
},
|
|
474
499
|
],
|
|
475
|
-
};
|
|
500
|
+
});
|
|
476
501
|
expect(
|
|
477
502
|
computeAudioLayers(config, {
|
|
478
503
|
audioBitrateProfile: AudioBitrateProfile.VOICE_STANDARD_UNSPECIFIED,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import '../../rtc/__tests__/mocks/webrtc.mocks';
|
|
2
2
|
import { describe, expect, it, vi } from 'vitest';
|
|
3
3
|
import { anyNumber } from 'vitest-mock-extended';
|
|
4
|
+
import { fromPartial } from '@total-typescript/shoehorn';
|
|
4
5
|
import { StreamVideoParticipant, VisibilityState } from '../../types';
|
|
5
6
|
import { CallingState } from '../CallingState';
|
|
6
7
|
import { CallState } from '../CallState';
|
|
@@ -56,8 +57,7 @@ describe('CallState', () => {
|
|
|
56
57
|
it(`shouldn't emit when primitive (backstage) values didn't change`, () => {
|
|
57
58
|
const state = new CallState();
|
|
58
59
|
const updateWith = (value: boolean) => {
|
|
59
|
-
|
|
60
|
-
state.updateFromCallResponse({ backstage: value });
|
|
60
|
+
state.updateFromCallResponse(fromPartial({ backstage: value }));
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
updateWith(false);
|
|
@@ -138,8 +138,7 @@ describe('CallState', () => {
|
|
|
138
138
|
it(`shouldn't emit when string arrays (blockedUserIds) value didn't change`, () => {
|
|
139
139
|
const state = new CallState();
|
|
140
140
|
const updateWith = (value: string[]) => {
|
|
141
|
-
|
|
142
|
-
state.updateFromCallResponse({ blocked_user_ids: value });
|
|
141
|
+
state.updateFromCallResponse(fromPartial({ blocked_user_ids: value }));
|
|
143
142
|
};
|
|
144
143
|
|
|
145
144
|
updateWith(['a', 'b']);
|
|
@@ -640,25 +639,21 @@ describe('CallState', () => {
|
|
|
640
639
|
describe('recording and broadcasting events', () => {
|
|
641
640
|
it('handles call.recording_started events', () => {
|
|
642
641
|
const state = new CallState();
|
|
643
|
-
|
|
644
|
-
state.updateFromEvent({ type: 'call.recording_started' });
|
|
642
|
+
state.updateFromEvent(fromPartial({ type: 'call.recording_started' }));
|
|
645
643
|
expect(state.recording).toBe(true);
|
|
646
644
|
});
|
|
647
645
|
|
|
648
646
|
it('handles call.recording_stopped events', () => {
|
|
649
647
|
const state = new CallState();
|
|
650
|
-
|
|
651
|
-
state.updateFromEvent({ type: 'call.recording_stopped' });
|
|
648
|
+
state.updateFromEvent(fromPartial({ type: 'call.recording_stopped' }));
|
|
652
649
|
expect(state.recording).toBe(false);
|
|
653
650
|
});
|
|
654
651
|
|
|
655
652
|
it('handles call.recording_failed events', () => {
|
|
656
653
|
const state = new CallState();
|
|
657
|
-
|
|
658
|
-
state.updateFromEvent({ type: 'call.recording_started' });
|
|
654
|
+
state.updateFromEvent(fromPartial({ type: 'call.recording_started' }));
|
|
659
655
|
expect(state.recording).toBe(true);
|
|
660
|
-
|
|
661
|
-
state.updateFromEvent({ type: 'call.recording_failed' });
|
|
656
|
+
state.updateFromEvent(fromPartial({ type: 'call.recording_failed' }));
|
|
662
657
|
expect(state.recording).toBe(false);
|
|
663
658
|
});
|
|
664
659
|
|