@pexip/media 20.3.5 → 21.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,65 @@
1
1
  # @pexip/media
2
2
 
3
+ ## 21.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 2606fa4: Fix blur normalize function
8
+
9
+ ### Minor Changes
10
+
11
+ - 1786b2d: Get correct stream status when only one input kind is requested
12
+ - a004e3c: Introduce new `renderParams` that can be applied via the video
13
+ constraints:
14
+ - `backgroundThreshold` - Segmentation threshold for classifying pixels as
15
+ background (lower values include more background).
16
+ - `downSampleFactor` - Factor for downsampling the video frame before
17
+ processing (higher values improve performance but reduce quality).
18
+ - `lightWrapBlurAmount` - Amount of blur applied to the light wrap effect
19
+ around the subject's edges.
20
+ - `lightWrapEdgeBand` - Width of the edge band in pixels where the light wrap
21
+ effect is applied.
22
+ - `lightWrapIntensity` - Intensity/strength of the light wrap effect (0.0 to
23
+ 1.0).
24
+ - `lightWrapTightness` - Controls how tightly the light wrap effect hugs the
25
+ subject's edges.
26
+ - `morphDilateRadiusPx` - Radius in pixels for morphological dilation
27
+ operation on the segmentation mask.
28
+ - `morphErodeRadiusPx` - Radius in pixels for morphological erosion operation
29
+ on the segmentation mask.
30
+ - `morphPass` - Number of morphological filter passes to apply for mask
31
+ refinement.
32
+ - `personCenterX` - X-coordinate (0.0 to 1.0) of the expected center position
33
+ of the person in the frame for segmentation optimization.
34
+ - `personCenterY` - Y-coordinate (0.0 to 1.0) of the expected center position
35
+ of the person in the frame for segmentation optimization.
36
+ - `sigmaRangeHi` - Bilateral filter: upper bound of sigma in color space for
37
+ edge-preserving smoothing.
38
+ - `sigmaRangeLo` - Bilateral filter: lower bound of sigma in color space for
39
+ edge-preserving smoothing.
40
+ - `sigmaSpace` - Bilateral filter: sigma in pixel space for mask edge
41
+ snapping.
42
+
43
+ ### Patch Changes
44
+
45
+ - c10f5b6: Normalise initial `lightWrapBlurAmount` and `edgeBlurAmount` values
46
+ - Updated dependencies [24bc717]
47
+ - Updated dependencies [1786b2d]
48
+ - Updated dependencies [872971f]
49
+ - Updated dependencies [2e895ac]
50
+ - Updated dependencies [3963c2e]
51
+ - Updated dependencies [1fff975]
52
+ - Updated dependencies [3963c2e]
53
+ - Updated dependencies [762ebd2]
54
+ - Updated dependencies [4eaf9d7]
55
+ - Updated dependencies [4d2da34]
56
+ - Updated dependencies [a004e3c]
57
+ - Updated dependencies [4cc4701]
58
+ - @pexip/media-processor@21.0.0
59
+ - @pexip/media-control@21.0.0
60
+ - @pexip/utils@17.2.0
61
+ - @pexip/signal@16.9.4
62
+
3
63
  ## 20.3.5
4
64
 
5
65
  ### Patch Changes
@@ -400,5 +400,7 @@ await mediaController.media.applyConstraints({
400
400
  | [isAudioContentHint](functions/isAudioContentHint.mdx) | - |
401
401
  | [isVideoContentHint](functions/isVideoContentHint.mdx) | - |
402
402
  | [isVideoStreamTrackProcessorAPIs](functions/isVideoStreamTrackProcessorAPIs.mdx) | - |
403
+ | [getBlurKernelSize](functions/getBlurKernelSize.mdx) | A function to get the blur kernel size of image height based on Kawase blur algorithm |
404
+ | [getTentBlurSize](functions/getTentBlurSize.mdx) | A function to get the Tent blur size of image height |
403
405
  | [applyContentHint](functions/applyContentHint.mdx) | Apply the content hint to the track |
404
406
  | [createVideoStreamProcess](functions/createVideoStreamProcess.mdx) | - |
@@ -0,0 +1,25 @@
1
+ ```ts
2
+ function getBlurKernelSize(
3
+ norm,
4
+ heightRef,
5
+ height,
6
+ max): number;
7
+ ```
8
+
9
+ A function to get the blur kernel size of image height based on
10
+ Kawase blur algorithm
11
+
12
+ ## Parameters
13
+
14
+ | Parameter | Type | Description |
15
+ | ------ | ------ | ------ |
16
+ | `norm` | `number` | [0-100] The normalized blur amount |
17
+ | `heightRef` | `number` | The reference image height |
18
+ | `height` | `number` | The current image height |
19
+ | `max` | `number` | The upper bound |
20
+
21
+ ## Returns
22
+
23
+ `number`
24
+
25
+ blur kernel size
@@ -0,0 +1,24 @@
1
+ ```ts
2
+ function getTentBlurSize(
3
+ norm,
4
+ heightRef,
5
+ height,
6
+ max): number;
7
+ ```
8
+
9
+ A function to get the Tent blur size of image height
10
+
11
+ ## Parameters
12
+
13
+ | Parameter | Type | Default value | Description |
14
+ | ------ | ------ | ------ | ------ |
15
+ | `norm` | `number` | `undefined` | [0-100] The normalized blur amount |
16
+ | `heightRef` | `number` | `undefined` | The reference image height |
17
+ | `height` | `number` | `undefined` | The current image height |
18
+ | `max` | `number` | `MAX_TENT_BLUR_SIZE` | The upper bound |
19
+
20
+ ## Returns
21
+
22
+ `number`
23
+
24
+ tent blur size
@@ -1,5 +1,5 @@
1
1
  ```ts
2
- type VideoRenderParams = Omit<RendererOptions, "effects"> & object;
2
+ type VideoRenderParams = Omit<RendererOptions, "effects" | "personCenter"> & object;
3
3
  ```
4
4
 
5
5
  ## Type Declaration
@@ -13,3 +13,5 @@ type VideoRenderParams = Omit<RendererOptions, "effects"> & object;
13
13
  | `pan?` | `boolean` | - |
14
14
  | `tilt?` | `boolean` | - |
15
15
  | `zoom?` | `boolean` | - |
16
+ | `personCenterX?` | `number` | - |
17
+ | `personCenterY?` | `number` | - |
@@ -6,3 +6,4 @@ export declare const PROCESSOR_LABELS: Readonly<{
6
6
  }>;
7
7
  export declare const GET_USER_MEDIA_TIMEOUT_MS = 10000;
8
8
  export declare const DEVICE_CHANGE_DEBOUNCE_TIMEOUT_MS = 500;
9
+ export declare const MAX_TENT_BLUR_SIZE = 9;
package/dist/constants.js CHANGED
@@ -6,3 +6,4 @@ export const PROCESSOR_LABELS = Object.freeze({
6
6
  });
7
7
  export const GET_USER_MEDIA_TIMEOUT_MS = 10000;
8
8
  export const DEVICE_CHANGE_DEBOUNCE_TIMEOUT_MS = 500;
9
+ export const MAX_TENT_BLUR_SIZE = 9;
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export * from './signals';
4
4
  export * from './status';
5
5
  export * from './typeGuard';
6
6
  export * from './types';
7
- export { applyContentHint } from './utils';
7
+ export { applyContentHint, getBlurKernelSize, getTentBlurSize } from './utils';
8
8
  export { createAudioMixingProcess } from './audioMixingProcessor';
9
9
  export { createAudioStreamProcess } from './audioProcessor';
10
10
  export { createMedia } from './media';
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ export * from './signals';
4
4
  export * from './status';
5
5
  export * from './typeGuard';
6
6
  export * from './types';
7
- export { applyContentHint } from './utils';
7
+ export { applyContentHint, getBlurKernelSize, getTentBlurSize } from './utils';
8
8
  export { createAudioMixingProcess } from './audioMixingProcessor';
9
9
  export { createAudioStreamProcess } from './audioProcessor';
10
10
  export { createMedia } from './media';
@@ -373,7 +373,7 @@ export const createPreviewStreamController = ({ getCurrentDevices, getCurrentMed
373
373
  const audioSettings = extractFeaturesToConstraints(AUDIO_SETTINGS_KEYS, previewAudio);
374
374
  // Omit the width & height settings from the preview so that the one
375
375
  // from the main can be applied
376
- const { width: _width, height: _height, ...videoSettings } = extractFeaturesToConstraints(VIDEO_SETTINGS_KEYS, previewVideo);
376
+ const { width: _width, height: _height, edgeBlurAmount: _edgeBlurAmount, lightWrapBlurAmount: _lightWrapBlurAmount, backgroundBlurAmount: _backgroundBlurAmount, ...videoSettings } = extractFeaturesToConstraints(VIDEO_SETTINGS_KEYS, previewVideo);
377
377
  logger.info({ audioInput, videoInput, audioSettings, videoSettings }, 'Apply changes to main');
378
378
  try {
379
379
  await replaceMainStream({
package/dist/types.d.ts CHANGED
@@ -531,7 +531,7 @@ export interface MediaController {
531
531
  */
532
532
  tryAndGetUserMedia: (constraints: MediaDeviceRequest) => void;
533
533
  }
534
- export type VideoRenderParams = Omit<RendererOptions, 'effects'> & {
534
+ export type VideoRenderParams = Omit<RendererOptions, 'effects' | 'personCenter'> & {
535
535
  /**
536
536
  * Target frame rate for the video segmentation
537
537
  */
@@ -545,6 +545,8 @@ export type VideoRenderParams = Omit<RendererOptions, 'effects'> & {
545
545
  pan?: boolean;
546
546
  tilt?: boolean;
547
547
  zoom?: boolean;
548
+ personCenterX?: number;
549
+ personCenterY?: number;
548
550
  };
549
551
  export interface MediaChangesSignals {
550
552
  onAddTrack: Signal<MediaStreamTrack>;
@@ -11,7 +11,10 @@ export declare const toSameDeviceStatus: ({ audio, video, }: {
11
11
  audio: boolean;
12
12
  video: boolean;
13
13
  }) => UserMediaStatus.PermissionsGranted | UserMediaStatus.PermissionsGrantedFallback | UserMediaStatus.PermissionsGrantedFallbackAudioinput | UserMediaStatus.PermissionsGrantedFallbackVideoinput;
14
- export declare const toOnlyDeviceStatus: (kind: "audioinput" | "videoinput", matched: boolean, devices: IndexedDevices) => UserMediaStatus.PermissionsRejectedAudioInput | UserMediaStatus.PermissionsRejectedVideoInput | UserMediaStatus.PermissionsOnlyAudioinput | UserMediaStatus.PermissionsOnlyAudioinputNoVideoDevices | UserMediaStatus.PermissionsOnlyAudioinputFallback | UserMediaStatus.PermissionsOnlyAudioinputFallbackNoVideoDevices | UserMediaStatus.PermissionsOnlyVideoinput | UserMediaStatus.PermissionsOnlyVideoinputNoAudioDevices | UserMediaStatus.PermissionsOnlyVideoinputFallback | UserMediaStatus.PermissionsOnlyVideoinputFallbackNoAudioDevices;
14
+ export declare const toOnlyDeviceStatus: (kind: "audioinput" | "videoinput", streamedRequestedDevices: {
15
+ audio: boolean;
16
+ video: boolean;
17
+ }, devices: IndexedDevices) => UserMediaStatus.PermissionsRejectedAudioInput | UserMediaStatus.PermissionsRejectedVideoInput | UserMediaStatus.PermissionsOnlyAudioinput | UserMediaStatus.PermissionsOnlyAudioinputNoVideoDevices | UserMediaStatus.PermissionsOnlyAudioinputFallback | UserMediaStatus.PermissionsOnlyAudioinputFallbackNoVideoDevices | UserMediaStatus.PermissionsOnlyVideoinput | UserMediaStatus.PermissionsOnlyVideoinputNoAudioDevices | UserMediaStatus.PermissionsOnlyVideoinputFallback | UserMediaStatus.PermissionsOnlyVideoinputFallbackNoAudioDevices;
15
18
  /**
16
19
  * Check if there is any track match with the `kind`
17
20
  */
package/dist/userMedia.js CHANGED
@@ -71,12 +71,16 @@ export const toSameDeviceStatus = ({ audio, video, }) => {
71
71
  }
72
72
  return UserMediaStatus.PermissionsGrantedFallback;
73
73
  };
74
- export const toOnlyDeviceStatus = (kind, matched, devices) => {
75
- const hasAnotherTypeOfAuthorizedDevice = devices.anyAuthorizedDevice(kind === 'audioinput' ? 'videoinput' : 'audioinput');
76
- const hasAnotherTypeOfDevice = devices.size(kind === 'audioinput' ? 'videoinput' : 'audioinput') > 0;
74
+ export const toOnlyDeviceStatus = (kind, streamedRequestedDevices, devices) => {
75
+ const [self, other] = kind === 'audioinput' ? ['audio', 'video'] : ['video', 'audio'];
76
+ const otherKind = kind === 'audioinput' ? 'videoinput' : 'audioinput';
77
+ const matched = streamedRequestedDevices[self];
78
+ const anotherDeviceRequested = streamedRequestedDevices[other];
79
+ const hasAnotherTypeOfAuthorizedDevice = devices.anyAuthorizedDevice(otherKind);
80
+ const hasAnotherTypeOfDevice = devices.size(otherKind) > 0;
77
81
  if (matched) {
78
82
  if (hasAnotherTypeOfDevice) {
79
- if (hasAnotherTypeOfAuthorizedDevice) {
83
+ if (hasAnotherTypeOfAuthorizedDevice || !anotherDeviceRequested) {
80
84
  return kind === 'audioinput'
81
85
  ? UserMediaStatus.PermissionsOnlyAudioinput
82
86
  : UserMediaStatus.PermissionsOnlyVideoinput;
@@ -90,7 +94,7 @@ export const toOnlyDeviceStatus = (kind, matched, devices) => {
90
94
  : UserMediaStatus.PermissionsOnlyVideoinputNoAudioDevices;
91
95
  }
92
96
  if (hasAnotherTypeOfDevice) {
93
- if (hasAnotherTypeOfAuthorizedDevice) {
97
+ if (hasAnotherTypeOfAuthorizedDevice || !anotherDeviceRequested) {
94
98
  return kind === 'audioinput'
95
99
  ? UserMediaStatus.PermissionsOnlyAudioinputFallback
96
100
  : UserMediaStatus.PermissionsOnlyVideoinputFallback;
@@ -421,10 +425,10 @@ export const requestUserMedia = (getCurrentDevices, getMedia = getUserMedia) =>
421
425
  ? constraints.video
422
426
  : false,
423
427
  });
424
- const { audio, video } = isStreamingRequestedDevices(constraints, stream, devices);
425
- const onlyAudioStatus = toOnlyDeviceStatus('audioinput', audio, devices);
426
- const onlyVideoStatus = toOnlyDeviceStatus('videoinput', video, devices);
427
- const status = deriveDeviceStatus(onlyAudioStatus, onlyVideoStatus, toSameDeviceStatus({ audio, video }));
428
+ const streamedRequestedDevices = isStreamingRequestedDevices(constraints, stream, devices);
429
+ const onlyAudioStatus = toOnlyDeviceStatus('audioinput', streamedRequestedDevices, devices);
430
+ const onlyVideoStatus = toOnlyDeviceStatus('videoinput', streamedRequestedDevices, devices);
431
+ const status = deriveDeviceStatus(onlyAudioStatus, onlyVideoStatus, toSameDeviceStatus(streamedRequestedDevices));
428
432
  return [stream, status];
429
433
  }
430
434
  catch (error) {
@@ -521,10 +525,10 @@ export const createGetUserMediaProcess = ({ getCurrentDevices, getUserMedia, sco
521
525
  }));
522
526
  }
523
527
  const relaxedConstraints = {};
524
- if (constraints.audio) {
528
+ if (constraints.audio !== undefined) {
525
529
  relaxedConstraints.audio = relaxInputConstraint('audioinput', constraints.audio, currentDevices);
526
530
  }
527
- if (constraints.video) {
531
+ if (constraints.video !== undefined) {
528
532
  relaxedConstraints.video = relaxInputConstraint('videoinput', constraints.video, currentDevices);
529
533
  }
530
534
  const [stream, status] = await getUserMedia(relaxedConstraints);
package/dist/utils.d.ts CHANGED
@@ -54,17 +54,28 @@ export declare const getSettingsFromKeys: (keys: ExtendedMediaTrackSettingsKey[]
54
54
  export declare const diffSettings: (keysToLookFor: ExtendedMediaTrackSettingsKey[]) => (settingsA: ExtendedMediaTrackSettings | undefined, settingsB: ExtendedMediaTrackSettings | undefined) => ExtendedMediaTrackSettings | undefined;
55
55
  export declare const mergeSettings: (settingsA: ExtendedMediaTrackSettings | undefined, settingsB: ExtendedMediaTrackSettings | undefined) => ExtendedMediaTrackSettings | undefined;
56
56
  /**
57
- * A function to get the blur kernel size of image height
57
+ * A function to get the blur kernel size of image height based on
58
+ * Kawase blur algorithm
58
59
  *
59
- * @param percentage - The percentage of image height to calculate the blur
60
- * kernel size
61
- * @param height - The image height
62
- * @param lowestProcessingHeight - The lowest image height that is processed by the consumer, used to adjust the blur kernel size across different resolutions
60
+ * @param norm - [0-100] The normalized blur amount
61
+ * @param heightRef - The reference image height
62
+ * @param height - The current image height
63
63
  * @param max - The upper bound
64
64
  *
65
65
  * @returns blur kernel size
66
66
  */
67
- export declare const getBlurKernelSize: (percentage: number, height: number, lowestProcessingHeight?: number, max?: number) => number;
67
+ export declare const getBlurKernelSize: (norm: number, heightRef: number, height: number, max?: number) => number;
68
+ /**
69
+ * A function to get the Tent blur size of image height
70
+ *
71
+ * @param norm - [0-100] The normalized blur amount
72
+ * @param heightRef - The reference image height
73
+ * @param height - The current image height
74
+ * @param max - The upper bound
75
+ *
76
+ * @returns tent blur size
77
+ */
78
+ export declare const getTentBlurSize: (norm: number, heightRef: number, height: number, max?: number) => number;
68
79
  /**
69
80
  * Apply the content hint to the track
70
81
  *
package/dist/utils.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { createStreamTrackEventSubscriptions, createTrackDevicesChanges, extractConstraintsWithKeys, findDeviceFromConstraints, relaxInputConstraint, resolveMediaDeviceConstraints, shouldRequestDevice, toMediaDeviceInputKind, } from '@pexip/media-control';
2
- import { calculateMaxBlurPass } from '@pexip/media-processor';
2
+ import { calculateMaxBlurPass, clamping, } from '@pexip/media-processor';
3
3
  import { hasOwn, assert, isEmpty } from '@pexip/utils';
4
4
  import { internalSignals } from './signals';
5
+ import { MAX_TENT_BLUR_SIZE } from './constants';
5
6
  export const makeDeriveDeviceStatus = (constraints) => (audio, video, both) => {
6
7
  if (constraints.audio) {
7
8
  if (constraints.video) {
@@ -684,26 +685,39 @@ export const mergeSettings = (settingsA, settingsB) => {
684
685
  }
685
686
  return { ...settingsA, ...settingsB };
686
687
  };
688
+ const clampNorm = clamping(0, 100);
687
689
  /**
688
- * A function to get the blur kernel size of image height
690
+ * A function to get the blur kernel size of image height based on
691
+ * Kawase blur algorithm
689
692
  *
690
- * @param percentage - The percentage of image height to calculate the blur
691
- * kernel size
692
- * @param height - The image height
693
- * @param lowestProcessingHeight - The lowest image height that is processed by the consumer, used to adjust the blur kernel size across different resolutions
693
+ * @param norm - [0-100] The normalized blur amount
694
+ * @param heightRef - The reference image height
695
+ * @param height - The current image height
694
696
  * @param max - The upper bound
695
697
  *
696
698
  * @returns blur kernel size
697
699
  */
698
- export const getBlurKernelSize = (percentage, height, lowestProcessingHeight, max = calculateMaxBlurPass(height)) => {
699
- if (height <= 0 || percentage <= 0 || max <= 0) {
700
+ export const getBlurKernelSize = (norm, heightRef, height, max = calculateMaxBlurPass(height)) => {
701
+ if (height <= 0 || norm <= 0 || max <= 0 || heightRef <= 0) {
700
702
  return 0;
701
703
  }
702
- const minHeightMaxBlurPass = lowestProcessingHeight
703
- ? calculateMaxBlurPass(lowestProcessingHeight)
704
- : max;
705
- return Math.min(Math.ceil(percentage * 0.01 * max) +
706
- Math.max(0, max - minHeightMaxBlurPass), max);
704
+ return clamping(0, max)(Math.round(clampNorm(norm) * 0.01 * max - Math.log2(heightRef / height)));
705
+ };
706
+ /**
707
+ * A function to get the Tent blur size of image height
708
+ *
709
+ * @param norm - [0-100] The normalized blur amount
710
+ * @param heightRef - The reference image height
711
+ * @param height - The current image height
712
+ * @param max - The upper bound
713
+ *
714
+ * @returns tent blur size
715
+ */
716
+ export const getTentBlurSize = (norm, heightRef, height, max = MAX_TENT_BLUR_SIZE) => {
717
+ if (height <= 0 || norm <= 0 || heightRef <= 0 || max <= 0) {
718
+ return 0;
719
+ }
720
+ return clamping(0, max)(Math.round((norm * 0.01 * max * height) / heightRef));
707
721
  };
708
722
  /**
709
723
  * Apply the content hint to the track
@@ -18,7 +18,7 @@ interface VideoStreamProcessOptions extends Partial<VideoRenderParams>, Omit<Pro
18
18
  * Whether or to enable this processor
19
19
  */
20
20
  shouldEnable: () => boolean;
21
- lowestProcessingHeight?: number;
21
+ referenceProcessingHeight?: number;
22
22
  processingWidth: number;
23
23
  processingHeight: number;
24
24
  hasInitializedDeps?: boolean;
@@ -34,9 +34,9 @@ interface VideoStreamProcessProps extends Partial<VideoRenderParams>, Required<P
34
34
  hasInitialized: boolean;
35
35
  contentHint?: VideoContentHint;
36
36
  }
37
- declare const FEATURE_KEYS: readonly ["backgroundBlurAmount", "backgroundImageUrl", "maskCombineRatio", "edgeBlurAmount", "foregroundThreshold", "frameRate", "videoSegmentation", "videoSegmentationModel", "width", "height", "pan", "tilt", "zoom", "contentHint"];
37
+ declare const FEATURE_KEYS: readonly ["backgroundBlurAmount", "backgroundImageUrl", "backgroundThreshold", "maskCombineRatio", "edgeBlurAmount", "foregroundThreshold", "frameRate", "videoSegmentation", "videoSegmentationModel", "width", "height", "pan", "tilt", "zoom", "contentHint", "sigmaSpace", "sigmaRangeLo", "sigmaRangeHi", "excludeBystanders", "personCenterX", "personCenterY", "morphErodeRadiusPx", "morphPass", "morphDilateRadiusPx", "lightWrapIntensity", "lightWrapTightness", "lightWrapEdgeBand", "lightWrapBlurAmount", "downSampleFactor"];
38
38
  type FeaturePropKeys = (typeof FEATURE_KEYS)[number];
39
39
  type FeatureProps = Pick<Partial<VideoStreamProcessProps>, FeaturePropKeys>;
40
40
  export declare const updateFeatureProps: (constraints: MediaDeviceRequest["video"], props: FeatureProps) => FeatureProps;
41
- export declare const createVideoStreamProcess: ({ backgroundImageUrl, dynamicProcessingDimensions, edgeBlurAmount, foregroundThreshold, frameRate, gpuAPI, label, lowestProcessingHeight, maskCombineRatio, processingHeight, processingWidth, shouldEnable, stopAsMute, trackProcessorAPI, videoSegmentation, signals, ...options }: VideoStreamProcessOptions) => TrackProcessor;
41
+ export declare const createVideoStreamProcess: ({ backgroundImageUrl, dynamicProcessingDimensions, backgroundThreshold, downSampleFactor, foregroundThreshold, lightWrapEdgeBand, lightWrapIntensity, lightWrapTightness, morphDilateRadiusPx, morphErodeRadiusPx, morphPass, personCenterX, personCenterY, sigmaSpace, sigmaRangeHi, sigmaRangeLo, frameRate, gpuAPI, label, referenceProcessingHeight, maskCombineRatio, processingHeight, processingWidth, shouldEnable, stopAsMute, trackProcessorAPI, videoSegmentation, signals, ...options }: VideoStreamProcessOptions) => TrackProcessor;
42
42
  export {};
@@ -1,13 +1,14 @@
1
1
  import { createCanvasTransform, createVideoProcessor, createVideoTrackProcessor, createVideoTrackProcessorWithFallback, isRenderEffects, isSegmentationModel, } from '@pexip/media-processor';
2
2
  import { extractConstraintsWithKeys, getValueFromConstrainNumber, muteStreamTrack, } from '@pexip/media-control';
3
3
  import { isEmpty, assert } from '@pexip/utils';
4
- import { createMediaTrack, getBlurKernelSize } from './utils';
4
+ import { createMediaTrack, getBlurKernelSize, getTentBlurSize } from './utils';
5
5
  import { logger, proxyWithLog } from './logger';
6
6
  import { isVideoContentHint } from './typeGuard';
7
7
  import { PROCESSOR_LABELS } from './constants';
8
8
  const FEATURE_KEYS = [
9
9
  'backgroundBlurAmount',
10
10
  'backgroundImageUrl',
11
+ 'backgroundThreshold',
11
12
  'maskCombineRatio',
12
13
  'edgeBlurAmount',
13
14
  'foregroundThreshold',
@@ -20,6 +21,20 @@ const FEATURE_KEYS = [
20
21
  'tilt',
21
22
  'zoom',
22
23
  'contentHint',
24
+ 'sigmaSpace',
25
+ 'sigmaRangeLo',
26
+ 'sigmaRangeHi',
27
+ 'excludeBystanders',
28
+ 'personCenterX',
29
+ 'personCenterY',
30
+ 'morphErodeRadiusPx',
31
+ 'morphPass',
32
+ 'morphDilateRadiusPx',
33
+ 'lightWrapIntensity',
34
+ 'lightWrapTightness',
35
+ 'lightWrapEdgeBand',
36
+ 'lightWrapBlurAmount',
37
+ 'downSampleFactor',
23
38
  ];
24
39
  const getVideoConstraints = extractConstraintsWithKeys(FEATURE_KEYS);
25
40
  export const updateFeatureProps = (constraints, props) => {
@@ -62,13 +77,27 @@ export const updateFeatureProps = (constraints, props) => {
62
77
  }
63
78
  return accm;
64
79
  }
65
- case 'width':
66
- case 'height':
67
- case 'frameRate':
68
- case 'foregroundThreshold':
80
+ case 'backgroundBlurAmount':
81
+ case 'backgroundThreshold':
82
+ case 'downSampleFactor':
69
83
  case 'edgeBlurAmount':
84
+ case 'foregroundThreshold':
85
+ case 'frameRate':
86
+ case 'height':
87
+ case 'lightWrapBlurAmount':
88
+ case 'lightWrapEdgeBand':
89
+ case 'lightWrapIntensity':
90
+ case 'lightWrapTightness':
70
91
  case 'maskCombineRatio':
71
- case 'backgroundBlurAmount': {
92
+ case 'morphDilateRadiusPx':
93
+ case 'morphErodeRadiusPx':
94
+ case 'morphPass':
95
+ case 'personCenterX':
96
+ case 'personCenterY':
97
+ case 'sigmaRangeHi':
98
+ case 'sigmaRangeLo':
99
+ case 'sigmaSpace':
100
+ case 'width': {
72
101
  const [feature] = extracted[key];
73
102
  if (feature !== undefined) {
74
103
  const value = getValueFromConstrainNumber(feature);
@@ -96,23 +125,68 @@ export const updateFeatureProps = (constraints, props) => {
96
125
  }
97
126
  }, {});
98
127
  };
99
- const applyFeatures = (transformer, features, lowestProcessingHeight) => {
128
+ const applyFeatures = (transformer, features, referenceProcessingHeight) => {
100
129
  for (const key in features) {
101
130
  const k = key;
102
131
  switch (k) {
103
- case 'edgeBlurAmount':
104
132
  case 'maskCombineRatio':
105
- case 'foregroundThreshold': {
133
+ case 'foregroundThreshold':
134
+ case 'sigmaSpace':
135
+ case 'sigmaRangeLo':
136
+ case 'sigmaRangeHi':
137
+ case 'morphErodeRadiusPx':
138
+ case 'morphPass':
139
+ case 'morphDilateRadiusPx':
140
+ case 'lightWrapIntensity':
141
+ case 'lightWrapTightness':
142
+ case 'lightWrapEdgeBand':
143
+ case 'downSampleFactor':
144
+ case 'backgroundThreshold': {
145
+ const value = features[k];
146
+ if (value !== undefined) {
147
+ transformer[k] = value;
148
+ }
149
+ break;
150
+ }
151
+ case 'excludeBystanders': {
106
152
  const value = features[k];
107
153
  if (value !== undefined) {
108
154
  transformer[k] = value;
109
155
  }
110
156
  break;
111
157
  }
158
+ case 'personCenterX': {
159
+ const value = features[k];
160
+ if (value !== undefined) {
161
+ transformer.personCenter = [
162
+ value,
163
+ transformer.personCenter[1],
164
+ ];
165
+ }
166
+ break;
167
+ }
168
+ case 'personCenterY': {
169
+ const value = features[k];
170
+ if (value !== undefined) {
171
+ transformer.personCenter = [
172
+ transformer.personCenter[0],
173
+ value,
174
+ ];
175
+ }
176
+ break;
177
+ }
178
+ case 'lightWrapBlurAmount':
112
179
  case 'backgroundBlurAmount': {
113
180
  const value = features[k];
114
181
  if (value !== undefined) {
115
- transformer[k] = getBlurKernelSize(value, transformer.height, lowestProcessingHeight);
182
+ transformer[k] = getBlurKernelSize(value, referenceProcessingHeight ?? transformer.height, transformer.height);
183
+ }
184
+ break;
185
+ }
186
+ case 'edgeBlurAmount': {
187
+ const value = features[k];
188
+ if (value !== undefined) {
189
+ transformer[k] = getTentBlurSize(value, referenceProcessingHeight ?? transformer.height, transformer.height);
116
190
  }
117
191
  break;
118
192
  }
@@ -143,28 +217,47 @@ const getTrackProcessor = (shouldUseStreamTrackProcessor, ...params) => {
143
217
  }
144
218
  return createVideoTrackProcessorWithFallback(...params);
145
219
  };
146
- export const createVideoStreamProcess = ({ backgroundImageUrl, dynamicProcessingDimensions = () => false, edgeBlurAmount, foregroundThreshold, frameRate, gpuAPI = () => 'webgl', // dynamically changing the config will not be picked up, not a priority for now
147
- label = PROCESSOR_LABELS.VideoProcessor, lowestProcessingHeight, maskCombineRatio, processingHeight, processingWidth, shouldEnable, stopAsMute = () => false, trackProcessorAPI = () => 'stream', videoSegmentation, signals, ...options }) => {
220
+ export const createVideoStreamProcess = ({ backgroundImageUrl, dynamicProcessingDimensions = () => false, backgroundThreshold, downSampleFactor, foregroundThreshold, lightWrapEdgeBand, lightWrapIntensity, lightWrapTightness, morphDilateRadiusPx, morphErodeRadiusPx, morphPass, personCenterX, personCenterY, sigmaSpace, sigmaRangeHi, sigmaRangeLo, frameRate, gpuAPI = () => 'webgl', // dynamically changing the config will not be picked up, not a priority for now
221
+ label = PROCESSOR_LABELS.VideoProcessor, referenceProcessingHeight, maskCombineRatio, processingHeight, processingWidth, shouldEnable, stopAsMute = () => false, trackProcessorAPI = () => 'stream', videoSegmentation, signals, ...options }) => {
148
222
  const videoSegmentationModel = options.videoSegmentationModel ?? 'selfie';
149
223
  const segmenter = options.segmenters[videoSegmentationModel];
150
224
  if (!segmenter) {
151
225
  throw new Error('Segmenter is undefined');
152
226
  }
153
227
  const backgroundBlurAmount = options.backgroundBlurAmount &&
154
- getBlurKernelSize(options.backgroundBlurAmount, processingHeight, lowestProcessingHeight);
228
+ getBlurKernelSize(options.backgroundBlurAmount, referenceProcessingHeight ?? processingHeight, processingHeight);
229
+ const lightWrapBlurAmount = options.lightWrapBlurAmount &&
230
+ getBlurKernelSize(options.lightWrapBlurAmount, referenceProcessingHeight ?? processingHeight, processingHeight);
231
+ const edgeBlurAmount = options.edgeBlurAmount &&
232
+ getTentBlurSize(options.edgeBlurAmount, referenceProcessingHeight ?? processingHeight, processingHeight);
155
233
  const transformer = options.transformer ??
156
234
  createCanvasTransform(segmenter, {
157
- width: processingWidth,
158
- height: processingHeight,
159
- effects: videoSegmentation,
160
- foregroundThreshold,
161
235
  backgroundBlurAmount,
162
- edgeBlurAmount,
163
236
  backgroundImageUrl,
237
+ backgroundThreshold,
238
+ downSampleFactor,
239
+ edgeBlurAmount,
240
+ effects: videoSegmentation,
241
+ foregroundThreshold,
242
+ height: processingHeight,
243
+ lightWrapBlurAmount,
244
+ lightWrapEdgeBand,
245
+ lightWrapIntensity,
246
+ lightWrapTightness,
164
247
  maskCombineRatio,
248
+ morphDilateRadiusPx,
249
+ morphErodeRadiusPx,
250
+ morphPass,
251
+ personCenter: personCenterX !== undefined && personCenterY !== undefined
252
+ ? [personCenterX, personCenterY]
253
+ : undefined,
254
+ sigmaRangeHi,
255
+ sigmaRangeLo,
256
+ sigmaSpace,
257
+ width: processingWidth,
165
258
  });
166
259
  const proxy = proxyWithLog(logger, label);
167
- let _videoProcessor = undefined;
260
+ let _videoProcessor;
168
261
  const props = {
169
262
  videoSegmentationModel,
170
263
  segmenters: {
@@ -186,11 +279,25 @@ label = PROCESSOR_LABELS.VideoProcessor, lowestProcessingHeight, maskCombineRati
186
279
  },
187
280
  videoSegmentation,
188
281
  backgroundBlurAmount,
282
+ backgroundImageUrl,
283
+ backgroundThreshold,
284
+ downSampleFactor,
189
285
  edgeBlurAmount,
190
286
  foregroundThreshold,
191
287
  frameRate,
192
- backgroundImageUrl,
288
+ lightWrapBlurAmount,
289
+ lightWrapEdgeBand,
290
+ lightWrapIntensity,
291
+ lightWrapTightness,
193
292
  maskCombineRatio,
293
+ morphDilateRadiusPx,
294
+ morphErodeRadiusPx,
295
+ morphPass,
296
+ personCenterX,
297
+ personCenterY,
298
+ sigmaRangeHi,
299
+ sigmaRangeLo,
300
+ sigmaSpace,
194
301
  hasInitialized: options.hasInitializedDeps ?? false,
195
302
  };
196
303
  return async (prevMediaTrack) => {
@@ -227,7 +334,16 @@ label = PROCESSOR_LABELS.VideoProcessor, lowestProcessingHeight, maskCombineRati
227
334
  });
228
335
  if (props.backgroundBlurAmount !== undefined) {
229
336
  props.transformer.backgroundBlurAmount =
230
- getBlurKernelSize(props.backgroundBlurAmount, height, lowestProcessingHeight);
337
+ getBlurKernelSize(props.backgroundBlurAmount, referenceProcessingHeight ??
338
+ processingHeight, height);
339
+ }
340
+ if (props.lightWrapBlurAmount !== undefined) {
341
+ props.transformer.lightWrapBlurAmount =
342
+ getBlurKernelSize(props.lightWrapBlurAmount, referenceProcessingHeight ??
343
+ processingHeight, height);
344
+ }
345
+ if (props.edgeBlurAmount !== undefined) {
346
+ props.transformer.edgeBlurAmount = getTentBlurSize(props.edgeBlurAmount, referenceProcessingHeight ?? processingHeight, height);
231
347
  }
232
348
  }
233
349
  }
@@ -238,7 +354,7 @@ label = PROCESSOR_LABELS.VideoProcessor, lowestProcessingHeight, maskCombineRati
238
354
  }
239
355
  props.hasInitialized = true;
240
356
  }
241
- applyFeatures(props.transformer, features, lowestProcessingHeight);
357
+ applyFeatures(props.transformer, features, referenceProcessingHeight);
242
358
  const model = features.videoSegmentationModel &&
243
359
  props.segmenters[features.videoSegmentationModel];
244
360
  if (features.videoSegmentationModel &&
@@ -318,7 +434,7 @@ label = PROCESSOR_LABELS.VideoProcessor, lowestProcessingHeight, maskCombineRati
318
434
  if (isEmpty(features)) {
319
435
  return;
320
436
  }
321
- applyFeatures(props.transformer, features, lowestProcessingHeight);
437
+ applyFeatures(props.transformer, features, referenceProcessingHeight);
322
438
  if (dynamicProcessingDimensions()) {
323
439
  const acceptedSettings = this.previousMediaTrack?.source.getSettings();
324
440
  if (acceptedSettings?.width && acceptedSettings?.height) {
@@ -328,7 +444,16 @@ label = PROCESSOR_LABELS.VideoProcessor, lowestProcessingHeight, maskCombineRati
328
444
  });
329
445
  if (props.backgroundBlurAmount !== undefined) {
330
446
  props.transformer.backgroundBlurAmount =
331
- getBlurKernelSize(props.backgroundBlurAmount, acceptedSettings.height, lowestProcessingHeight);
447
+ getBlurKernelSize(props.backgroundBlurAmount, referenceProcessingHeight ??
448
+ processingHeight, acceptedSettings.height);
449
+ }
450
+ if (props.lightWrapBlurAmount !== undefined) {
451
+ props.transformer.lightWrapBlurAmount =
452
+ getBlurKernelSize(props.lightWrapBlurAmount, referenceProcessingHeight ??
453
+ processingHeight, acceptedSettings.height);
454
+ }
455
+ if (props.edgeBlurAmount !== undefined) {
456
+ props.transformer.edgeBlurAmount = getTentBlurSize(props.edgeBlurAmount, referenceProcessingHeight ?? processingHeight, acceptedSettings.height);
332
457
  }
333
458
  }
334
459
  }
@@ -343,18 +468,35 @@ label = PROCESSOR_LABELS.VideoProcessor, lowestProcessingHeight, maskCombineRati
343
468
  getSettings: () => {
344
469
  const contentHint = track.contentHint ?? '';
345
470
  return {
346
- videoSegmentation: transformer.effects,
347
- foregroundThreshold: transformer.foregroundThreshold,
348
- // Background blur amount is a function of height
349
471
  backgroundBlurAmount: props.backgroundBlurAmount,
472
+ backgroundImageUrl: transformer.backgroundImageUrl,
473
+ backgroundThreshold: transformer.backgroundThreshold,
474
+ contentHint,
475
+ downSampleFactor: transformer.downSampleFactor,
350
476
  edgeBlurAmount: transformer.edgeBlurAmount,
477
+ excludeBystanders: transformer.excludeBystanders,
478
+ foregroundThreshold: transformer.foregroundThreshold,
479
+ frameRate: props.frameRate,
480
+ height: props.height,
481
+ lightWrapBlurAmount: transformer.lightWrapBlurAmount,
482
+ lightWrapEdgeBand: transformer.lightWrapEdgeBand,
483
+ lightWrapIntensity: transformer.lightWrapIntensity,
484
+ lightWrapTightness: transformer.lightWrapTightness,
351
485
  maskCombineRatio: transformer.maskCombineRatio,
352
- backgroundImageUrl: transformer.backgroundImageUrl,
353
- videoSegmentationModel: transformer.segmenter.modelAsset.modelName,
486
+ morphDilateRadiusPx: transformer.morphDilateRadiusPx,
487
+ morphErodeRadiusPx: transformer.morphErodeRadiusPx,
488
+ morphPass: transformer.morphPass,
354
489
  pan: props.pan,
490
+ personCenterX: transformer.personCenter[0],
491
+ personCenterY: transformer.personCenter[1],
492
+ sigmaRangeHi: transformer.sigmaRangeHi,
493
+ sigmaRangeLo: transformer.sigmaRangeLo,
494
+ sigmaSpace: transformer.sigmaSpace,
355
495
  tilt: props.tilt,
496
+ videoSegmentation: transformer.effects,
497
+ videoSegmentationModel: transformer.segmenter.modelAsset.modelName,
498
+ width: props.width,
356
499
  zoom: props.zoom,
357
- contentHint,
358
500
  };
359
501
  },
360
502
  });
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@pexip/media",
3
- "version": "20.3.5",
3
+ "version": "21.0.0",
4
4
  "description": "Home for media related stuff",
5
- "homepage": "https://gitlab.com/pexip/zoo",
5
+ "homepage": "https://developer.pexip.com",
6
6
  "bugs": "https://gitlab.com/pexip/zoo/issues",
7
7
  "repository": {
8
8
  "type": "git",
@@ -34,11 +34,11 @@
34
34
  "api-docs"
35
35
  ],
36
36
  "scripts": {
37
- "build": "pexip-bundler build && yarn docs",
38
- "build:sourcemap": "pexip-bundler build --sourcemap && yarn docs",
37
+ "build": "pexip-bundler build",
38
+ "build:sourcemap": "pexip-bundler build --sourcemap",
39
39
  "clean": "rm -fr dist api-docs",
40
40
  "dev": "pexip-bundler dev",
41
- "prepack": "yarn clean && yarn build",
41
+ "prepack": "yarn clean && yarn build && yarn docs",
42
42
  "test": "jest",
43
43
  "check-format": "prettier --ignore-path=../../.prettierignore --check .",
44
44
  "format": "prettier --ignore-path=../../.prettierignore --write .",
@@ -47,10 +47,10 @@
47
47
  "docs": "typedoc"
48
48
  },
49
49
  "dependencies": {
50
- "@pexip/media-control": "20.3.5",
51
- "@pexip/media-processor": "20.3.5",
52
- "@pexip/signal": "16.9.3",
53
- "@pexip/utils": "17.1.3"
50
+ "@pexip/media-control": "21.0.0",
51
+ "@pexip/media-processor": "21.0.0",
52
+ "@pexip/signal": "16.9.4",
53
+ "@pexip/utils": "17.2.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@jest/globals": "^30.2.0",