@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/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(this.state, this.speaker);
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 (request?: StartRecordingRequest) => {
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}/start_recording`, request ? request : {});
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}/stop_recording`,
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
- // @ts-expect-error partial data
268
- audio: {
269
- noise_cancellation: {
270
- mode: NoiseCancellationSettingsModeEnum.DISABLED,
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
- settings: {
283
- // @ts-expect-error - partial data
284
- audio: {
285
- noise_cancellation: {
286
- mode: NoiseCancellationSettingsModeEnum.AUTO_ON,
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
- // @ts-expect-error partial data
371
- settings: { audio: { hifi_audio_enabled: true } },
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
- // @ts-expect-error partial data
393
- settings: { audio: { hifi_audio_enabled: false } },
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
- settings: {
83
- // @ts-expect-error partial data
84
- audio: {
85
- noise_cancellation: {
86
- mode: NoiseCancellationSettingsModeEnum.AVAILABLE,
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
- // @ts-expect-error partial data
90
- screensharing: {
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
  };