@pexip/media 17.4.0 → 18.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 CHANGED
@@ -1,5 +1,41 @@
1
1
  # @pexip/media
2
2
 
3
+ ## 18.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - bfb8058: Use the lastest version of media-processor
8
+
9
+ ### Minor Changes
10
+
11
+ - 12fac2c: Fis VideoProcessor Leak
12
+
13
+ Closes https://gitlab.com/pexip/zoo/-/issues/4649
14
+
15
+ ### Patch Changes
16
+
17
+ - d246fbf: ```ts await props.media.release(); cleanUpMainSubscription();
18
+
19
+ ```
20
+
21
+ are done in the `cleanup` function and removed from `applyChanges` and `revertChanges`. Cleanup is removed from those functions and needs to be called explicitly.
22
+ ```
23
+
24
+ - Updated dependencies [bfb8058]
25
+ - Updated dependencies [79d02d8]
26
+ - Updated dependencies [0a5a136]
27
+ - Updated dependencies [6b75621]
28
+ - Updated dependencies [218a2c0]
29
+ - Updated dependencies [bfb8058]
30
+ - Updated dependencies [bfb8058]
31
+ - Updated dependencies [9bcfc68]
32
+ - Updated dependencies [1543a66]
33
+ - Updated dependencies [68e8ec1]
34
+ - @pexip/media-processor@18.0.0
35
+ - @pexip/media-control@18.0.0
36
+ - @pexip/utils@16.11.0
37
+ - @pexip/signal@16.7.0
38
+
3
39
  ## 17.4.0
4
40
 
5
41
  ### Minor Changes
@@ -49,6 +49,7 @@ export interface PreviewStreamController {
49
49
  updateVideoInput(input: PreviewInput): void;
50
50
  applyChanges(force?: boolean): Promise<void>;
51
51
  revertChanges(): Promise<void>;
52
+ cleanup: () => Promise<void>;
52
53
  onMediaChanged(callback: EventCallback<Media>): Unsubscribe;
53
54
  onAudioInputChanged(callback: EventCallback<PreviewInput>): Unsubscribe;
54
55
  onVideoInputChanged(callback: EventCallback<PreviewInput>): Unsubscribe;
@@ -246,8 +246,10 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
246
246
  props.updatingPreview = false;
247
247
  }
248
248
  };
249
- const cleanup = () => {
249
+ const cleanup = async () => {
250
250
  logger.debug('Cleanup preview controller');
251
+ await props.media.release();
252
+ cleanUpMainSubscription();
251
253
  props.audioInput = undefined;
252
254
  props.videoInput = undefined;
253
255
  onEnded?.();
@@ -261,8 +263,6 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
261
263
  const applyChanges = async (force = false) => {
262
264
  const { audioInput, videoInput } = props;
263
265
  const { audio: [previewAudio], video: [previewVideo], } = props.media.getSettings();
264
- await props.media.release();
265
- cleanUpMainSubscription();
266
266
  if (force || hasChanges()) {
267
267
  const audioSettings = extractFeaturesToConstraints(AUDIO_SETTINGS_KEYS, previewAudio);
268
268
  const videoSettings = extractFeaturesToConstraints(VIDEO_SETTINGS_KEYS, previewVideo);
@@ -286,11 +286,8 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
286
286
  throw error;
287
287
  }
288
288
  }
289
- cleanup();
290
289
  };
291
290
  const revertChanges = async () => {
292
- await props.media.release();
293
- cleanUpMainSubscription();
294
291
  const mainMedia = getCurrentMedia();
295
292
  const mainStream = mainMedia?.stream;
296
293
  if (mainMedia && mainStream) {
@@ -323,7 +320,6 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
323
320
  }
324
321
  }
325
322
  }
326
- cleanup();
327
323
  };
328
324
  const updateInput = (hasChanged, updater) => (input) => {
329
325
  if (hasChanged(input)) {
@@ -371,5 +367,6 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
371
367
  onUpdatingMain: toEvenHandler('updatingMain'),
372
368
  applyChanges,
373
369
  revertChanges,
370
+ cleanup,
374
371
  };
375
372
  };
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { InputConstraintSet, MediaDeviceInfoLike, MediaDeviceRequest } from '@pexip/media-control';
2
- import type { RenderParams, RenderEffects, Segmenter, SegmentationModel } from '@pexip/media-processor';
2
+ import type { RendererOptions, RenderEffects, Segmenter, SegmentationModel } from '@pexip/media-processor';
3
3
  import type { Signal } from '@pexip/signal';
4
4
  export type Unsubscribe = () => void;
5
5
  export interface ExtendedMediaTrackSettings extends MediaTrackSettings {
@@ -9,9 +9,9 @@ export interface ExtendedMediaTrackSettings extends MediaTrackSettings {
9
9
  vad?: boolean;
10
10
  asd?: boolean;
11
11
  backgroundBlurAmount?: number;
12
- bgImageUrl?: string;
12
+ backgroundImageUrl?: string;
13
13
  edgeBlurAmount?: number;
14
- flipHorizontal?: boolean;
14
+ maskCombineRatio?: number;
15
15
  foregroundThreshold?: number;
16
16
  videoSegmentation?: RenderEffects;
17
17
  videoSegmentationModel?: SegmentationModel;
@@ -417,18 +417,16 @@ export interface MediaController {
417
417
  */
418
418
  tryAndGetUserMedia: (constraints: MediaDeviceRequest) => void;
419
419
  }
420
- export type VideoRenderParams = Omit<RenderParams, 'backgroundImage' | 'effects'> & {
420
+ export type VideoRenderParams = Omit<RendererOptions, 'effects'> & {
421
421
  /**
422
422
  * Target frame rate for the video segmentation
423
423
  */
424
424
  frameRate: number;
425
- /**
426
- * Default background image URL for overlay effects
427
- */
428
- bgImageUrl: string;
429
425
  /**
430
426
  * Render Effects
431
427
  */
428
+ width: number;
429
+ height: number;
432
430
  videoSegmentation: RenderEffects;
433
431
  pan?: boolean;
434
432
  tilt?: boolean;
@@ -473,9 +471,9 @@ export declare enum DeniedDevices {
473
471
  Camera = "camera",
474
472
  Both = "microphone-and-camera"
475
473
  }
476
- export interface Segmenters {
477
- mediapipeSelfie: Segmenter;
478
- }
474
+ export type Segmenters = {
475
+ [Property in SegmentationModel]: Segmenter;
476
+ };
479
477
  /**
480
478
  * Audio content hints are only applicable when the MediaStreamTrack contains an
481
479
  * audio track
package/dist/utils.d.ts CHANGED
@@ -93,7 +93,7 @@ export declare const wrapToJSON: (media: Media) => Media;
93
93
  * @param percentage - The percentage of image height to calculate the blur
94
94
  * kernel size
95
95
  * @param height - The image height
96
- * @param max - The max kernel size
96
+ * @param max - The upper bound
97
97
  *
98
98
  * @returns blur kernel size
99
99
  */
package/dist/utils.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { applyConstraints, createTrackDevicesChanges, extractConstraintsWithKeys, findDeviceFromConstraints, muteStreamTrack, relaxInputConstraint, stopMediaStream, isAudioInput, isVideoInput, } from '@pexip/media-control';
2
+ import { calculateMaxBlurPass } from '@pexip/media-processor';
2
3
  import { UserMediaStatus } from './types';
3
4
  import { isOverConstrained } from './status';
4
5
  import { isMedia } from './typeGuard';
@@ -275,8 +276,8 @@ export const VIDEO_SETTINGS_KEYS = [
275
276
  'foregroundThreshold',
276
277
  'backgroundBlurAmount',
277
278
  'edgeBlurAmount',
278
- 'flipHorizontal',
279
- 'bgImageUrl',
279
+ 'maskCombineRatio',
280
+ 'backgroundImageUrl',
280
281
  'width',
281
282
  'height',
282
283
  'contentHint',
@@ -337,15 +338,15 @@ export const wrapToJSON = (media) => {
337
338
  * @param percentage - The percentage of image height to calculate the blur
338
339
  * kernel size
339
340
  * @param height - The image height
340
- * @param max - The max kernel size
341
+ * @param max - The upper bound
341
342
  *
342
343
  * @returns blur kernel size
343
344
  */
344
- export const getBlurKernelSize = (percentage, height, max = 20) => {
345
+ export const getBlurKernelSize = (percentage, height, max = calculateMaxBlurPass(height)) => {
345
346
  if (height <= 0 || percentage <= 0 || max <= 0) {
346
347
  return 0;
347
348
  }
348
- return Math.min(Math.ceil(percentage * 0.001 * height), max);
349
+ return Math.min(Math.ceil(percentage * 0.01 * max), max);
349
350
  };
350
351
  /**
351
352
  * Apply the content hint to the track
@@ -1,10 +1,10 @@
1
- import type { VideoProcessor, SegmentationModel, SegmentationTransform } from '@pexip/media-processor';
1
+ import type { VideoProcessor, SegmentationTransform, SegmentationModel } from '@pexip/media-processor';
2
2
  import type { MediaDeviceRequest } from '@pexip/media-control';
3
3
  import type { Process, Media, VideoRenderParams, Segmenters, VideoStreamTrackProcessorAPIs, VideoContentHint } from './types';
4
4
  interface ProcessorDeps {
5
5
  videoProcessor?: () => VideoProcessor;
6
6
  transformer?: SegmentationTransform;
7
- segmenters: Segmenters;
7
+ segmenters: Partial<Segmenters>;
8
8
  videoSegmentationModel?: SegmentationModel;
9
9
  }
10
10
  interface VideoStreamProcessOptions extends Partial<VideoRenderParams>, Omit<ProcessorDeps, 'videoProcessor'> {
@@ -33,24 +33,9 @@ interface VideoStreamProcessProps extends Partial<VideoRenderParams>, Required<P
33
33
  hasInitialized: boolean;
34
34
  contentHint?: VideoContentHint;
35
35
  }
36
- declare const FEATURE_KEYS: [
37
- 'backgroundBlurAmount',
38
- 'bgImageUrl',
39
- 'edgeBlurAmount',
40
- 'flipHorizontal',
41
- 'foregroundThreshold',
42
- 'frameRate',
43
- 'videoSegmentation',
44
- 'videoSegmentationModel',
45
- 'width',
46
- 'height',
47
- 'pan',
48
- 'tilt',
49
- 'zoom',
50
- 'contentHint'
51
- ];
36
+ declare const FEATURE_KEYS: readonly ["backgroundBlurAmount", "backgroundImageUrl", "maskCombineRatio", "edgeBlurAmount", "foregroundThreshold", "frameRate", "videoSegmentation", "videoSegmentationModel", "width", "height", "pan", "tilt", "zoom", "contentHint"];
52
37
  type FeaturePropKeys = (typeof FEATURE_KEYS)[number];
53
38
  type FeatureProps = Pick<Partial<VideoStreamProcessProps>, FeaturePropKeys>;
54
39
  export declare const updateFeatureProps: (constraints: MediaDeviceRequest['video'], props: FeatureProps) => FeatureProps;
55
- export declare const createVideoStreamProcess: ({ trackProcessorAPI, processingWidth, processingHeight, shouldEnable, frameRate, videoSegmentation, foregroundThreshold, bgImageUrl, flipHorizontal, edgeBlurAmount, scope, ...options }: VideoStreamProcessOptions) => Process<Promise<Media>>;
40
+ export declare const createVideoStreamProcess: ({ trackProcessorAPI, processingWidth, processingHeight, shouldEnable, frameRate, videoSegmentation, foregroundThreshold, backgroundImageUrl, maskCombineRatio, edgeBlurAmount, scope, ...options }: VideoStreamProcessOptions) => Process<Promise<Media>>;
56
41
  export {};
@@ -6,9 +6,9 @@ import { logger, proxyWithLog } from './logger';
6
6
  import { isVideoContentHint } from './typeGuard';
7
7
  const FEATURE_KEYS = [
8
8
  'backgroundBlurAmount',
9
- 'bgImageUrl',
9
+ 'backgroundImageUrl',
10
+ 'maskCombineRatio',
10
11
  'edgeBlurAmount',
11
- 'flipHorizontal',
12
12
  'foregroundThreshold',
13
13
  'frameRate',
14
14
  'videoSegmentation',
@@ -49,7 +49,7 @@ export const updateFeatureProps = (constraints, props) => {
49
49
  }
50
50
  return accm;
51
51
  }
52
- case 'bgImageUrl': {
52
+ case 'backgroundImageUrl': {
53
53
  const [[feature] = []] = extracted[key];
54
54
  if (feature) {
55
55
  props[key] = feature;
@@ -62,6 +62,7 @@ export const updateFeatureProps = (constraints, props) => {
62
62
  case 'frameRate':
63
63
  case 'foregroundThreshold':
64
64
  case 'edgeBlurAmount':
65
+ case 'maskCombineRatio':
65
66
  case 'backgroundBlurAmount': {
66
67
  const [feature] = extracted[key];
67
68
  if (feature !== undefined) {
@@ -76,7 +77,6 @@ export const updateFeatureProps = (constraints, props) => {
76
77
  }
77
78
  return accm;
78
79
  }
79
- case 'flipHorizontal':
80
80
  case 'pan':
81
81
  case 'tilt':
82
82
  case 'zoom': {
@@ -90,16 +90,12 @@ export const updateFeatureProps = (constraints, props) => {
90
90
  }
91
91
  }, {});
92
92
  };
93
- const applyFeatures = async (transformer, features) => {
94
- // bgImageUrl should be applied before `videoSegmentation` effects since
95
- // backgroundImage is the prerequisite of the `overlay` effects
96
- if (features.bgImageUrl) {
97
- await transformer.loadBackgroundImage(features.bgImageUrl);
98
- }
93
+ const applyFeatures = (transformer, features) => {
99
94
  Object.keys(features).forEach(key => {
100
95
  const k = key;
101
96
  switch (k) {
102
97
  case 'edgeBlurAmount':
98
+ case 'maskCombineRatio':
103
99
  case 'foregroundThreshold': {
104
100
  const value = features[k];
105
101
  if (value !== undefined) {
@@ -121,11 +117,10 @@ const applyFeatures = async (transformer, features) => {
121
117
  }
122
118
  return;
123
119
  }
124
- case 'flipHorizontal': {
120
+ case 'backgroundImageUrl': {
125
121
  const value = features[k];
126
- if (value !== undefined &&
127
- value !== transformer.flipHorizontal) {
128
- transformer.flipHorizontal = value;
122
+ if (value && value !== transformer.backgroundImageUrl) {
123
+ transformer.backgroundImageUrl = value;
129
124
  }
130
125
  return;
131
126
  }
@@ -192,39 +187,53 @@ export const createVideoStreamProcess = ({ trackProcessorAPI = () => 'stream', p
192
187
  //backgroundBlurAmount,
193
188
  videoSegmentation,
194
189
  //edgeBlurAmount,
195
- foregroundThreshold, bgImageUrl, flipHorizontal, edgeBlurAmount, scope = 'media', ...options }) => {
196
- const videoSegmentationModel = options.videoSegmentationModel ?? 'mediapipeSelfie';
190
+ foregroundThreshold, backgroundImageUrl, maskCombineRatio, edgeBlurAmount, scope = 'media', ...options }) => {
191
+ const videoSegmentationModel = options.videoSegmentationModel ?? 'selfie';
192
+ const segmenter = options.segmenters[videoSegmentationModel];
193
+ if (!segmenter) {
194
+ throw new Error('Segmenter is undefined');
195
+ }
197
196
  const backgroundBlurAmount = options.backgroundBlurAmount &&
198
197
  getBlurKernelSize(options.backgroundBlurAmount, processingHeight);
199
198
  const transformer = options.transformer ??
200
- createCanvasTransform(options.segmenters[videoSegmentationModel], {
199
+ createCanvasTransform(segmenter, {
201
200
  width: processingWidth,
202
201
  height: processingHeight,
203
202
  effects: videoSegmentation,
204
203
  foregroundThreshold,
205
204
  backgroundBlurAmount,
206
205
  edgeBlurAmount,
207
- flipHorizontal,
206
+ backgroundImageUrl,
207
+ maskCombineRatio,
208
208
  });
209
209
  const proxy = proxyWithLog(logger, scope);
210
+ let _videoProcessor = undefined;
210
211
  const props = {
211
212
  videoSegmentationModel,
212
213
  segmenters: {
213
- mediapipeSelfie: proxy(options.segmenters.mediapipeSelfie, 'Segmenter'),
214
+ selfie: options.segmenters.selfie &&
215
+ proxy(options.segmenters.selfie, 'Segmenter'),
216
+ deeplabV3: options.segmenters.deeplabV3 &&
217
+ proxy(options.segmenters.deeplabV3, 'Segmenter'),
214
218
  },
215
219
  transformer: proxy(transformer, 'Transformer'),
216
- videoProcessor: () => proxy(createVideoProcessor([transformer], getTrackProcessor(trackProcessorAPI() === 'stream', {
217
- width: processingWidth,
218
- height: processingHeight,
219
- frameRate,
220
- })), 'VideoProcessor'),
220
+ videoProcessor: () => {
221
+ if (!_videoProcessor) {
222
+ _videoProcessor = proxy(createVideoProcessor([transformer], getTrackProcessor(trackProcessorAPI() === 'stream', {
223
+ width: processingWidth,
224
+ height: processingHeight,
225
+ frameRate,
226
+ })), 'VideoProcessor');
227
+ }
228
+ return _videoProcessor;
229
+ },
221
230
  videoSegmentation,
222
231
  backgroundBlurAmount,
223
232
  edgeBlurAmount,
224
233
  foregroundThreshold,
225
234
  frameRate,
226
- bgImageUrl,
227
- flipHorizontal,
235
+ backgroundImageUrl,
236
+ maskCombineRatio,
228
237
  hasInitialized: options.hasInitializedDeps ?? false,
229
238
  };
230
239
  return async (mediaP) => {
@@ -240,21 +249,24 @@ foregroundThreshold, bgImageUrl, flipHorizontal, edgeBlurAmount, scope = 'media'
240
249
  await props.videoProcessor().open();
241
250
  props.hasInitialized = true;
242
251
  }
243
- await applyFeatures(props.transformer, features);
252
+ applyFeatures(props.transformer, features);
244
253
  await adjustResolution(media, features, {
245
254
  width: processingWidth,
246
255
  height: processingHeight,
247
256
  });
257
+ const model = features.videoSegmentationModel &&
258
+ props.segmenters[features.videoSegmentationModel];
248
259
  if (features.videoSegmentationModel &&
249
260
  features.videoSegmentationModel !==
250
- props.transformer.segmenter.model) {
251
- props.transformer.segmenter =
252
- props.segmenters[features.videoSegmentationModel];
261
+ props.transformer.segmenter.modelAsset.modelName &&
262
+ model) {
263
+ props.transformer.segmenter = model;
253
264
  }
254
265
  const stream = await props.videoProcessor().process(media.stream);
255
266
  const release = async () => {
256
267
  props.videoProcessor().close();
257
268
  await media.release();
269
+ _videoProcessor = undefined;
258
270
  props.hasInitialized = false;
259
271
  };
260
272
  const muteAudio = (mute) => {
@@ -278,16 +290,17 @@ foregroundThreshold, bgImageUrl, flipHorizontal, edgeBlurAmount, scope = 'media'
278
290
  return;
279
291
  }
280
292
  try {
281
- await applyFeatures(props.transformer, features);
293
+ applyFeatures(props.transformer, features);
282
294
  await adjustResolution(media, features, {
283
295
  width: processingWidth,
284
296
  height: processingHeight,
285
297
  });
286
- if (features.videoSegmentationModel &&
298
+ const model = features.videoSegmentationModel &&
299
+ props.segmenters[features.videoSegmentationModel];
300
+ if (model &&
287
301
  features.videoSegmentationModel !==
288
- props.transformer.segmenter.model) {
289
- props.transformer.segmenter =
290
- props.segmenters[features.videoSegmentationModel];
302
+ props.transformer.segmenter.modelAsset.modelName) {
303
+ props.transformer.segmenter = model;
291
304
  }
292
305
  }
293
306
  catch (error) {
@@ -319,9 +332,9 @@ foregroundThreshold, bgImageUrl, flipHorizontal, edgeBlurAmount, scope = 'media'
319
332
  // Background blur amount is a function of height
320
333
  backgroundBlurAmount: props.backgroundBlurAmount,
321
334
  edgeBlurAmount: transformer.edgeBlurAmount,
322
- flipHorizontal: transformer.flipHorizontal,
323
- bgImageUrl: transformer.backgroundImage && props.bgImageUrl,
324
- videoSegmentationModel: transformer.segmenter.model,
335
+ maskCombineRatio: transformer.maskCombineRatio,
336
+ backgroundImageUrl: transformer.backgroundImageUrl,
337
+ videoSegmentationModel: transformer.segmenter.modelAsset.modelName,
325
338
  pan: props.pan,
326
339
  tilt: props.tilt,
327
340
  zoom: props.zoom,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pexip/media",
3
- "version": "17.4.0",
3
+ "version": "18.0.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",
@@ -32,14 +32,14 @@
32
32
  "typecheck": "yarn tsc --noEmit -p ."
33
33
  },
34
34
  "dependencies": {
35
- "@pexip/media-control": "17.4.0",
36
- "@pexip/media-processor": "17.4.0",
35
+ "@pexip/media-control": "18.0.0",
36
+ "@pexip/media-processor": "18.0.0",
37
37
  "@pexip/signal": "16.7.0",
38
- "@pexip/utils": "16.10.0"
38
+ "@pexip/utils": "16.11.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@jest/globals": "^29.7.0",
42
- "@pexip/bundler": "16.6.0",
42
+ "@pexip/bundler": "16.6.1",
43
43
  "prettier": "^3.2.5",
44
44
  "typescript": "~5.3.0"
45
45
  },