@stream-io/video-client 1.32.0 → 1.33.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 +16 -0
- package/dist/index.browser.es.js +344 -91
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +345 -92
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +344 -91
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +3 -2
- package/dist/src/devices/AudioDeviceManager.d.ts +25 -0
- package/dist/src/devices/AudioDeviceManagerState.d.ts +24 -0
- package/dist/src/devices/CameraManager.d.ts +2 -2
- package/dist/src/devices/CameraManagerState.d.ts +3 -4
- package/dist/src/devices/{InputMediaDeviceManager.d.ts → DeviceManager.d.ts} +6 -6
- package/dist/src/devices/{InputMediaDeviceManagerState.d.ts → DeviceManagerState.d.ts} +4 -4
- package/dist/src/devices/MicrophoneManager.d.ts +5 -3
- package/dist/src/devices/MicrophoneManagerState.d.ts +6 -10
- package/dist/src/devices/ScreenShareManager.d.ts +4 -2
- package/dist/src/devices/ScreenShareState.d.ts +6 -2
- package/dist/src/devices/SpeakerState.d.ts +4 -4
- package/dist/src/devices/index.d.ts +2 -2
- package/dist/src/gen/coordinator/index.d.ts +169 -2
- package/dist/src/gen/video/sfu/models/models.d.ts +43 -0
- package/dist/src/rtc/BasePeerConnection.d.ts +2 -12
- package/dist/src/rtc/Publisher.d.ts +9 -6
- package/dist/src/rtc/Subscriber.d.ts +2 -1
- package/dist/src/rtc/TransceiverCache.d.ts +10 -11
- package/dist/src/rtc/index.d.ts +1 -1
- package/dist/src/rtc/{videoLayers.d.ts → layers.d.ts} +7 -1
- package/dist/src/rtc/types.d.ts +31 -0
- package/dist/src/sorting/participants.d.ts +5 -2
- package/package.json +3 -2
- package/src/Call.ts +13 -6
- package/src/__tests__/Call.publishing.test.ts +14 -3
- package/src/__tests__/StreamVideoClient.api.test.ts +1 -1
- package/src/devices/AudioDeviceManager.ts +61 -0
- package/src/devices/AudioDeviceManagerState.ts +44 -0
- package/src/devices/CameraManager.ts +4 -4
- package/src/devices/CameraManagerState.ts +9 -8
- package/src/devices/{InputMediaDeviceManager.ts → DeviceManager.ts} +11 -8
- package/src/devices/{InputMediaDeviceManagerState.ts → DeviceManagerState.ts} +7 -4
- package/src/devices/MicrophoneManager.ts +26 -6
- package/src/devices/MicrophoneManagerState.ts +18 -19
- package/src/devices/ScreenShareManager.ts +23 -4
- package/src/devices/ScreenShareState.ts +11 -3
- package/src/devices/SpeakerState.ts +6 -14
- package/src/devices/__tests__/CameraManager.test.ts +1 -0
- package/src/devices/__tests__/{InputMediaDeviceManager.test.ts → DeviceManager.test.ts} +4 -4
- package/src/devices/__tests__/{InputMediaDeviceManagerFilters.test.ts → DeviceManagerFilters.test.ts} +4 -4
- package/src/devices/__tests__/{InputMediaDeviceManagerState.test.ts → DeviceManagerState.test.ts} +2 -2
- package/src/devices/__tests__/MicrophoneManager.test.ts +41 -1
- package/src/devices/__tests__/NoiseCancellationStub.ts +3 -1
- package/src/devices/__tests__/ScreenShareManager.test.ts +5 -1
- package/src/devices/index.ts +2 -2
- package/src/events/__tests__/internal.test.ts +25 -11
- package/src/gen/coordinator/index.ts +169 -2
- package/src/gen/video/sfu/models/models.ts +65 -0
- package/src/rtc/BasePeerConnection.ts +1 -16
- package/src/rtc/Publisher.ts +74 -31
- package/src/rtc/Subscriber.ts +2 -4
- package/src/rtc/TransceiverCache.ts +23 -27
- package/src/rtc/__tests__/Publisher.test.ts +61 -29
- package/src/rtc/__tests__/{videoLayers.test.ts → layers.test.ts} +76 -1
- package/src/rtc/index.ts +2 -1
- package/src/rtc/{videoLayers.ts → layers.ts} +28 -7
- package/src/rtc/types.ts +44 -0
- package/src/sorting/__tests__/sorting.test.ts +106 -0
- package/src/sorting/participants.ts +30 -14
- package/src/sorting/presets.ts +16 -4
- package/src/store/CallState.ts +36 -10
- package/src/store/__tests__/CallState.test.ts +20 -2
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { combineLatest, Observable } from 'rxjs';
|
|
2
2
|
import type { INoiseCancellation } from '@stream-io/audio-filters-web';
|
|
3
3
|
import { Call } from '../Call';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
AudioDeviceManager,
|
|
6
|
+
createAudioConstraints,
|
|
7
|
+
} from './AudioDeviceManager';
|
|
5
8
|
import { MicrophoneManagerState } from './MicrophoneManagerState';
|
|
6
|
-
import { TrackDisableMode } from './
|
|
9
|
+
import { TrackDisableMode } from './DeviceManagerState';
|
|
7
10
|
import { getAudioDevices, getAudioStream } from './devices';
|
|
8
|
-
import { TrackType } from '../gen/video/sfu/models/models';
|
|
11
|
+
import { AudioBitrateProfile, TrackType } from '../gen/video/sfu/models/models';
|
|
9
12
|
import { createSoundDetector } from '../helpers/sound-detector';
|
|
10
13
|
import { isReactNative } from '../helpers/platforms';
|
|
11
14
|
import {
|
|
@@ -21,7 +24,7 @@ import {
|
|
|
21
24
|
import { RNSpeechDetector } from '../helpers/RNSpeechDetector';
|
|
22
25
|
import { withoutConcurrency } from '../helpers/concurrency';
|
|
23
26
|
|
|
24
|
-
export class MicrophoneManager extends
|
|
27
|
+
export class MicrophoneManager extends AudioDeviceManager<MicrophoneManagerState> {
|
|
25
28
|
private speakingWhileMutedNotificationEnabled = true;
|
|
26
29
|
private soundDetectorConcurrencyTag = Symbol('soundDetectorConcurrencyTag');
|
|
27
30
|
private soundDetectorCleanup?: Function;
|
|
@@ -245,16 +248,33 @@ export class MicrophoneManager extends InputMediaDeviceManager<MicrophoneManager
|
|
|
245
248
|
}
|
|
246
249
|
}
|
|
247
250
|
|
|
248
|
-
protected getDevices(): Observable<MediaDeviceInfo[]> {
|
|
251
|
+
protected override getDevices(): Observable<MediaDeviceInfo[]> {
|
|
249
252
|
return getAudioDevices(this.call.tracer);
|
|
250
253
|
}
|
|
251
254
|
|
|
252
|
-
protected getStream(
|
|
255
|
+
protected override getStream(
|
|
253
256
|
constraints: MediaTrackConstraints,
|
|
254
257
|
): Promise<MediaStream> {
|
|
255
258
|
return getAudioStream(constraints, this.call.tracer);
|
|
256
259
|
}
|
|
257
260
|
|
|
261
|
+
protected override doSetAudioBitrateProfile(profile: AudioBitrateProfile) {
|
|
262
|
+
this.setDefaultConstraints({
|
|
263
|
+
...this.state.defaultConstraints,
|
|
264
|
+
...createAudioConstraints(profile),
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
if (this.noiseCancellation) {
|
|
268
|
+
const disableAudioProcessing =
|
|
269
|
+
profile === AudioBitrateProfile.MUSIC_HIGH_QUALITY;
|
|
270
|
+
if (disableAudioProcessing) {
|
|
271
|
+
this.noiseCancellation.disable(); // disable for high quality music mode
|
|
272
|
+
} else {
|
|
273
|
+
this.noiseCancellation.enable(); // restore it for other modes if available
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
258
278
|
private async startSpeakingWhileMutedDetection(deviceId?: string) {
|
|
259
279
|
await withoutConcurrency(this.soundDetectorConcurrencyTag, async () => {
|
|
260
280
|
await this.stopSpeakingWhileMutedDetection();
|
|
@@ -1,33 +1,30 @@
|
|
|
1
|
-
import { BehaviorSubject, distinctUntilChanged
|
|
1
|
+
import { BehaviorSubject, distinctUntilChanged } from 'rxjs';
|
|
2
2
|
import { RxUtils } from '../store';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
TrackDisableMode,
|
|
6
|
-
} from './InputMediaDeviceManagerState';
|
|
3
|
+
import { TrackDisableMode } from './DeviceManagerState';
|
|
4
|
+
import { AudioDeviceManagerState } from './AudioDeviceManagerState';
|
|
7
5
|
import { getAudioBrowserPermission, resolveDeviceId } from './devices';
|
|
6
|
+
import { AudioBitrateProfile } from '../gen/video/sfu/models/models';
|
|
8
7
|
|
|
9
|
-
export class MicrophoneManagerState extends
|
|
8
|
+
export class MicrophoneManagerState extends AudioDeviceManagerState<MediaTrackConstraints> {
|
|
10
9
|
private speakingWhileMutedSubject = new BehaviorSubject<boolean>(false);
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
|
-
* An Observable that emits `true` if the user's microphone is muted but they'
|
|
14
|
-
*
|
|
15
|
-
* This feature is not available in the React Native SDK.
|
|
12
|
+
* An Observable that emits `true` if the user's microphone is muted, but they're speaking.
|
|
16
13
|
*/
|
|
17
|
-
speakingWhileMuted
|
|
14
|
+
speakingWhileMuted$ = this.speakingWhileMutedSubject
|
|
15
|
+
.asObservable()
|
|
16
|
+
.pipe(distinctUntilChanged());
|
|
18
17
|
|
|
19
18
|
constructor(disableMode: TrackDisableMode) {
|
|
20
|
-
super(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
.
|
|
24
|
-
|
|
19
|
+
super(
|
|
20
|
+
disableMode,
|
|
21
|
+
getAudioBrowserPermission(),
|
|
22
|
+
AudioBitrateProfile.VOICE_STANDARD_UNSPECIFIED,
|
|
23
|
+
);
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
/**
|
|
28
|
-
* `true` if the user's microphone is muted but they'
|
|
29
|
-
*
|
|
30
|
-
* This feature is not available in the React Native SDK.
|
|
27
|
+
* `true` if the user's microphone is muted but they're speaking.
|
|
31
28
|
*/
|
|
32
29
|
get speakingWhileMuted() {
|
|
33
30
|
return RxUtils.getCurrentValue(this.speakingWhileMuted$);
|
|
@@ -40,7 +37,9 @@ export class MicrophoneManagerState extends InputMediaDeviceManagerState {
|
|
|
40
37
|
RxUtils.setCurrentValue(this.speakingWhileMutedSubject, isSpeaking);
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
protected getDeviceIdFromStream(
|
|
40
|
+
protected override getDeviceIdFromStream(
|
|
41
|
+
stream: MediaStream,
|
|
42
|
+
): string | undefined {
|
|
44
43
|
const [track] = stream.getAudioTracks();
|
|
45
44
|
const unresolvedDeviceId = track?.getSettings().deviceId;
|
|
46
45
|
return resolveDeviceId(unresolvedDeviceId, 'audioinput');
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { Observable, of } from 'rxjs';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AudioDeviceManager,
|
|
4
|
+
createAudioConstraints,
|
|
5
|
+
} from './AudioDeviceManager';
|
|
3
6
|
import { ScreenShareState } from './ScreenShareState';
|
|
4
7
|
import { Call } from '../Call';
|
|
5
|
-
import { TrackType } from '../gen/video/sfu/models/models';
|
|
8
|
+
import { AudioBitrateProfile, TrackType } from '../gen/video/sfu/models/models';
|
|
6
9
|
import { getScreenShareStream } from './devices';
|
|
7
10
|
import { ScreenShareSettings } from '../types';
|
|
8
11
|
import { createSubscription } from '../store/rxUtils';
|
|
9
12
|
|
|
10
|
-
export class ScreenShareManager extends
|
|
13
|
+
export class ScreenShareManager extends AudioDeviceManager<
|
|
11
14
|
ScreenShareState,
|
|
12
15
|
DisplayMediaStreamOptions
|
|
13
16
|
> {
|
|
@@ -23,6 +26,7 @@ export class ScreenShareManager extends InputMediaDeviceManager<
|
|
|
23
26
|
|
|
24
27
|
if (maybeTargetResolution) {
|
|
25
28
|
this.setDefaultConstraints({
|
|
29
|
+
...this.state.defaultConstraints,
|
|
26
30
|
video: {
|
|
27
31
|
width: maybeTargetResolution.width,
|
|
28
32
|
height: maybeTargetResolution.height,
|
|
@@ -73,7 +77,7 @@ export class ScreenShareManager extends InputMediaDeviceManager<
|
|
|
73
77
|
return of([]); // there are no devices to be listed for Screen Share
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
protected async getStream(
|
|
80
|
+
protected override async getStream(
|
|
77
81
|
constraints: DisplayMediaStreamOptions,
|
|
78
82
|
): Promise<MediaStream> {
|
|
79
83
|
if (!this.state.audioEnabled) {
|
|
@@ -92,6 +96,21 @@ export class ScreenShareManager extends InputMediaDeviceManager<
|
|
|
92
96
|
return stream;
|
|
93
97
|
}
|
|
94
98
|
|
|
99
|
+
protected override doSetAudioBitrateProfile(profile: AudioBitrateProfile) {
|
|
100
|
+
const { defaultConstraints } = this.state;
|
|
101
|
+
const baseAudioConstraints =
|
|
102
|
+
typeof defaultConstraints?.audio !== 'boolean'
|
|
103
|
+
? defaultConstraints?.audio
|
|
104
|
+
: null;
|
|
105
|
+
this.setDefaultConstraints({
|
|
106
|
+
...defaultConstraints,
|
|
107
|
+
audio: {
|
|
108
|
+
...baseAudioConstraints,
|
|
109
|
+
...createAudioConstraints(profile),
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
95
114
|
protected override async stopPublishStream(): Promise<void> {
|
|
96
115
|
return this.call.stopPublish(
|
|
97
116
|
TrackType.SCREEN_SHARE,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { BehaviorSubject, distinctUntilChanged } from 'rxjs';
|
|
2
|
-
import {
|
|
2
|
+
import { AudioDeviceManagerState } from './AudioDeviceManagerState';
|
|
3
|
+
import { AudioBitrateProfile } from '../gen/video/sfu/models/models';
|
|
3
4
|
import { ScreenShareSettings } from '../types';
|
|
4
5
|
import { RxUtils } from '../store';
|
|
5
6
|
|
|
6
|
-
export class ScreenShareState extends
|
|
7
|
+
export class ScreenShareState extends AudioDeviceManagerState<DisplayMediaStreamOptions> {
|
|
7
8
|
private audioEnabledSubject = new BehaviorSubject<boolean>(true);
|
|
8
9
|
private settingsSubject = new BehaviorSubject<
|
|
9
10
|
ScreenShareSettings | undefined
|
|
@@ -21,10 +22,17 @@ export class ScreenShareState extends InputMediaDeviceManagerState<DisplayMediaS
|
|
|
21
22
|
*/
|
|
22
23
|
settings$ = this.settingsSubject.asObservable();
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Constructs a new ScreenShareState instance.
|
|
27
|
+
*/
|
|
28
|
+
constructor() {
|
|
29
|
+
super('stop-tracks', undefined, AudioBitrateProfile.MUSIC_HIGH_QUALITY);
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
/**
|
|
25
33
|
* @internal
|
|
26
34
|
*/
|
|
27
|
-
protected getDeviceIdFromStream = (
|
|
35
|
+
protected override getDeviceIdFromStream = (
|
|
28
36
|
stream: MediaStream,
|
|
29
37
|
): string | undefined => {
|
|
30
38
|
const [track] = stream.getTracks();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BehaviorSubject, distinctUntilChanged
|
|
1
|
+
import { BehaviorSubject, distinctUntilChanged } from 'rxjs';
|
|
2
2
|
import { RxUtils } from '../store';
|
|
3
3
|
import { checkIfAudioOutputChangeSupported } from './devices';
|
|
4
4
|
import { Tracer } from '../stats';
|
|
@@ -16,26 +16,18 @@ export class SpeakerState {
|
|
|
16
16
|
*
|
|
17
17
|
* Note: this feature is not supported in React Native
|
|
18
18
|
*/
|
|
19
|
-
selectedDevice
|
|
19
|
+
selectedDevice$ = this.selectedDeviceSubject
|
|
20
|
+
.asObservable()
|
|
21
|
+
.pipe(distinctUntilChanged());
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* An Observable that emits the currently selected volume
|
|
23
25
|
*
|
|
24
26
|
* Note: this feature is not supported in React Native
|
|
25
27
|
*/
|
|
26
|
-
volume
|
|
28
|
+
volume$ = this.volumeSubject.asObservable().pipe(distinctUntilChanged());
|
|
27
29
|
|
|
28
|
-
private tracer: Tracer
|
|
29
|
-
|
|
30
|
-
constructor(tracer: Tracer) {
|
|
31
|
-
this.tracer = tracer;
|
|
32
|
-
this.selectedDevice$ = this.selectedDeviceSubject
|
|
33
|
-
.asObservable()
|
|
34
|
-
.pipe(distinctUntilChanged());
|
|
35
|
-
this.volume$ = this.volumeSubject
|
|
36
|
-
.asObservable()
|
|
37
|
-
.pipe(distinctUntilChanged());
|
|
38
|
-
}
|
|
30
|
+
constructor(private tracer: Tracer) {}
|
|
39
31
|
|
|
40
32
|
/**
|
|
41
33
|
* The currently selected device
|
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
mockVideoDevices,
|
|
13
13
|
mockVideoStream,
|
|
14
14
|
} from './mocks';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { DeviceManager } from '../DeviceManager';
|
|
16
|
+
import { DeviceManagerState } from '../DeviceManagerState';
|
|
17
17
|
import { of } from 'rxjs';
|
|
18
18
|
import { TrackType } from '../../gen/video/sfu/models/models';
|
|
19
19
|
|
|
@@ -34,13 +34,13 @@ vi.mock('../devices.ts', () => {
|
|
|
34
34
|
};
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
class TestInputMediaDeviceManagerState extends
|
|
37
|
+
class TestInputMediaDeviceManagerState extends DeviceManagerState {
|
|
38
38
|
public getDeviceIdFromStream = vi.fn(
|
|
39
39
|
(stream) => stream.getVideoTracks()[0].getSettings().deviceId,
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
class TestInputMediaDeviceManager extends
|
|
43
|
+
class TestInputMediaDeviceManager extends DeviceManager<TestInputMediaDeviceManagerState> {
|
|
44
44
|
public getDevices = vi.fn(() => of(mockVideoDevices));
|
|
45
45
|
public getStream = vi.fn(() => Promise.resolve(mockVideoStream()));
|
|
46
46
|
public publishStream = vi.fn();
|
|
@@ -3,8 +3,8 @@ import { of } from 'rxjs';
|
|
|
3
3
|
import { Call } from '../../Call';
|
|
4
4
|
import { StreamClient } from '../../coordinator/connection/client';
|
|
5
5
|
import { StreamVideoWriteableStateStore } from '../../store';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { DeviceManagerState } from '../DeviceManagerState';
|
|
7
|
+
import { DeviceManager } from '../DeviceManager';
|
|
8
8
|
import {
|
|
9
9
|
mockBrowserPermission,
|
|
10
10
|
mockVideoDevices,
|
|
@@ -14,13 +14,13 @@ import { TrackType } from '../../gen/video/sfu/models/models';
|
|
|
14
14
|
|
|
15
15
|
import '../../rtc/__tests__/mocks/webrtc.mocks';
|
|
16
16
|
|
|
17
|
-
class TestInputMediaDeviceManagerState extends
|
|
17
|
+
class TestInputMediaDeviceManagerState extends DeviceManagerState {
|
|
18
18
|
public getDeviceIdFromStream = vi.fn(
|
|
19
19
|
(stream) => stream.getTracks()[0].getSettings().deviceId,
|
|
20
20
|
);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
class TestInputMediaDeviceManager extends
|
|
23
|
+
class TestInputMediaDeviceManager extends DeviceManager<TestInputMediaDeviceManagerState> {
|
|
24
24
|
public getDevices = vi.fn(() => of(mockVideoDevices));
|
|
25
25
|
public getStream = vi.fn(() => Promise.resolve(mockVideoStream()));
|
|
26
26
|
public publishStream = vi.fn();
|
package/src/devices/__tests__/{InputMediaDeviceManagerState.test.ts → DeviceManagerState.test.ts}
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { beforeAll, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { DeviceManagerState } from '../DeviceManagerState';
|
|
3
3
|
import { firstValueFrom } from 'rxjs';
|
|
4
4
|
import { BrowserPermission } from '../BrowserPermission';
|
|
5
5
|
|
|
6
|
-
class TestInputMediaDeviceManagerState extends
|
|
6
|
+
class TestInputMediaDeviceManagerState extends DeviceManagerState {
|
|
7
7
|
constructor() {
|
|
8
8
|
super(
|
|
9
9
|
'stop-tracks',
|
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
NoiseCancellationSettingsModeEnum,
|
|
8
8
|
OwnCapability,
|
|
9
9
|
} from '../../gen/coordinator';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
AudioBitrateProfile,
|
|
12
|
+
TrackType,
|
|
13
|
+
} from '../../gen/video/sfu/models/models';
|
|
11
14
|
import { CallingState, StreamVideoWriteableStateStore } from '../../store';
|
|
12
15
|
import {
|
|
13
16
|
mockAudioDevices,
|
|
@@ -100,6 +103,7 @@ describe('MicrophoneManager', () => {
|
|
|
100
103
|
expect(manager['call'].publish).toHaveBeenCalledWith(
|
|
101
104
|
manager.state.mediaStream,
|
|
102
105
|
TrackType.AUDIO,
|
|
106
|
+
{ audioBitrateProfile: AudioBitrateProfile.VOICE_STANDARD_UNSPECIFIED },
|
|
103
107
|
);
|
|
104
108
|
});
|
|
105
109
|
|
|
@@ -367,6 +371,42 @@ describe('MicrophoneManager', () => {
|
|
|
367
371
|
});
|
|
368
372
|
});
|
|
369
373
|
|
|
374
|
+
describe('Hi-Fi Audio', () => {
|
|
375
|
+
it('enables hi-fi audio', async () => {
|
|
376
|
+
call.state.updateFromCallResponse({
|
|
377
|
+
// @ts-expect-error partial data
|
|
378
|
+
settings: { audio: { hifi_audio_enabled: true } },
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
await manager.enable();
|
|
382
|
+
|
|
383
|
+
// @ts-expect-error - private api
|
|
384
|
+
const apply = vi.spyOn(manager, 'applySettingsToStream');
|
|
385
|
+
const publish = vi.spyOn(call, 'publish');
|
|
386
|
+
const profile = AudioBitrateProfile.MUSIC_HIGH_QUALITY;
|
|
387
|
+
await manager.setAudioBitrateProfile(profile);
|
|
388
|
+
|
|
389
|
+
expect(apply).toHaveBeenCalledOnce();
|
|
390
|
+
expect(publish).toHaveBeenCalledWith(
|
|
391
|
+
manager.state.mediaStream,
|
|
392
|
+
TrackType.AUDIO,
|
|
393
|
+
{ audioBitrateProfile: profile },
|
|
394
|
+
);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('throws an error when enabling hi-fi audio if not allowed', async () => {
|
|
398
|
+
call.state.updateFromCallResponse({
|
|
399
|
+
// @ts-expect-error partial data
|
|
400
|
+
settings: { audio: { hifi_audio_enabled: false } },
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
await manager.enable();
|
|
404
|
+
await expect(() =>
|
|
405
|
+
manager.setAudioBitrateProfile(AudioBitrateProfile.VOICE_HIGH_QUALITY),
|
|
406
|
+
).rejects.toThrowError();
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
370
410
|
afterEach(() => {
|
|
371
411
|
vi.clearAllMocks();
|
|
372
412
|
vi.resetModules();
|
|
@@ -3,9 +3,10 @@ import type { INoiseCancellation } from '@stream-io/audio-filters-web';
|
|
|
3
3
|
export class NoiseCancellationStub implements INoiseCancellation {
|
|
4
4
|
private listeners: { [event: string]: Array<(arg: boolean) => void> } = {};
|
|
5
5
|
|
|
6
|
+
canAutoEnable = async () => true;
|
|
6
7
|
isSupported = () => true;
|
|
7
8
|
init = () => Promise.resolve(undefined);
|
|
8
|
-
isEnabled = () => true;
|
|
9
|
+
isEnabled = async () => true;
|
|
9
10
|
enable = () => this.listeners['change']?.forEach((l) => l(true));
|
|
10
11
|
disable = () => this.listeners['change']?.forEach((l) => l(false));
|
|
11
12
|
setSuppressionLevel = () => {};
|
|
@@ -16,4 +17,5 @@ export class NoiseCancellationStub implements INoiseCancellation {
|
|
|
16
17
|
return () => {};
|
|
17
18
|
};
|
|
18
19
|
off = () => {};
|
|
20
|
+
resume = () => {};
|
|
19
21
|
}
|
|
@@ -6,7 +6,10 @@ import { CallingState, StreamVideoWriteableStateStore } from '../../store';
|
|
|
6
6
|
import * as RxUtils from '../../store/rxUtils';
|
|
7
7
|
import { mockCall, mockDeviceIds$, mockScreenShareStream } from './mocks';
|
|
8
8
|
import { getScreenShareStream } from '../devices';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
AudioBitrateProfile,
|
|
11
|
+
TrackType,
|
|
12
|
+
} from '../../gen/video/sfu/models/models';
|
|
10
13
|
import { Tracer } from '../../stats';
|
|
11
14
|
|
|
12
15
|
vi.mock('../devices.ts', () => {
|
|
@@ -121,6 +124,7 @@ describe('ScreenShareManager', () => {
|
|
|
121
124
|
expect(call.publish).toHaveBeenCalledWith(
|
|
122
125
|
manager.state.mediaStream,
|
|
123
126
|
TrackType.SCREEN_SHARE,
|
|
127
|
+
{ audioBitrateProfile: AudioBitrateProfile.MUSIC_HIGH_QUALITY },
|
|
124
128
|
);
|
|
125
129
|
});
|
|
126
130
|
|
package/src/devices/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from './utils';
|
|
2
2
|
export * from './devices';
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
3
|
+
export * from './DeviceManager';
|
|
4
|
+
export * from './DeviceManagerState';
|
|
5
5
|
export * from './CameraManager';
|
|
6
6
|
export * from './CameraManagerState';
|
|
7
7
|
export * from './MicrophoneManager';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { fromPartial } from '@total-typescript/shoehorn';
|
|
2
3
|
import { Call } from '../../Call';
|
|
3
4
|
import { Dispatcher } from '../../rtc';
|
|
4
5
|
import { CallState } from '../../store';
|
|
@@ -30,13 +31,12 @@ describe('internal events', () => {
|
|
|
30
31
|
dispatcher.dispatch({
|
|
31
32
|
eventPayload: {
|
|
32
33
|
oneofKind: 'connectionQualityChanged',
|
|
33
|
-
// @ts-expect-error incomplete data
|
|
34
34
|
connectionQualityChanged: {
|
|
35
35
|
connectionQualityUpdates: [
|
|
36
|
-
{
|
|
36
|
+
fromPartial({
|
|
37
37
|
sessionId: 'session-1',
|
|
38
38
|
connectionQuality: ConnectionQuality.EXCELLENT,
|
|
39
|
-
},
|
|
39
|
+
}),
|
|
40
40
|
],
|
|
41
41
|
},
|
|
42
42
|
},
|
|
@@ -60,7 +60,6 @@ describe('internal events', () => {
|
|
|
60
60
|
dispatcher.dispatch({
|
|
61
61
|
eventPayload: {
|
|
62
62
|
oneofKind: 'healthCheckResponse',
|
|
63
|
-
// @ts-expect-error incomplete data
|
|
64
63
|
healthCheckResponse: { participantCount: { total: 5, anonymous: 2 } },
|
|
65
64
|
},
|
|
66
65
|
});
|
|
@@ -118,20 +117,35 @@ describe('internal events', () => {
|
|
|
118
117
|
it('handles pinUpdated', () => {
|
|
119
118
|
const state = new CallState();
|
|
120
119
|
state.setParticipants([
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
120
|
+
fromPartial({
|
|
121
|
+
userId: 'u1',
|
|
122
|
+
sessionId: 'session-1',
|
|
123
|
+
publishedTracks: [],
|
|
124
|
+
}),
|
|
125
|
+
fromPartial({
|
|
126
|
+
userId: 'u2',
|
|
127
|
+
sessionId: 'session-2',
|
|
128
|
+
publishedTracks: [],
|
|
129
|
+
}),
|
|
125
130
|
]);
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
|
|
132
|
+
watchPinsUpdated(state)({
|
|
133
|
+
pins: [{ userId: 'u1', sessionId: 'session-1' }],
|
|
134
|
+
});
|
|
135
|
+
|
|
128
136
|
expect(state.participants).toEqual([
|
|
129
137
|
{
|
|
130
138
|
userId: 'u1',
|
|
131
139
|
sessionId: 'session-1',
|
|
132
140
|
pin: { isLocalPin: false, pinnedAt: expect.any(Number) },
|
|
141
|
+
publishedTracks: [],
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
userId: 'u2',
|
|
145
|
+
sessionId: 'session-2',
|
|
146
|
+
pin: undefined,
|
|
147
|
+
publishedTracks: [],
|
|
133
148
|
},
|
|
134
|
-
{ userId: 'u2', sessionId: 'session-2', pin: undefined },
|
|
135
149
|
]);
|
|
136
150
|
});
|
|
137
151
|
|