@stream-io/video-client 1.4.3 → 1.4.4
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 +7 -0
- package/dist/index.browser.es.js +33 -11
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +33 -11
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +33 -11
- package/dist/index.es.js.map +1 -1
- package/dist/src/devices/InputMediaDeviceManager.d.ts +4 -3
- package/dist/src/devices/MicrophoneManager.d.ts +1 -0
- package/dist/src/devices/filters.d.ts +32 -0
- package/package.json +1 -1
- package/src/devices/InputMediaDeviceManager.ts +55 -12
- package/src/devices/MicrophoneManager.ts +6 -4
- package/src/devices/__tests__/InputMediaDeviceManagerFilters.test.ts +7 -6
- package/src/devices/filters.ts +38 -0
|
@@ -3,7 +3,7 @@ import { Call } from '../Call';
|
|
|
3
3
|
import { InputMediaDeviceManagerState } from './InputMediaDeviceManagerState';
|
|
4
4
|
import { Logger } from '../coordinator/connection/types';
|
|
5
5
|
import { TrackType } from '../gen/video/sfu/models/models';
|
|
6
|
-
|
|
6
|
+
import { MediaStreamFilter, MediaStreamFilterRegistrationResult } from './filters';
|
|
7
7
|
export declare abstract class InputMediaDeviceManager<T extends InputMediaDeviceManagerState<C>, C = MediaTrackConstraints> {
|
|
8
8
|
protected readonly call: Call;
|
|
9
9
|
readonly state: T;
|
|
@@ -17,6 +17,7 @@ export declare abstract class InputMediaDeviceManager<T extends InputMediaDevice
|
|
|
17
17
|
private isTrackStoppedDueToTrackEnd;
|
|
18
18
|
private filters;
|
|
19
19
|
private statusChangeConcurrencyTag;
|
|
20
|
+
private filterRegistrationConcurrencyTag;
|
|
20
21
|
protected constructor(call: Call, state: T, trackType: TrackType);
|
|
21
22
|
/**
|
|
22
23
|
* Lists the available audio/video devices
|
|
@@ -55,9 +56,9 @@ export declare abstract class InputMediaDeviceManager<T extends InputMediaDevice
|
|
|
55
56
|
* a new stream with the applied filter.
|
|
56
57
|
*
|
|
57
58
|
* @param filter the filter to register.
|
|
58
|
-
* @returns
|
|
59
|
+
* @returns MediaStreamFilterRegistrationResult
|
|
59
60
|
*/
|
|
60
|
-
registerFilter(filter: MediaStreamFilter):
|
|
61
|
+
registerFilter(filter: MediaStreamFilter): MediaStreamFilterRegistrationResult;
|
|
61
62
|
/**
|
|
62
63
|
* Will set the default constraints for the device.
|
|
63
64
|
*
|
|
@@ -12,6 +12,7 @@ export declare class MicrophoneManager extends InputMediaDeviceManager<Microphon
|
|
|
12
12
|
private noiseCancellation;
|
|
13
13
|
private noiseCancellationChangeUnsubscribe;
|
|
14
14
|
private noiseCancellationRegistration?;
|
|
15
|
+
private uregisterNoiseCancellation?;
|
|
15
16
|
constructor(call: Call, disableMode?: TrackDisableMode);
|
|
16
17
|
/**
|
|
17
18
|
* Enables noise cancellation for the microphone.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type MediaStreamFilter = (input: MediaStream) => MediaStreamFilterResult;
|
|
2
|
+
export type MediaStreamFilterCleanup = () => void;
|
|
3
|
+
export interface MediaStreamFilterResult {
|
|
4
|
+
/**
|
|
5
|
+
* Transformed media stream. If the filter is asynchronous, a promise which
|
|
6
|
+
* resolves with a transformed media stream.
|
|
7
|
+
*/
|
|
8
|
+
output: MediaStream | Promise<MediaStream>;
|
|
9
|
+
/**
|
|
10
|
+
* An optional cleanup callback. It is called when the filter is stopped, and
|
|
11
|
+
* when it is unregistered.
|
|
12
|
+
*/
|
|
13
|
+
stop?: MediaStreamFilterCleanup;
|
|
14
|
+
}
|
|
15
|
+
export interface MediaStreamFilterEntry {
|
|
16
|
+
start: MediaStreamFilter;
|
|
17
|
+
/**
|
|
18
|
+
* When the filter is running, it holds a cleanup callback returned when the filter
|
|
19
|
+
* was started.
|
|
20
|
+
*/
|
|
21
|
+
stop: MediaStreamFilterCleanup | undefined;
|
|
22
|
+
}
|
|
23
|
+
export interface MediaStreamFilterRegistrationResult {
|
|
24
|
+
/**
|
|
25
|
+
* Promise that resolves when the filter is applied to the stream.
|
|
26
|
+
*/
|
|
27
|
+
registered: Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Function that can be called to unregister the filter.
|
|
30
|
+
*/
|
|
31
|
+
unregister: () => Promise<void>;
|
|
32
|
+
}
|
package/package.json
CHANGED
|
@@ -8,9 +8,16 @@ import { Logger } from '../coordinator/connection/types';
|
|
|
8
8
|
import { getLogger } from '../logger';
|
|
9
9
|
import { TrackType } from '../gen/video/sfu/models/models';
|
|
10
10
|
import { deviceIds$ } from './devices';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
import {
|
|
12
|
+
settled,
|
|
13
|
+
withCancellation,
|
|
14
|
+
withoutConcurrency,
|
|
15
|
+
} from '../helpers/concurrency';
|
|
16
|
+
import {
|
|
17
|
+
MediaStreamFilter,
|
|
18
|
+
MediaStreamFilterEntry,
|
|
19
|
+
MediaStreamFilterRegistrationResult,
|
|
20
|
+
} from './filters';
|
|
14
21
|
|
|
15
22
|
export abstract class InputMediaDeviceManager<
|
|
16
23
|
T extends InputMediaDeviceManagerState<C>,
|
|
@@ -24,8 +31,11 @@ export abstract class InputMediaDeviceManager<
|
|
|
24
31
|
|
|
25
32
|
protected subscriptions: Function[] = [];
|
|
26
33
|
private isTrackStoppedDueToTrackEnd = false;
|
|
27
|
-
private filters:
|
|
34
|
+
private filters: MediaStreamFilterEntry[] = [];
|
|
28
35
|
private statusChangeConcurrencyTag = Symbol('statusChangeConcurrencyTag');
|
|
36
|
+
private filterRegistrationConcurrencyTag = Symbol(
|
|
37
|
+
'filterRegistrationConcurrencyTag',
|
|
38
|
+
);
|
|
29
39
|
|
|
30
40
|
protected constructor(
|
|
31
41
|
protected readonly call: Call,
|
|
@@ -139,14 +149,32 @@ export abstract class InputMediaDeviceManager<
|
|
|
139
149
|
* a new stream with the applied filter.
|
|
140
150
|
*
|
|
141
151
|
* @param filter the filter to register.
|
|
142
|
-
* @returns
|
|
152
|
+
* @returns MediaStreamFilterRegistrationResult
|
|
143
153
|
*/
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
registerFilter(
|
|
155
|
+
filter: MediaStreamFilter,
|
|
156
|
+
): MediaStreamFilterRegistrationResult {
|
|
157
|
+
const entry: MediaStreamFilterEntry = {
|
|
158
|
+
start: filter,
|
|
159
|
+
stop: undefined,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const registered = withoutConcurrency(
|
|
163
|
+
this.filterRegistrationConcurrencyTag,
|
|
164
|
+
async () => {
|
|
165
|
+
this.filters.push(entry);
|
|
166
|
+
await this.applySettingsToStream();
|
|
167
|
+
},
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
registered,
|
|
172
|
+
unregister: () =>
|
|
173
|
+
withoutConcurrency(this.filterRegistrationConcurrencyTag, async () => {
|
|
174
|
+
entry.stop?.();
|
|
175
|
+
this.filters = this.filters.filter((f) => f !== entry);
|
|
176
|
+
await this.applySettingsToStream();
|
|
177
|
+
}),
|
|
150
178
|
};
|
|
151
179
|
}
|
|
152
180
|
|
|
@@ -224,6 +252,7 @@ export abstract class InputMediaDeviceManager<
|
|
|
224
252
|
this.state.mediaStream.release();
|
|
225
253
|
}
|
|
226
254
|
this.state.setMediaStream(undefined, undefined);
|
|
255
|
+
this.filters.forEach((entry) => entry.stop?.());
|
|
227
256
|
}
|
|
228
257
|
}
|
|
229
258
|
|
|
@@ -335,7 +364,21 @@ export abstract class InputMediaDeviceManager<
|
|
|
335
364
|
rootStream = this.getStream(constraints as C);
|
|
336
365
|
// we publish the last MediaStream of the chain
|
|
337
366
|
stream = await this.filters.reduce(
|
|
338
|
-
(parent,
|
|
367
|
+
(parent, entry) =>
|
|
368
|
+
parent
|
|
369
|
+
.then((inputStream) => {
|
|
370
|
+
const { stop, output } = entry.start(inputStream);
|
|
371
|
+
entry.stop = stop;
|
|
372
|
+
return output;
|
|
373
|
+
})
|
|
374
|
+
.then(chainWith(parent), (error) => {
|
|
375
|
+
this.logger(
|
|
376
|
+
'warn',
|
|
377
|
+
'Fitler failed to start and will be ignored',
|
|
378
|
+
error,
|
|
379
|
+
);
|
|
380
|
+
return parent;
|
|
381
|
+
}),
|
|
339
382
|
rootStream,
|
|
340
383
|
);
|
|
341
384
|
}
|
|
@@ -24,7 +24,8 @@ export class MicrophoneManager extends InputMediaDeviceManager<MicrophoneManager
|
|
|
24
24
|
private rnSpeechDetector: RNSpeechDetector | undefined;
|
|
25
25
|
private noiseCancellation: INoiseCancellation | undefined;
|
|
26
26
|
private noiseCancellationChangeUnsubscribe: (() => void) | undefined;
|
|
27
|
-
private noiseCancellationRegistration?: Promise<
|
|
27
|
+
private noiseCancellationRegistration?: Promise<void>;
|
|
28
|
+
private uregisterNoiseCancellation?: () => Promise<void>;
|
|
28
29
|
|
|
29
30
|
constructor(
|
|
30
31
|
call: Call,
|
|
@@ -139,9 +140,11 @@ export class MicrophoneManager extends InputMediaDeviceManager<MicrophoneManager
|
|
|
139
140
|
},
|
|
140
141
|
);
|
|
141
142
|
|
|
142
|
-
|
|
143
|
+
const registrationResult = this.registerFilter(
|
|
143
144
|
noiseCancellation.toFilter(),
|
|
144
145
|
);
|
|
146
|
+
this.noiseCancellationRegistration = registrationResult.registered;
|
|
147
|
+
this.uregisterNoiseCancellation = registrationResult.unregister;
|
|
145
148
|
await this.noiseCancellationRegistration;
|
|
146
149
|
|
|
147
150
|
// handles an edge case where a noise cancellation is enabled after
|
|
@@ -170,8 +173,7 @@ export class MicrophoneManager extends InputMediaDeviceManager<MicrophoneManager
|
|
|
170
173
|
if (isReactNative()) {
|
|
171
174
|
throw new Error('Noise cancellation is not supported in React Native');
|
|
172
175
|
}
|
|
173
|
-
await this.
|
|
174
|
-
?.then((unregister) => unregister())
|
|
176
|
+
await (this.uregisterNoiseCancellation?.() ?? Promise.resolve())
|
|
175
177
|
.then(() => this.noiseCancellation?.disable())
|
|
176
178
|
.then(() => this.noiseCancellationChangeUnsubscribe?.())
|
|
177
179
|
.catch((err) => {
|
|
@@ -58,8 +58,9 @@ describe('MediaStream Filters', () => {
|
|
|
58
58
|
const track = new MediaStreamTrack();
|
|
59
59
|
vi.spyOn(mediaStream, 'getTracks').mockReturnValue([track]);
|
|
60
60
|
vi.spyOn(track, 'getSettings').mockReturnValue({ deviceId: '123' });
|
|
61
|
-
const filter = vi.fn().mockReturnValue(mediaStream);
|
|
62
|
-
const unregister =
|
|
61
|
+
const filter = vi.fn().mockReturnValue({ output: mediaStream });
|
|
62
|
+
const { registered, unregister } = manager.registerFilter(filter);
|
|
63
|
+
await registered;
|
|
63
64
|
await manager.enable();
|
|
64
65
|
|
|
65
66
|
expect(filter).toHaveBeenCalled();
|
|
@@ -92,10 +93,10 @@ describe('MediaStream Filters', () => {
|
|
|
92
93
|
|
|
93
94
|
const rootMediaStream = createMediaStream();
|
|
94
95
|
const filterMediaStream = createMediaStream();
|
|
95
|
-
const filter1 = vi.fn().mockReturnValue(rootMediaStream);
|
|
96
|
-
const filter2 = vi.fn().mockReturnValue(filterMediaStream);
|
|
97
|
-
await manager.registerFilter(filter1);
|
|
98
|
-
await manager.registerFilter(filter2);
|
|
96
|
+
const filter1 = vi.fn().mockReturnValue({ output: rootMediaStream });
|
|
97
|
+
const filter2 = vi.fn().mockReturnValue({ output: filterMediaStream });
|
|
98
|
+
await manager.registerFilter(filter1).registered;
|
|
99
|
+
await manager.registerFilter(filter2).registered;
|
|
99
100
|
await manager.enable();
|
|
100
101
|
|
|
101
102
|
expect(filter1).toHaveBeenCalled();
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type MediaStreamFilter = (input: MediaStream) => MediaStreamFilterResult;
|
|
2
|
+
export type MediaStreamFilterCleanup = () => void;
|
|
3
|
+
|
|
4
|
+
export interface MediaStreamFilterResult {
|
|
5
|
+
/**
|
|
6
|
+
* Transformed media stream. If the filter is asynchronous, a promise which
|
|
7
|
+
* resolves with a transformed media stream.
|
|
8
|
+
*/
|
|
9
|
+
output: MediaStream | Promise<MediaStream>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* An optional cleanup callback. It is called when the filter is stopped, and
|
|
13
|
+
* when it is unregistered.
|
|
14
|
+
*/
|
|
15
|
+
stop?: MediaStreamFilterCleanup;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface MediaStreamFilterEntry {
|
|
19
|
+
start: MediaStreamFilter;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* When the filter is running, it holds a cleanup callback returned when the filter
|
|
23
|
+
* was started.
|
|
24
|
+
*/
|
|
25
|
+
stop: MediaStreamFilterCleanup | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MediaStreamFilterRegistrationResult {
|
|
29
|
+
/**
|
|
30
|
+
* Promise that resolves when the filter is applied to the stream.
|
|
31
|
+
*/
|
|
32
|
+
registered: Promise<void>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Function that can be called to unregister the filter.
|
|
36
|
+
*/
|
|
37
|
+
unregister: () => Promise<void>;
|
|
38
|
+
}
|