@pexip/media 18.4.0 → 19.0.0
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 +130 -0
- package/README.md +9 -8
- package/dist/audioMixingProcessor.d.ts +2 -2
- package/dist/audioMixingProcessor.js +113 -100
- package/dist/audioProcessor.d.ts +4 -4
- package/dist/audioProcessor.js +117 -152
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +7 -0
- package/dist/media.d.ts +12 -2
- package/dist/media.js +242 -170
- package/dist/previewController.d.ts +10 -9
- package/dist/previewController.js +99 -68
- package/dist/signals.d.ts +10 -1
- package/dist/signals.js +9 -0
- package/dist/status.d.ts +5 -2
- package/dist/status.js +3 -3
- package/dist/types.d.ts +135 -40
- package/dist/userMedia.d.ts +10 -8
- package/dist/userMedia.js +122 -178
- package/dist/utils.d.ts +25 -47
- package/dist/utils.js +487 -186
- package/dist/videoProcessor.d.ts +3 -7
- package/dist/videoProcessor.js +103 -139
- package/package.json +12 -7
package/dist/audioProcessor.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createQueue, isEmpty } from '@pexip/utils';
|
|
1
|
+
import { extractConstraintsWithKeys } from '@pexip/media-control';
|
|
2
|
+
import { createQueue, isEmpty, assert } from '@pexip/utils';
|
|
3
3
|
import { createAudioGraph, createAudioGraphProxy, createStreamSourceGraphNode, createStreamDestinationGraphNode, createAnalyzerSubscribableGraphNode, createDenoiseWorkletGraphNode, createAudioSignalDetector, createVADetector, createVoiceDetectorFromTimeData, createVoiceDetectorFromProbability, avg, } from '@pexip/media-processor';
|
|
4
|
+
import { PROCESSOR_LABELS } from './constants';
|
|
4
5
|
import { logger } from './logger';
|
|
5
6
|
import { isAudioContentHint } from './typeGuard';
|
|
6
|
-
import {
|
|
7
|
+
import { createMediaTrack } from './utils';
|
|
7
8
|
/**
|
|
8
9
|
* Fetch the wasm when it doesn't exist from the provided, otherwise do nothing
|
|
9
10
|
*
|
|
@@ -58,7 +59,7 @@ audioGraphOptions, audioSignalDetectionDuration = 4.0, // 4 seconds
|
|
|
58
59
|
clock, createNodes, denoiseParams, fftSize = 2048, // FFT size
|
|
59
60
|
onAudioSignalDetected, onVoiceActivityDetected, shouldEnable, silentThreshold = 10.0 / 32767, // At least one LSB 16-bit data (compare is on absolute value).
|
|
60
61
|
throttleMs = 3000, // 3 seconds
|
|
61
|
-
|
|
62
|
+
label = PROCESSOR_LABELS.AudioProcessor, }) => {
|
|
62
63
|
const props = {
|
|
63
64
|
audioGraphOptions,
|
|
64
65
|
vad: false,
|
|
@@ -86,7 +87,7 @@ scope = 'media', }) => {
|
|
|
86
87
|
}
|
|
87
88
|
catch (error) {
|
|
88
89
|
logger.error({
|
|
89
|
-
|
|
90
|
+
label,
|
|
90
91
|
error,
|
|
91
92
|
moduleURL: denoiseParams?.workletModule,
|
|
92
93
|
options: denoiseParams?.workletOptions,
|
|
@@ -99,7 +100,7 @@ scope = 'media', }) => {
|
|
|
99
100
|
}
|
|
100
101
|
catch (error) {
|
|
101
102
|
logger.error({
|
|
102
|
-
|
|
103
|
+
label,
|
|
103
104
|
error,
|
|
104
105
|
url: denoiseParams?.wasmURL,
|
|
105
106
|
prevWasm: props.denoiseWasm,
|
|
@@ -144,167 +145,131 @@ scope = 'media', }) => {
|
|
|
144
145
|
props.analyzer = analyzer;
|
|
145
146
|
return analyzer;
|
|
146
147
|
};
|
|
147
|
-
return async (
|
|
148
|
-
|
|
149
|
-
updateFeatureProps(media.constraints?.audio, props);
|
|
148
|
+
return async (prevMediaTrack) => {
|
|
149
|
+
updateFeatureProps(prevMediaTrack.getConstraints(), props);
|
|
150
150
|
const shouldProcessAudio = shouldEnable() &&
|
|
151
|
-
!!media.stream?.getAudioTracks().length &&
|
|
152
151
|
(!!onVoiceActivityDetected ||
|
|
153
152
|
!!onAudioSignalDetected ||
|
|
154
|
-
props.asd ||
|
|
155
|
-
props.vad ||
|
|
156
|
-
props.denoise ||
|
|
153
|
+
!!props.asd ||
|
|
154
|
+
!!props.vad ||
|
|
155
|
+
!!props.denoise ||
|
|
157
156
|
!!createNodes);
|
|
158
|
-
if (!
|
|
159
|
-
return
|
|
157
|
+
if (!shouldProcessAudio || !prevMediaTrack.track) {
|
|
158
|
+
return prevMediaTrack;
|
|
160
159
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
160
|
+
const source = createStreamSourceGraphNode(new MediaStream([prevMediaTrack.track]));
|
|
161
|
+
const destination = createStreamDestinationGraphNode();
|
|
162
|
+
const otherNodes = createNodes?.(prevMediaTrack.track) ?? [];
|
|
163
|
+
const analyzer = createAnalyzer();
|
|
164
|
+
const initialAudioNodeConnection = [
|
|
165
|
+
[source, ...otherNodes, destination],
|
|
166
|
+
[source, analyzer],
|
|
167
|
+
];
|
|
168
|
+
logger.debug({ initialAudioNodeConnection, label }, 'Initial AudioNodeConnection');
|
|
169
|
+
const audioGraph = createAudioGraphProxy(createAudioGraph(initialAudioNodeConnection, props.audioGraphOptions), {
|
|
170
|
+
connect: (target, args) => {
|
|
171
|
+
logger.debug({ label, target, args }, 'connect nodes');
|
|
172
|
+
},
|
|
173
|
+
disconnect: (target, args) => {
|
|
174
|
+
logger.debug({ label, target, args }, 'disconnect nodes');
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
props.audioGraph = audioGraph;
|
|
178
|
+
const denoiseNode = await createDenoiseNode(props.denoise);
|
|
179
|
+
const connectDenoise = (node) => {
|
|
180
|
+
if (node) {
|
|
181
|
+
audioGraph.disconnect([source, ...otherNodes, destination]);
|
|
182
|
+
audioGraph.connect([source, node, ...otherNodes, destination]);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
connectDenoise(denoiseNode);
|
|
186
|
+
const track = destination?.node?.stream.getAudioTracks().at(0) ?? null;
|
|
187
|
+
assert(track, 'No audio track available');
|
|
188
|
+
// Inherit contentHint from previous track
|
|
189
|
+
track.contentHint = prevMediaTrack.track.contentHint;
|
|
190
|
+
const release = async () => {
|
|
191
|
+
logger.debug({ label }, 'Release Media');
|
|
192
|
+
track.stop();
|
|
193
|
+
// Release Props
|
|
194
|
+
await audioGraph.release();
|
|
195
|
+
props.denoiseNode = undefined;
|
|
196
|
+
props.analyzer = undefined;
|
|
197
|
+
props.audioGraph = undefined;
|
|
198
|
+
};
|
|
199
|
+
const mute = (mute) => {
|
|
200
|
+
if (track) {
|
|
201
|
+
track.enabled = !mute;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
return createMediaTrack({
|
|
205
|
+
label,
|
|
206
|
+
kind: 'audioinput',
|
|
207
|
+
constraints: prevMediaTrack.getConstraints(),
|
|
208
|
+
previousMediaTrack: prevMediaTrack,
|
|
209
|
+
input: prevMediaTrack.input,
|
|
210
|
+
expectedInput: prevMediaTrack.expectedInput,
|
|
211
|
+
mute,
|
|
212
|
+
track,
|
|
213
|
+
release,
|
|
214
|
+
applyConstraints: async (constraints) => {
|
|
215
|
+
if (isEmpty(constraints)) {
|
|
200
216
|
return;
|
|
201
217
|
}
|
|
202
|
-
const features = updateFeatureProps(constraints
|
|
203
|
-
logger.debug({
|
|
218
|
+
const features = updateFeatureProps(constraints, props);
|
|
219
|
+
logger.debug({ label, constraints: constraints, features }, 'apply audio constraints');
|
|
204
220
|
if (isEmpty(features) ||
|
|
205
221
|
['closed', 'closing'].includes(audioGraph.state)) {
|
|
206
222
|
return;
|
|
207
223
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (denoiseNode) {
|
|
211
|
-
|
|
212
|
-
connectDenoise(denoiseNode);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
if (props.denoiseNode) {
|
|
217
|
-
audioGraph.disconnect([
|
|
218
|
-
source,
|
|
219
|
-
props.denoiseNode,
|
|
220
|
-
...otherNodes,
|
|
221
|
-
destination,
|
|
222
|
-
]);
|
|
223
|
-
audioGraph.connect([
|
|
224
|
-
source,
|
|
225
|
-
...otherNodes,
|
|
226
|
-
destination,
|
|
227
|
-
]);
|
|
228
|
-
audioGraph.releaseInit(props.denoiseNode);
|
|
229
|
-
props.denoiseNode = undefined;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
const analyzer = createAnalyzer();
|
|
233
|
-
if (analyzer) {
|
|
234
|
-
audioGraph.connect([source, analyzer]);
|
|
224
|
+
const denoiseNode = await createDenoiseNode(props.denoise);
|
|
225
|
+
if (denoiseNode) {
|
|
226
|
+
if (!source.hasConnectedTo(denoiseNode)) {
|
|
227
|
+
connectDenoise(denoiseNode);
|
|
235
228
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
if (props.denoiseNode) {
|
|
232
|
+
audioGraph.disconnect([
|
|
233
|
+
source,
|
|
234
|
+
props.denoiseNode,
|
|
235
|
+
...otherNodes,
|
|
236
|
+
destination,
|
|
237
|
+
]);
|
|
238
|
+
audioGraph.connect([
|
|
239
|
+
source,
|
|
240
|
+
...otherNodes,
|
|
241
|
+
destination,
|
|
242
|
+
]);
|
|
243
|
+
audioGraph.releaseInit(props.denoiseNode);
|
|
244
|
+
props.denoiseNode = undefined;
|
|
242
245
|
}
|
|
243
246
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
247
|
+
const analyzer = createAnalyzer();
|
|
248
|
+
if (analyzer) {
|
|
249
|
+
audioGraph.connect([source, analyzer]);
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
if (props.analyzer) {
|
|
253
|
+
audioGraph.disconnect([source, props.analyzer]);
|
|
254
|
+
audioGraph.releaseInit(props.analyzer);
|
|
255
|
+
props.analyzer = undefined;
|
|
251
256
|
}
|
|
252
257
|
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
media.muteVideo(mute);
|
|
270
|
-
muteStreamTrack(stream)(mute, 'video');
|
|
271
|
-
};
|
|
272
|
-
const prevGetSettings = media.getSettings;
|
|
273
|
-
return wrapToJSON(shallowCopy(media, {
|
|
274
|
-
stream,
|
|
275
|
-
applyConstraints,
|
|
276
|
-
muteAudio,
|
|
277
|
-
muteVideo,
|
|
278
|
-
release,
|
|
279
|
-
getSettings: () => {
|
|
280
|
-
const { audio, video } = prevGetSettings();
|
|
281
|
-
const denoise = !!props.denoiseNode &&
|
|
282
|
-
source.hasConnectedTo(props.denoiseNode);
|
|
283
|
-
const asd = !!props.asd;
|
|
284
|
-
const vad = !!props.vad;
|
|
285
|
-
const contentHint = stream.getAudioTracks().at(0)
|
|
286
|
-
?.contentHint ?? '';
|
|
287
|
-
const audioSettings = {
|
|
288
|
-
denoise,
|
|
289
|
-
asd,
|
|
290
|
-
vad,
|
|
291
|
-
contentHint,
|
|
292
|
-
};
|
|
293
|
-
const settings = {
|
|
294
|
-
audio: audio.map(settings => ({
|
|
295
|
-
...settings,
|
|
296
|
-
...audioSettings,
|
|
297
|
-
})),
|
|
298
|
-
video,
|
|
299
|
-
};
|
|
300
|
-
logger.debug({ scope, settings: audioSettings }, 'get audio processor settings');
|
|
301
|
-
return settings;
|
|
302
|
-
},
|
|
303
|
-
}));
|
|
304
|
-
}
|
|
305
|
-
catch (error) {
|
|
306
|
-
logger.error({ scope, error }, 'Unable to use WebAudio, return the raw media instead');
|
|
307
|
-
return media;
|
|
308
|
-
}
|
|
258
|
+
},
|
|
259
|
+
getSettings: () => {
|
|
260
|
+
const denoise = !!props.denoiseNode &&
|
|
261
|
+
source.hasConnectedTo(props.denoiseNode);
|
|
262
|
+
const asd = !!props.asd;
|
|
263
|
+
const vad = !!props.vad;
|
|
264
|
+
const contentHint = track.contentHint;
|
|
265
|
+
const audioSettings = {
|
|
266
|
+
denoise,
|
|
267
|
+
asd,
|
|
268
|
+
vad,
|
|
269
|
+
contentHint,
|
|
270
|
+
};
|
|
271
|
+
return audioSettings;
|
|
272
|
+
},
|
|
273
|
+
});
|
|
309
274
|
};
|
|
310
275
|
};
|
package/dist/media.d.ts
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { IndexedDevices, MediaDeviceRequest, InputDevicePermission } from '@pexip/media-control';
|
|
2
|
+
import type { GetUserMediaProcess, Media, MediaController, MediaOptions, MediaSignals, MediaTrack } from './types';
|
|
3
|
+
export interface MediaUpdaterOptions {
|
|
4
|
+
getUserMedia: GetUserMediaProcess;
|
|
5
|
+
getCurrentDevices: () => Promise<IndexedDevices>;
|
|
6
|
+
shouldDiscardMedia: () => boolean;
|
|
7
|
+
onMediaTracksChanged: (media: Media, tracks: MediaTrack[]) => void;
|
|
8
|
+
getInputDevicePermission?: () => Promise<InputDevicePermission>;
|
|
9
|
+
signals?: MediaSignals;
|
|
10
|
+
}
|
|
11
|
+
export declare const createMediaUpdater: ({ getUserMedia, getCurrentDevices, shouldDiscardMedia: shouldDisgardMedia, onMediaTracksChanged, getInputDevicePermission, signals, }: MediaUpdaterOptions) => (constraints: MediaDeviceRequest, currentMedia: Media | undefined) => Promise<void>;
|
|
2
12
|
/**
|
|
3
13
|
* Create an object to interact with the media scream, which is usually used for
|
|
4
14
|
* our main stream.
|
|
5
15
|
*
|
|
6
16
|
* @param options - @see MediaOptions
|
|
7
17
|
*/
|
|
8
|
-
export declare const createMedia: ({ getMuteState, signals,
|
|
18
|
+
export declare const createMedia: ({ getMuteState, signals, audioProcessors, videoProcessors, getDefaultConstraints, }: MediaOptions) => MediaController;
|