@stream-io/video-client 0.7.9 → 0.7.11
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 +14 -0
- package/dist/index.browser.es.js +109 -69
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +109 -69
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +109 -69
- package/dist/index.es.js.map +1 -1
- package/dist/src/devices/CameraManagerState.d.ts +1 -1
- package/dist/src/devices/InputMediaDeviceManagerState.d.ts +8 -3
- package/dist/src/devices/MicrophoneManager.d.ts +11 -1
- package/dist/src/devices/MicrophoneManagerState.d.ts +2 -2
- package/dist/src/store/CallState.d.ts +1 -2
- package/package.json +1 -1
- package/src/devices/CameraManagerState.ts +7 -5
- package/src/devices/InputMediaDeviceManager.ts +7 -3
- package/src/devices/InputMediaDeviceManagerState.ts +12 -6
- package/src/devices/MicrophoneManager.ts +56 -28
- package/src/devices/MicrophoneManagerState.ts +8 -6
- package/src/devices/__tests__/MicrophoneManager.test.ts +66 -42
- package/src/helpers/RNSpeechDetector.ts +1 -13
- package/src/store/CallState.ts +50 -30
- package/src/store/__tests__/CallState.test.ts +201 -3
|
@@ -23,6 +23,6 @@ export declare class CameraManagerState extends InputMediaDeviceManagerState {
|
|
|
23
23
|
/**
|
|
24
24
|
* @internal
|
|
25
25
|
*/
|
|
26
|
-
setMediaStream(stream: MediaStream | undefined): void;
|
|
26
|
+
setMediaStream(stream: MediaStream | undefined, rootStream: MediaStream | undefined): void;
|
|
27
27
|
protected getDeviceIdFromStream(stream: MediaStream): string | undefined;
|
|
28
28
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { BehaviorSubject, Observable } from 'rxjs';
|
|
2
2
|
import { RxUtils } from '../store';
|
|
3
3
|
export type InputDeviceStatus = 'enabled' | 'disabled' | undefined;
|
|
4
|
+
export type TrackDisableMode = 'stop-tracks' | 'disable-tracks';
|
|
4
5
|
export declare abstract class InputMediaDeviceManagerState<C = MediaTrackConstraints> {
|
|
5
|
-
readonly disableMode:
|
|
6
|
+
readonly disableMode: TrackDisableMode;
|
|
6
7
|
private readonly permissionName;
|
|
7
8
|
protected statusSubject: BehaviorSubject<InputDeviceStatus>;
|
|
8
9
|
protected mediaStreamSubject: BehaviorSubject<MediaStream | undefined>;
|
|
@@ -41,7 +42,7 @@ export declare abstract class InputMediaDeviceManagerState<C = MediaTrackConstra
|
|
|
41
42
|
* @param permissionName the permission name to use for querying.
|
|
42
43
|
* `undefined` means no permission is required.
|
|
43
44
|
*/
|
|
44
|
-
constructor(disableMode?:
|
|
45
|
+
constructor(disableMode?: TrackDisableMode, permissionName?: PermissionName | undefined);
|
|
45
46
|
/**
|
|
46
47
|
* The device status
|
|
47
48
|
*/
|
|
@@ -67,10 +68,14 @@ export declare abstract class InputMediaDeviceManagerState<C = MediaTrackConstra
|
|
|
67
68
|
*/
|
|
68
69
|
setStatus(status: InputDeviceStatus): void;
|
|
69
70
|
/**
|
|
71
|
+
* Updates the `mediaStream` state variable.
|
|
72
|
+
*
|
|
70
73
|
* @internal
|
|
71
74
|
* @param stream the stream to set.
|
|
75
|
+
* @param rootStream the root stream, applicable when filters are used
|
|
76
|
+
* as this is the stream that holds the actual deviceId information.
|
|
72
77
|
*/
|
|
73
|
-
setMediaStream(stream: MediaStream | undefined): void;
|
|
78
|
+
setMediaStream(stream: MediaStream | undefined, rootStream: MediaStream | undefined): void;
|
|
74
79
|
/**
|
|
75
80
|
* @internal
|
|
76
81
|
* @param deviceId the device id to set.
|
|
@@ -3,13 +3,15 @@ import type { INoiseCancellation } from '@stream-io/audio-filters-web';
|
|
|
3
3
|
import { Call } from '../Call';
|
|
4
4
|
import { InputMediaDeviceManager } from './InputMediaDeviceManager';
|
|
5
5
|
import { MicrophoneManagerState } from './MicrophoneManagerState';
|
|
6
|
+
import { TrackDisableMode } from './InputMediaDeviceManagerState';
|
|
6
7
|
export declare class MicrophoneManager extends InputMediaDeviceManager<MicrophoneManagerState> {
|
|
8
|
+
private speakingWhileMutedNotificationEnabled;
|
|
7
9
|
private soundDetectorCleanup?;
|
|
8
10
|
private rnSpeechDetector;
|
|
9
11
|
private noiseCancellation;
|
|
10
12
|
private noiseCancellationChangeUnsubscribe;
|
|
11
13
|
private noiseCancellationRegistration?;
|
|
12
|
-
constructor(call: Call);
|
|
14
|
+
constructor(call: Call, disableMode?: TrackDisableMode);
|
|
13
15
|
/**
|
|
14
16
|
* Enables noise cancellation for the microphone.
|
|
15
17
|
*
|
|
@@ -23,6 +25,14 @@ export declare class MicrophoneManager extends InputMediaDeviceManager<Microphon
|
|
|
23
25
|
* Note: not supported in React Native.
|
|
24
26
|
*/
|
|
25
27
|
disableNoiseCancellation(): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Enables speaking while muted notification.
|
|
30
|
+
*/
|
|
31
|
+
enableSpeakingWhileMutedNotification(): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Disables speaking while muted notification.
|
|
34
|
+
*/
|
|
35
|
+
disableSpeakingWhileMutedNotification(): Promise<void>;
|
|
26
36
|
protected getDevices(): Observable<MediaDeviceInfo[]>;
|
|
27
37
|
protected getStream(constraints: MediaTrackConstraints): Promise<MediaStream>;
|
|
28
38
|
protected publishStream(stream: MediaStream): Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Observable } from 'rxjs';
|
|
2
|
-
import { InputMediaDeviceManagerState } from './InputMediaDeviceManagerState';
|
|
2
|
+
import { InputMediaDeviceManagerState, TrackDisableMode } from './InputMediaDeviceManagerState';
|
|
3
3
|
export declare class MicrophoneManagerState extends InputMediaDeviceManagerState {
|
|
4
4
|
private speakingWhileMutedSubject;
|
|
5
5
|
/**
|
|
@@ -8,7 +8,7 @@ export declare class MicrophoneManagerState extends InputMediaDeviceManagerState
|
|
|
8
8
|
* This feature is not available in the React Native SDK.
|
|
9
9
|
*/
|
|
10
10
|
speakingWhileMuted$: Observable<boolean>;
|
|
11
|
-
constructor();
|
|
11
|
+
constructor(disableMode: TrackDisableMode);
|
|
12
12
|
/**
|
|
13
13
|
* `true` if the user's microphone is muted but they'are speaking.
|
|
14
14
|
*
|
|
@@ -5,7 +5,6 @@ import { CallStatsReport } from '../stats';
|
|
|
5
5
|
import { CallIngressResponse, CallResponse, CallSessionResponse, CallSettingsResponse, EgressResponse, MemberResponse, OwnCapability, ThumbnailResponse, UserResponse, WSEvent } from '../gen/coordinator';
|
|
6
6
|
import { Pin } from '../gen/video/sfu/models/models';
|
|
7
7
|
import { Comparator } from '../sorting';
|
|
8
|
-
import { Logger } from '../coordinator/connection/types';
|
|
9
8
|
/**
|
|
10
9
|
* Represents the state of the current call.
|
|
11
10
|
*/
|
|
@@ -211,7 +210,7 @@ export declare class CallState {
|
|
|
211
210
|
* Will provide the thumbnails of this call.
|
|
212
211
|
*/
|
|
213
212
|
thumbnails$: Observable<ThumbnailResponse | undefined>;
|
|
214
|
-
readonly logger: Logger;
|
|
213
|
+
readonly logger: import("../..").Logger;
|
|
215
214
|
/**
|
|
216
215
|
* A list of comparators that are used to sort the participants.
|
|
217
216
|
*
|
package/package.json
CHANGED
|
@@ -45,8 +45,11 @@ export class CameraManagerState extends InputMediaDeviceManagerState {
|
|
|
45
45
|
/**
|
|
46
46
|
* @internal
|
|
47
47
|
*/
|
|
48
|
-
setMediaStream(
|
|
49
|
-
|
|
48
|
+
setMediaStream(
|
|
49
|
+
stream: MediaStream | undefined,
|
|
50
|
+
rootStream: MediaStream | undefined,
|
|
51
|
+
): void {
|
|
52
|
+
super.setMediaStream(stream, rootStream);
|
|
50
53
|
if (stream) {
|
|
51
54
|
// RN getSettings() doesn't return facingMode, so we don't verify camera direction
|
|
52
55
|
const direction = isReactNative()
|
|
@@ -59,8 +62,7 @@ export class CameraManagerState extends InputMediaDeviceManagerState {
|
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
protected getDeviceIdFromStream(stream: MediaStream): string | undefined {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
| undefined;
|
|
65
|
+
const [track] = stream.getVideoTracks();
|
|
66
|
+
return track?.getSettings().deviceId;
|
|
65
67
|
}
|
|
66
68
|
}
|
|
@@ -209,7 +209,7 @@ export abstract class InputMediaDeviceManager<
|
|
|
209
209
|
// @ts-expect-error called to dispose the stream in RN
|
|
210
210
|
this.state.mediaStream.release();
|
|
211
211
|
}
|
|
212
|
-
this.state.setMediaStream(undefined);
|
|
212
|
+
this.state.setMediaStream(undefined, undefined);
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
|
|
@@ -245,6 +245,7 @@ export abstract class InputMediaDeviceManager<
|
|
|
245
245
|
protected async unmuteStream() {
|
|
246
246
|
this.logger('debug', 'Starting stream');
|
|
247
247
|
let stream: MediaStream;
|
|
248
|
+
let rootStream: Promise<MediaStream> | undefined;
|
|
248
249
|
if (
|
|
249
250
|
this.state.mediaStream &&
|
|
250
251
|
this.getTracks().every((t) => t.readyState === 'live')
|
|
@@ -315,17 +316,20 @@ export abstract class InputMediaDeviceManager<
|
|
|
315
316
|
return filterStream;
|
|
316
317
|
};
|
|
317
318
|
|
|
319
|
+
// the rootStream represents the stream coming from the actual device
|
|
320
|
+
// e.g. camera or microphone stream
|
|
321
|
+
rootStream = this.getStream(constraints as C);
|
|
318
322
|
// we publish the last MediaStream of the chain
|
|
319
323
|
stream = await this.filters.reduce(
|
|
320
324
|
(parent, filter) => parent.then(filter).then(chainWith(parent)),
|
|
321
|
-
|
|
325
|
+
rootStream,
|
|
322
326
|
);
|
|
323
327
|
}
|
|
324
328
|
if (this.call.state.callingState === CallingState.JOINED) {
|
|
325
329
|
await this.publishStream(stream);
|
|
326
330
|
}
|
|
327
331
|
if (this.state.mediaStream !== stream) {
|
|
328
|
-
this.state.setMediaStream(stream);
|
|
332
|
+
this.state.setMediaStream(stream, await rootStream);
|
|
329
333
|
this.getTracks().forEach((track) => {
|
|
330
334
|
track.addEventListener('ended', async () => {
|
|
331
335
|
if (this.enablePromise) {
|
|
@@ -9,6 +9,7 @@ import { RxUtils } from '../store';
|
|
|
9
9
|
import { getLogger } from '../logger';
|
|
10
10
|
|
|
11
11
|
export type InputDeviceStatus = 'enabled' | 'disabled' | undefined;
|
|
12
|
+
export type TrackDisableMode = 'stop-tracks' | 'disable-tracks';
|
|
12
13
|
|
|
13
14
|
export abstract class InputMediaDeviceManagerState<C = MediaTrackConstraints> {
|
|
14
15
|
protected statusSubject = new BehaviorSubject<InputDeviceStatus>(undefined);
|
|
@@ -102,9 +103,7 @@ export abstract class InputMediaDeviceManagerState<C = MediaTrackConstraints> {
|
|
|
102
103
|
* `undefined` means no permission is required.
|
|
103
104
|
*/
|
|
104
105
|
constructor(
|
|
105
|
-
public readonly disableMode:
|
|
106
|
-
| 'stop-tracks'
|
|
107
|
-
| 'disable-tracks' = 'stop-tracks',
|
|
106
|
+
public readonly disableMode: TrackDisableMode = 'stop-tracks',
|
|
108
107
|
private readonly permissionName: PermissionName | undefined = undefined,
|
|
109
108
|
) {}
|
|
110
109
|
|
|
@@ -146,13 +145,20 @@ export abstract class InputMediaDeviceManagerState<C = MediaTrackConstraints> {
|
|
|
146
145
|
}
|
|
147
146
|
|
|
148
147
|
/**
|
|
148
|
+
* Updates the `mediaStream` state variable.
|
|
149
|
+
*
|
|
149
150
|
* @internal
|
|
150
151
|
* @param stream the stream to set.
|
|
152
|
+
* @param rootStream the root stream, applicable when filters are used
|
|
153
|
+
* as this is the stream that holds the actual deviceId information.
|
|
151
154
|
*/
|
|
152
|
-
setMediaStream(
|
|
155
|
+
setMediaStream(
|
|
156
|
+
stream: MediaStream | undefined,
|
|
157
|
+
rootStream: MediaStream | undefined,
|
|
158
|
+
) {
|
|
153
159
|
this.setCurrentValue(this.mediaStreamSubject, stream);
|
|
154
|
-
if (
|
|
155
|
-
this.setDevice(this.getDeviceIdFromStream(
|
|
160
|
+
if (rootStream) {
|
|
161
|
+
this.setDevice(this.getDeviceIdFromStream(rootStream));
|
|
156
162
|
}
|
|
157
163
|
}
|
|
158
164
|
|
|
@@ -3,6 +3,7 @@ import type { INoiseCancellation } from '@stream-io/audio-filters-web';
|
|
|
3
3
|
import { Call } from '../Call';
|
|
4
4
|
import { InputMediaDeviceManager } from './InputMediaDeviceManager';
|
|
5
5
|
import { MicrophoneManagerState } from './MicrophoneManagerState';
|
|
6
|
+
import { TrackDisableMode } from './InputMediaDeviceManagerState';
|
|
6
7
|
import { getAudioDevices, getAudioStream } from './devices';
|
|
7
8
|
import { TrackType } from '../gen/video/sfu/models/models';
|
|
8
9
|
import { createSoundDetector } from '../helpers/sound-detector';
|
|
@@ -16,37 +17,48 @@ import { createSubscription } from '../store/rxUtils';
|
|
|
16
17
|
import { RNSpeechDetector } from '../helpers/RNSpeechDetector';
|
|
17
18
|
|
|
18
19
|
export class MicrophoneManager extends InputMediaDeviceManager<MicrophoneManagerState> {
|
|
20
|
+
private speakingWhileMutedNotificationEnabled = true;
|
|
19
21
|
private soundDetectorCleanup?: Function;
|
|
20
22
|
private rnSpeechDetector: RNSpeechDetector | undefined;
|
|
21
23
|
private noiseCancellation: INoiseCancellation | undefined;
|
|
22
24
|
private noiseCancellationChangeUnsubscribe: (() => void) | undefined;
|
|
23
25
|
private noiseCancellationRegistration?: Promise<() => Promise<void>>;
|
|
24
26
|
|
|
25
|
-
constructor(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
27
|
+
constructor(
|
|
28
|
+
call: Call,
|
|
29
|
+
disableMode: TrackDisableMode = isReactNative()
|
|
30
|
+
? 'disable-tracks'
|
|
31
|
+
: 'stop-tracks',
|
|
32
|
+
) {
|
|
33
|
+
super(call, new MicrophoneManagerState(disableMode), TrackType.AUDIO);
|
|
34
|
+
|
|
35
|
+
this.subscriptions.push(
|
|
36
|
+
createSubscription(
|
|
37
|
+
combineLatest([
|
|
38
|
+
this.call.state.callingState$,
|
|
39
|
+
this.call.state.ownCapabilities$,
|
|
40
|
+
this.state.selectedDevice$,
|
|
41
|
+
this.state.status$,
|
|
42
|
+
]),
|
|
43
|
+
async ([callingState, ownCapabilities, deviceId, status]) => {
|
|
44
|
+
if (callingState === CallingState.LEFT) {
|
|
45
|
+
await this.stopSpeakingWhileMutedDetection();
|
|
46
|
+
}
|
|
47
|
+
if (callingState !== CallingState.JOINED) return;
|
|
48
|
+
if (!this.speakingWhileMutedNotificationEnabled) return;
|
|
49
|
+
|
|
50
|
+
if (ownCapabilities.includes(OwnCapability.SEND_AUDIO)) {
|
|
51
|
+
if (status === 'disabled') {
|
|
52
|
+
await this.startSpeakingWhileMutedDetection(deviceId);
|
|
53
|
+
} else {
|
|
54
|
+
await this.stopSpeakingWhileMutedDetection();
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
await this.stopSpeakingWhileMutedDetection();
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
),
|
|
61
|
+
);
|
|
50
62
|
|
|
51
63
|
this.subscriptions.push(
|
|
52
64
|
createSubscription(this.call.state.callingState$, (callingState) => {
|
|
@@ -163,6 +175,24 @@ export class MicrophoneManager extends InputMediaDeviceManager<MicrophoneManager
|
|
|
163
175
|
await this.call.notifyNoiseCancellationStopped();
|
|
164
176
|
}
|
|
165
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Enables speaking while muted notification.
|
|
180
|
+
*/
|
|
181
|
+
async enableSpeakingWhileMutedNotification() {
|
|
182
|
+
this.speakingWhileMutedNotificationEnabled = true;
|
|
183
|
+
if (this.state.status === 'disabled') {
|
|
184
|
+
await this.startSpeakingWhileMutedDetection(this.state.selectedDevice);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Disables speaking while muted notification.
|
|
190
|
+
*/
|
|
191
|
+
async disableSpeakingWhileMutedNotification() {
|
|
192
|
+
this.speakingWhileMutedNotificationEnabled = false;
|
|
193
|
+
await this.stopSpeakingWhileMutedDetection();
|
|
194
|
+
}
|
|
195
|
+
|
|
166
196
|
protected getDevices(): Observable<MediaDeviceInfo[]> {
|
|
167
197
|
return getAudioDevices();
|
|
168
198
|
}
|
|
@@ -208,9 +238,7 @@ export class MicrophoneManager extends InputMediaDeviceManager<MicrophoneManager
|
|
|
208
238
|
}
|
|
209
239
|
|
|
210
240
|
private async stopSpeakingWhileMutedDetection() {
|
|
211
|
-
if (!this.soundDetectorCleanup)
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
241
|
+
if (!this.soundDetectorCleanup) return;
|
|
214
242
|
this.state.setSpeakingWhileMuted(false);
|
|
215
243
|
try {
|
|
216
244
|
await this.soundDetectorCleanup();
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { BehaviorSubject, distinctUntilChanged, Observable } from 'rxjs';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
InputMediaDeviceManagerState,
|
|
4
|
+
TrackDisableMode,
|
|
5
|
+
} from './InputMediaDeviceManagerState';
|
|
3
6
|
|
|
4
7
|
export class MicrophoneManagerState extends InputMediaDeviceManagerState {
|
|
5
8
|
private speakingWhileMutedSubject = new BehaviorSubject<boolean>(false);
|
|
@@ -11,9 +14,9 @@ export class MicrophoneManagerState extends InputMediaDeviceManagerState {
|
|
|
11
14
|
*/
|
|
12
15
|
speakingWhileMuted$: Observable<boolean>;
|
|
13
16
|
|
|
14
|
-
constructor() {
|
|
17
|
+
constructor(disableMode: TrackDisableMode) {
|
|
15
18
|
super(
|
|
16
|
-
|
|
19
|
+
disableMode,
|
|
17
20
|
// `microphone` is not in the W3C standard yet,
|
|
18
21
|
// but it's supported by Chrome and Safari.
|
|
19
22
|
'microphone' as PermissionName,
|
|
@@ -41,8 +44,7 @@ export class MicrophoneManagerState extends InputMediaDeviceManagerState {
|
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
protected getDeviceIdFromStream(stream: MediaStream): string | undefined {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
| undefined;
|
|
47
|
+
const [track] = stream.getAudioTracks();
|
|
48
|
+
return track?.getSettings().deviceId;
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -76,6 +76,7 @@ describe('MicrophoneManager', () => {
|
|
|
76
76
|
streamClient: new StreamClient('abc123'),
|
|
77
77
|
clientStore: new StreamVideoWriteableStateStore(),
|
|
78
78
|
}),
|
|
79
|
+
'disable-tracks',
|
|
79
80
|
);
|
|
80
81
|
});
|
|
81
82
|
it('list devices', () => {
|
|
@@ -147,65 +148,88 @@ describe('MicrophoneManager', () => {
|
|
|
147
148
|
expect(manager.state.mediaStream).toBeUndefined();
|
|
148
149
|
});
|
|
149
150
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
describe('Speaking While Muted', () => {
|
|
152
|
+
it(`should start sound detection if mic is disabled`, async () => {
|
|
153
|
+
await manager.enable();
|
|
154
|
+
// @ts-expect-error
|
|
155
|
+
vi.spyOn(manager, 'startSpeakingWhileMutedDetection');
|
|
156
|
+
await manager.disable();
|
|
155
157
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
expect(manager['startSpeakingWhileMutedDetection']).toHaveBeenCalled();
|
|
159
|
+
});
|
|
158
160
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
161
|
+
it(`should stop sound detection if mic is enabled`, async () => {
|
|
162
|
+
manager.state.setSpeakingWhileMuted(true);
|
|
163
|
+
manager['soundDetectorCleanup'] = () => {};
|
|
162
164
|
|
|
163
|
-
|
|
165
|
+
await manager.enable();
|
|
164
166
|
|
|
165
|
-
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('should update speaking while muted state', async () => {
|
|
169
|
-
const mock = createSoundDetector as Mock;
|
|
170
|
-
let handler: SoundStateChangeHandler;
|
|
171
|
-
mock.mockImplementation((_: MediaStream, h: SoundStateChangeHandler) => {
|
|
172
|
-
handler = h;
|
|
167
|
+
expect(manager.state.speakingWhileMuted).toBe(false);
|
|
173
168
|
});
|
|
174
|
-
await manager['startSpeakingWhileMutedDetection']();
|
|
175
169
|
|
|
176
|
-
|
|
170
|
+
it('should update speaking while muted state', async () => {
|
|
171
|
+
const mock = createSoundDetector as Mock;
|
|
172
|
+
let handler: SoundStateChangeHandler;
|
|
173
|
+
mock.mockImplementation((_: MediaStream, h: SoundStateChangeHandler) => {
|
|
174
|
+
handler = h;
|
|
175
|
+
});
|
|
176
|
+
await manager['startSpeakingWhileMutedDetection']();
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
expect(manager.state.speakingWhileMuted).toBe(false);
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
handler!({ isSoundDetected: true, audioLevel: 2 });
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
expect(manager.state.speakingWhileMuted).toBe(true);
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
});
|
|
184
|
+
handler!({ isSoundDetected: false, audioLevel: 0 });
|
|
186
185
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
await manager.disable();
|
|
186
|
+
expect(manager.state.speakingWhileMuted).toBe(false);
|
|
187
|
+
});
|
|
190
188
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
189
|
+
it('should stop speaking while muted notifications if user loses permission to send audio', async () => {
|
|
190
|
+
await manager.enable();
|
|
191
|
+
await manager.disable();
|
|
194
192
|
|
|
195
|
-
|
|
196
|
-
|
|
193
|
+
// @ts-expect-error
|
|
194
|
+
vi.spyOn(manager, 'stopSpeakingWhileMutedDetection');
|
|
195
|
+
manager['call'].state.setOwnCapabilities([]);
|
|
197
196
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
197
|
+
expect(manager['stopSpeakingWhileMutedDetection']).toHaveBeenCalled();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should start speaking while muted notifications if user gains permission to send audio', async () => {
|
|
201
|
+
await manager.enable();
|
|
202
|
+
await manager.disable();
|
|
201
203
|
|
|
202
|
-
|
|
204
|
+
manager['call'].state.setOwnCapabilities([]);
|
|
203
205
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
// @ts-expect-error
|
|
207
|
+
vi.spyOn(manager, 'stopSpeakingWhileMutedDetection');
|
|
208
|
+
manager['call'].state.setOwnCapabilities([OwnCapability.SEND_AUDIO]);
|
|
207
209
|
|
|
208
|
-
|
|
210
|
+
expect(manager['stopSpeakingWhileMutedDetection']).toHaveBeenCalled();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it(`disables speaking while muted notifications`, async () => {
|
|
214
|
+
// @ts-expect-error - private api
|
|
215
|
+
vi.spyOn(manager, 'startSpeakingWhileMutedDetection');
|
|
216
|
+
// @ts-expect-error - private api
|
|
217
|
+
vi.spyOn(manager, 'stopSpeakingWhileMutedDetection');
|
|
218
|
+
await manager.disableSpeakingWhileMutedNotification();
|
|
219
|
+
await manager.disable();
|
|
220
|
+
expect(manager['stopSpeakingWhileMutedDetection']).toHaveBeenCalled();
|
|
221
|
+
expect(
|
|
222
|
+
manager['startSpeakingWhileMutedDetection'],
|
|
223
|
+
).not.toHaveBeenCalled();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it(`enables speaking while muted notifications`, async () => {
|
|
227
|
+
// @ts-expect-error - private api
|
|
228
|
+
vi.spyOn(manager, 'startSpeakingWhileMutedDetection');
|
|
229
|
+
await manager.enableSpeakingWhileMutedNotification();
|
|
230
|
+
await manager.disable();
|
|
231
|
+
expect(manager['startSpeakingWhileMutedDetection']).toHaveBeenCalled();
|
|
232
|
+
});
|
|
209
233
|
});
|
|
210
234
|
|
|
211
235
|
describe('Noise Cancellation', () => {
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
import { BaseStats } from '../stats';
|
|
2
2
|
import { SoundStateChangeHandler } from './sound-detector';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Flatten the stats report into an array of stats objects.
|
|
6
|
-
*
|
|
7
|
-
* @param report the report to flatten.
|
|
8
|
-
*/
|
|
9
|
-
const flatten = (report: RTCStatsReport) => {
|
|
10
|
-
const stats: RTCStats[] = [];
|
|
11
|
-
report.forEach((s) => {
|
|
12
|
-
stats.push(s);
|
|
13
|
-
});
|
|
14
|
-
return stats;
|
|
15
|
-
};
|
|
3
|
+
import { flatten } from '../stats/utils';
|
|
16
4
|
|
|
17
5
|
const AUDIO_LEVEL_THRESHOLD = 0.2;
|
|
18
6
|
|