@pexip/media 18.5.0 → 19.1.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 +126 -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 +250 -169
- package/dist/previewController.d.ts +10 -9
- package/dist/previewController.js +100 -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 +489 -187
- package/dist/videoProcessor.d.ts +3 -7
- package/dist/videoProcessor.js +103 -139
- package/package.json +6 -5
package/dist/videoProcessor.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { VideoProcessor, SegmentationTransform, SegmentationModel } from '@pexip/media-processor';
|
|
2
2
|
import type { MediaDeviceRequest } from '@pexip/media-control';
|
|
3
|
-
import type {
|
|
3
|
+
import type { TrackProcessor, VideoRenderParams, Segmenters, VideoStreamTrackProcessorAPIs, VideoContentHint } from './types';
|
|
4
4
|
interface ProcessorDeps {
|
|
5
5
|
videoProcessor?: () => VideoProcessor;
|
|
6
6
|
transformer?: SegmentationTransform;
|
|
@@ -18,16 +18,12 @@ interface VideoStreamProcessOptions extends Partial<VideoRenderParams>, Omit<Pro
|
|
|
18
18
|
* Whether or to enable this processor
|
|
19
19
|
*/
|
|
20
20
|
shouldEnable: () => boolean;
|
|
21
|
-
/**
|
|
22
|
-
* Callback when error occurs
|
|
23
|
-
*/
|
|
24
|
-
onError?: (error: Error) => void;
|
|
25
21
|
processingWidth: number;
|
|
26
22
|
processingHeight: number;
|
|
27
23
|
hasInitializedDeps?: boolean;
|
|
28
24
|
width?: number;
|
|
29
25
|
height?: number;
|
|
30
|
-
|
|
26
|
+
label?: string;
|
|
31
27
|
}
|
|
32
28
|
interface VideoStreamProcessProps extends Partial<VideoRenderParams>, Required<ProcessorDeps> {
|
|
33
29
|
hasInitialized: boolean;
|
|
@@ -37,5 +33,5 @@ declare const FEATURE_KEYS: readonly ["backgroundBlurAmount", "backgroundImageUr
|
|
|
37
33
|
type FeaturePropKeys = (typeof FEATURE_KEYS)[number];
|
|
38
34
|
type FeatureProps = Pick<Partial<VideoStreamProcessProps>, FeaturePropKeys>;
|
|
39
35
|
export declare const updateFeatureProps: (constraints: MediaDeviceRequest['video'], props: FeatureProps) => FeatureProps;
|
|
40
|
-
export declare const createVideoStreamProcess: ({ trackProcessorAPI, processingWidth, processingHeight, shouldEnable, frameRate, videoSegmentation, foregroundThreshold, backgroundImageUrl, maskCombineRatio, edgeBlurAmount,
|
|
36
|
+
export declare const createVideoStreamProcess: ({ trackProcessorAPI, processingWidth, processingHeight, shouldEnable, frameRate, videoSegmentation, foregroundThreshold, backgroundImageUrl, maskCombineRatio, edgeBlurAmount, label, ...options }: VideoStreamProcessOptions) => TrackProcessor;
|
|
41
37
|
export {};
|
package/dist/videoProcessor.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createVideoProcessor, createCanvasTransform, createVideoTrackProcessor, createVideoTrackProcessorWithFallback, isRenderEffects, isSegmentationModel, } from '@pexip/media-processor';
|
|
2
2
|
import { muteStreamTrack, extractConstraintsWithKeys, getValueFromConstrainNumber, } from '@pexip/media-control';
|
|
3
|
-
import { isEmpty } from '@pexip/utils';
|
|
4
|
-
import {
|
|
3
|
+
import { isEmpty, assert } from '@pexip/utils';
|
|
4
|
+
import { createMediaTrack, getBlurKernelSize } from './utils';
|
|
5
5
|
import { logger, proxyWithLog } from './logger';
|
|
6
6
|
import { isVideoContentHint } from './typeGuard';
|
|
7
|
+
import { PROCESSOR_LABELS } from './constants';
|
|
7
8
|
const FEATURE_KEYS = [
|
|
8
9
|
'backgroundBlurAmount',
|
|
9
10
|
'backgroundImageUrl',
|
|
@@ -26,7 +27,7 @@ export const updateFeatureProps = (constraints, props) => {
|
|
|
26
27
|
return FEATURE_KEYS.reduce((accm, key) => {
|
|
27
28
|
switch (key) {
|
|
28
29
|
case 'contentHint': {
|
|
29
|
-
const [[feature] = []] = extracted[key];
|
|
30
|
+
const [[feature = ''] = []] = extracted[key];
|
|
30
31
|
if (isVideoContentHint(feature) && props[key] !== feature) {
|
|
31
32
|
props[key] = feature;
|
|
32
33
|
return { ...accm, [key]: feature };
|
|
@@ -130,34 +131,28 @@ const applyFeatures = (transformer, features) => {
|
|
|
130
131
|
}
|
|
131
132
|
});
|
|
132
133
|
};
|
|
133
|
-
const adjustResolution = async (
|
|
134
|
+
const adjustResolution = async (track, features, processingSize) => {
|
|
134
135
|
if (features.videoSegmentation) {
|
|
135
|
-
const
|
|
136
|
-
const constraints = updateFeatureProps(
|
|
136
|
+
const videoSettings = track.getSettings();
|
|
137
|
+
const constraints = updateFeatureProps(track.getConstraints(), {});
|
|
137
138
|
switch (features.videoSegmentation) {
|
|
138
139
|
case 'blur':
|
|
139
140
|
case 'overlay': {
|
|
140
141
|
if (videoSettings?.height !== processingSize.height) {
|
|
141
142
|
try {
|
|
142
|
-
await
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
height: processingSize.height,
|
|
146
|
-
},
|
|
143
|
+
await track.applyConstraints({
|
|
144
|
+
width: processingSize.width,
|
|
145
|
+
height: processingSize.height,
|
|
147
146
|
});
|
|
148
|
-
const
|
|
147
|
+
const postVideoSettings = track.getSettings();
|
|
149
148
|
if (postVideoSettings?.height !== processingSize.height) {
|
|
150
149
|
// Workaround Firefox 16:9 ratio https://bugzilla.mozilla.org/show_bug.cgi?id=1193640
|
|
151
|
-
await
|
|
152
|
-
video: { height: 720 },
|
|
153
|
-
});
|
|
150
|
+
await track.applyConstraints({ height: 720 });
|
|
154
151
|
}
|
|
155
152
|
}
|
|
156
153
|
catch (error) {
|
|
157
154
|
// Workaround Firefox 16:9 ratio https://bugzilla.mozilla.org/show_bug.cgi?id=1193640
|
|
158
|
-
await
|
|
159
|
-
video: { height: 720 },
|
|
160
|
-
});
|
|
155
|
+
await track.applyConstraints({ height: 720 });
|
|
161
156
|
}
|
|
162
157
|
}
|
|
163
158
|
break;
|
|
@@ -165,11 +160,7 @@ const adjustResolution = async (media, features, processingSize) => {
|
|
|
165
160
|
case 'none': {
|
|
166
161
|
if (constraints.height &&
|
|
167
162
|
constraints.height !== videoSettings?.height) {
|
|
168
|
-
await
|
|
169
|
-
video: {
|
|
170
|
-
height: constraints.height,
|
|
171
|
-
},
|
|
172
|
-
});
|
|
163
|
+
await track.applyConstraints({ height: constraints.height });
|
|
173
164
|
}
|
|
174
165
|
break;
|
|
175
166
|
}
|
|
@@ -187,7 +178,7 @@ export const createVideoStreamProcess = ({ trackProcessorAPI = () => 'stream', p
|
|
|
187
178
|
//backgroundBlurAmount,
|
|
188
179
|
videoSegmentation,
|
|
189
180
|
//edgeBlurAmount,
|
|
190
|
-
foregroundThreshold, backgroundImageUrl, maskCombineRatio, edgeBlurAmount,
|
|
181
|
+
foregroundThreshold, backgroundImageUrl, maskCombineRatio, edgeBlurAmount, label = PROCESSOR_LABELS.VideoProcessor, ...options }) => {
|
|
191
182
|
const videoSegmentationModel = options.videoSegmentationModel ?? 'selfie';
|
|
192
183
|
const segmenter = options.segmenters[videoSegmentationModel];
|
|
193
184
|
if (!segmenter) {
|
|
@@ -206,7 +197,7 @@ foregroundThreshold, backgroundImageUrl, maskCombineRatio, edgeBlurAmount, scope
|
|
|
206
197
|
backgroundImageUrl,
|
|
207
198
|
maskCombineRatio,
|
|
208
199
|
});
|
|
209
|
-
const proxy = proxyWithLog(logger,
|
|
200
|
+
const proxy = proxyWithLog(logger, label);
|
|
210
201
|
let _videoProcessor = undefined;
|
|
211
202
|
const props = {
|
|
212
203
|
videoSegmentationModel,
|
|
@@ -236,127 +227,100 @@ foregroundThreshold, backgroundImageUrl, maskCombineRatio, edgeBlurAmount, scope
|
|
|
236
227
|
maskCombineRatio,
|
|
237
228
|
hasInitialized: options.hasInitializedDeps ?? false,
|
|
238
229
|
};
|
|
239
|
-
return async (
|
|
240
|
-
const
|
|
241
|
-
const features = updateFeatureProps(media.constraints?.video, props);
|
|
230
|
+
return async (prevMediaTrack) => {
|
|
231
|
+
const features = updateFeatureProps(prevMediaTrack.getConstraints(), props);
|
|
242
232
|
const shouldEnabled = shouldEnable();
|
|
243
|
-
if (!shouldEnabled || !
|
|
244
|
-
logger.debug({
|
|
245
|
-
return
|
|
233
|
+
if (!shouldEnabled || !prevMediaTrack.track) {
|
|
234
|
+
logger.debug({ label, features, shouldEnabled }, 'Video processing is skipped');
|
|
235
|
+
return prevMediaTrack;
|
|
246
236
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
237
|
+
if (!props.hasInitialized) {
|
|
238
|
+
await props.videoProcessor().open();
|
|
239
|
+
props.hasInitialized = true;
|
|
240
|
+
}
|
|
241
|
+
applyFeatures(props.transformer, features);
|
|
242
|
+
await adjustResolution(prevMediaTrack, features, {
|
|
243
|
+
width: processingWidth,
|
|
244
|
+
height: processingHeight,
|
|
245
|
+
});
|
|
246
|
+
const model = features.videoSegmentationModel &&
|
|
247
|
+
props.segmenters[features.videoSegmentationModel];
|
|
248
|
+
if (features.videoSegmentationModel &&
|
|
249
|
+
features.videoSegmentationModel !==
|
|
250
|
+
props.transformer.segmenter.modelAsset.modelName &&
|
|
251
|
+
model) {
|
|
252
|
+
props.transformer.segmenter = model;
|
|
253
|
+
}
|
|
254
|
+
const stream = await props
|
|
255
|
+
.videoProcessor()
|
|
256
|
+
// FIXME: Change process to accept MediaStreamTrack instead of MediaStream
|
|
257
|
+
.process(new MediaStream([prevMediaTrack.track]));
|
|
258
|
+
const track = stream.getVideoTracks().at(0);
|
|
259
|
+
assert(track, 'Video track should be there');
|
|
260
|
+
// Inherit contentHint from previous track
|
|
261
|
+
track.contentHint = prevMediaTrack.track.contentHint;
|
|
262
|
+
const release = async () => {
|
|
263
|
+
props.videoProcessor().close();
|
|
264
|
+
await prevMediaTrack.release();
|
|
265
|
+
_videoProcessor = undefined;
|
|
266
|
+
props.hasInitialized = false;
|
|
267
|
+
};
|
|
268
|
+
const mute = (mute) => {
|
|
269
|
+
props.transformer.effects = mute
|
|
270
|
+
? 'none'
|
|
271
|
+
: props.videoSegmentation ?? 'none';
|
|
272
|
+
prevMediaTrack.mute(mute);
|
|
273
|
+
muteStreamTrack(stream)(mute, 'video');
|
|
274
|
+
};
|
|
275
|
+
return createMediaTrack({
|
|
276
|
+
label,
|
|
277
|
+
kind: 'videoinput',
|
|
278
|
+
constraints: prevMediaTrack.getConstraints(),
|
|
279
|
+
previousMediaTrack: prevMediaTrack,
|
|
280
|
+
input: prevMediaTrack.input,
|
|
281
|
+
expectedInput: prevMediaTrack.expectedInput,
|
|
282
|
+
track,
|
|
283
|
+
mute,
|
|
284
|
+
release,
|
|
285
|
+
applyConstraints: async (constraints) => {
|
|
286
|
+
if (isEmpty(constraints)) {
|
|
285
287
|
return;
|
|
286
288
|
}
|
|
287
|
-
const features = updateFeatureProps(constraints
|
|
288
|
-
logger.debug({
|
|
289
|
+
const features = updateFeatureProps(constraints, props);
|
|
290
|
+
logger.debug({ label, constraints: constraints, features }, 'apply video constraints');
|
|
289
291
|
if (isEmpty(features)) {
|
|
290
292
|
return;
|
|
291
293
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
props.transformer.segmenter = model;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
catch (error) {
|
|
307
|
-
if (error instanceof Error) {
|
|
308
|
-
logger.error({
|
|
309
|
-
scope,
|
|
310
|
-
constraints: constraints.video,
|
|
311
|
-
features,
|
|
312
|
-
error,
|
|
313
|
-
}, 'failed to apply video constraints');
|
|
314
|
-
options.onError?.(error);
|
|
315
|
-
}
|
|
294
|
+
applyFeatures(props.transformer, features);
|
|
295
|
+
await adjustResolution(prevMediaTrack, features, {
|
|
296
|
+
width: processingWidth,
|
|
297
|
+
height: processingHeight,
|
|
298
|
+
});
|
|
299
|
+
const model = features.videoSegmentationModel &&
|
|
300
|
+
props.segmenters[features.videoSegmentationModel];
|
|
301
|
+
if (model &&
|
|
302
|
+
features.videoSegmentationModel !==
|
|
303
|
+
props.transformer.segmenter.modelAsset.modelName) {
|
|
304
|
+
props.transformer.segmenter = model;
|
|
316
305
|
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
backgroundImageUrl: transformer.backgroundImageUrl,
|
|
337
|
-
videoSegmentationModel: transformer.segmenter.modelAsset.modelName,
|
|
338
|
-
pan: props.pan,
|
|
339
|
-
tilt: props.tilt,
|
|
340
|
-
zoom: props.zoom,
|
|
341
|
-
contentHint,
|
|
342
|
-
};
|
|
343
|
-
const settings = {
|
|
344
|
-
audio,
|
|
345
|
-
video: video.map(settings => ({
|
|
346
|
-
...settings,
|
|
347
|
-
...videoSettings,
|
|
348
|
-
})),
|
|
349
|
-
};
|
|
350
|
-
logger.debug({ scope, settings: videoSettings }, 'get video processor settings');
|
|
351
|
-
return settings;
|
|
352
|
-
},
|
|
353
|
-
}));
|
|
354
|
-
}
|
|
355
|
-
catch (e) {
|
|
356
|
-
if (e instanceof Error) {
|
|
357
|
-
options.onError?.(e);
|
|
358
|
-
}
|
|
359
|
-
return media;
|
|
360
|
-
}
|
|
306
|
+
},
|
|
307
|
+
getSettings: () => {
|
|
308
|
+
const contentHint = track.contentHint ?? '';
|
|
309
|
+
return {
|
|
310
|
+
videoSegmentation: transformer.effects,
|
|
311
|
+
foregroundThreshold: transformer.foregroundThreshold,
|
|
312
|
+
// Background blur amount is a function of height
|
|
313
|
+
backgroundBlurAmount: props.backgroundBlurAmount,
|
|
314
|
+
edgeBlurAmount: transformer.edgeBlurAmount,
|
|
315
|
+
maskCombineRatio: transformer.maskCombineRatio,
|
|
316
|
+
backgroundImageUrl: transformer.backgroundImageUrl,
|
|
317
|
+
videoSegmentationModel: transformer.segmenter.modelAsset.modelName,
|
|
318
|
+
pan: props.pan,
|
|
319
|
+
tilt: props.tilt,
|
|
320
|
+
zoom: props.zoom,
|
|
321
|
+
contentHint,
|
|
322
|
+
};
|
|
323
|
+
},
|
|
324
|
+
});
|
|
361
325
|
};
|
|
362
326
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pexip/media",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "19.1.0",
|
|
4
4
|
"description": "Home for media related stuff",
|
|
5
5
|
"homepage": "https://gitlab.com/pexip/zoo",
|
|
6
6
|
"bugs": "https://gitlab.com/pexip/zoo/issues",
|
|
@@ -33,13 +33,14 @@
|
|
|
33
33
|
"typecheck": "yarn tsc --noEmit -p ."
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@pexip/media-control": "
|
|
37
|
-
"@pexip/media-processor": "
|
|
36
|
+
"@pexip/media-control": "19.1.0",
|
|
37
|
+
"@pexip/media-processor": "19.1.0",
|
|
38
38
|
"@pexip/signal": "16.7.1",
|
|
39
|
-
"@pexip/utils": "16.
|
|
39
|
+
"@pexip/utils": "16.13.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@
|
|
42
|
+
"@jest/globals": "^29.7.0",
|
|
43
|
+
"@pexip/bundler": "17.1.0",
|
|
43
44
|
"@swc/core": "^1.4.6",
|
|
44
45
|
"@swc/jest": "^0.2.36",
|
|
45
46
|
"jest": "^29.5.12",
|