@pexip/media 20.1.0 → 20.3.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/dist/utils.d.ts CHANGED
@@ -28,6 +28,7 @@ export declare const findExpectedInput: (devices: IndexedDevices, constraints: I
28
28
  * there is no track to check
29
29
  */
30
30
  export declare const isTrackMuted: (track: MediaStreamTrack | undefined) => boolean | undefined;
31
+ export declare const isTrackEnded: (track: MediaStreamTrack) => boolean;
31
32
  export declare const buildMedia: (mediaInit: MediaInit, onDevicesChanged?: import("@pexip/signal").Signal<IndexedDevices, IndexedDevices>) => Media;
32
33
  /**
33
34
  * Shallow copy the provided object and override with provided overriding
@@ -58,11 +59,12 @@ export declare const mergeSettings: (settingsA: ExtendedMediaTrackSettings | und
58
59
  * @param percentage - The percentage of image height to calculate the blur
59
60
  * kernel size
60
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
61
63
  * @param max - The upper bound
62
64
  *
63
65
  * @returns blur kernel size
64
66
  */
65
- export declare const getBlurKernelSize: (percentage: number, height: number, max?: number) => number;
67
+ export declare const getBlurKernelSize: (percentage: number, height: number, lowestProcessingHeight?: number, max?: number) => number;
66
68
  /**
67
69
  * Apply the content hint to the track
68
70
  *
package/dist/utils.js CHANGED
@@ -19,6 +19,10 @@ export const createMediaTrack = (trackInit) => {
19
19
  (trackInit.kind === trackInit.input?.kind &&
20
20
  trackInit.kind === toMediaDeviceInputKind(trackInit.track));
21
21
  assert(trackConsistency, `Inconsistent track kind: ${trackInit.input?.kind} ${trackInit.track && toMediaDeviceInputKind(trackInit.track)} vs ${trackInit.kind}`);
22
+ assert(!hasOwn(trackInit, 'mute') ||
23
+ (hasOwn(trackInit, 'mute') && hasOwn(trackInit, 'overrideMute')), 'Inconsistent overrideMute mute()');
24
+ assert(!hasOwn(trackInit, 'muted') ||
25
+ (hasOwn(trackInit, 'muted') && hasOwn(trackInit, 'overrideMute')), 'Inconsistent overrideMute muted');
22
26
  const currentConstraints = typeof trackInit.constraints === 'boolean' ? {} : trackInit.constraints;
23
27
  // Find the original source track through the linked list
24
28
  let sourceMediaTrack = trackInit.previousMediaTrack;
@@ -41,6 +45,25 @@ export const createMediaTrack = (trackInit) => {
41
45
  ...(trackInit.getSettings?.() ?? {}),
42
46
  };
43
47
  };
48
+ // Suscribe track events
49
+ let trackUnsubscribe;
50
+ if (trackInit.track) {
51
+ trackUnsubscribe = createStreamTrackEventSubscriptions(trackInit.track, {
52
+ ended(track) {
53
+ assert(track.id === trackInit.track?.id, 'Same Track');
54
+ void mediaTrack.release();
55
+ trackInit.signals?.onMediaTrackStopped?.emit(mediaTrack);
56
+ },
57
+ mute(track) {
58
+ assert(track.id === trackInit.track?.id, 'Same Track');
59
+ trackInit.signals?.onMediaTrackSuspended?.emit(mediaTrack);
60
+ },
61
+ unmute(track) {
62
+ assert(track.id === trackInit.track?.id, 'Same Track');
63
+ trackInit.signals?.onMediaTrackResumed?.emit(mediaTrack);
64
+ },
65
+ });
66
+ }
44
67
  const mediaTrack = {
45
68
  get kind() {
46
69
  return trackInit.kind;
@@ -52,9 +75,10 @@ export const createMediaTrack = (trackInit) => {
52
75
  return trackInit.track;
53
76
  },
54
77
  get input() {
55
- return trackInit.track?.readyState !== 'live'
56
- ? undefined
57
- : trackInit.input;
78
+ if (!trackInit.track) {
79
+ return undefined;
80
+ }
81
+ return trackInit.input;
58
82
  },
59
83
  get expectedInput() {
60
84
  return trackInit.expectedInput;
@@ -69,33 +93,53 @@ export const createMediaTrack = (trackInit) => {
69
93
  if (trackInit.overrideMute) {
70
94
  return trackInit.muted;
71
95
  }
72
- return (
73
- // If the source track muted, the following processed track will
74
- // be muted as well since there is no data to process
75
- !!isTrackMuted(this.source.track) ||
76
- isTrackMuted(trackInit.track));
96
+ if (trackInit.track) {
97
+ return !trackInit.track.enabled;
98
+ }
99
+ return undefined;
100
+ },
101
+ get stopped() {
102
+ return trackInit.track && isTrackEnded(trackInit.track);
103
+ },
104
+ get suspended() {
105
+ // Any track that is suspended in the chain will cause this track to be suspended
106
+ return (trackInit.previousMediaTrack?.suspended ||
107
+ trackInit.track?.muted);
77
108
  },
78
109
  get source() {
79
- return sourceMediaTrack ?? mediaTrack;
110
+ return sourceMediaTrack ?? this;
80
111
  },
81
- mute(toMute) {
112
+ mute(toMute, soft) {
113
+ if (this.muted === toMute) {
114
+ return;
115
+ }
82
116
  if (trackInit.overrideMute) {
83
- return trackInit.mute?.(toMute);
117
+ trackInit.mute?.(toMute, this, soft);
84
118
  }
85
- trackInit.previousMediaTrack?.mute(toMute);
86
- if (trackInit.track) {
87
- trackInit.track.enabled = !toMute;
119
+ else {
120
+ if (trackInit.track) {
121
+ trackInit.track.enabled = !toMute;
122
+ }
123
+ trackInit.previousMediaTrack?.mute(toMute, soft);
88
124
  }
125
+ trackInit.signals?.onMediaTrackMuted?.emit(this);
89
126
  },
90
127
  getSettings,
91
128
  getConstraints() {
92
129
  return getConstraints();
93
130
  },
94
- clone(signals) {
131
+ clone(signals, label) {
95
132
  return createMediaTrack({
96
- ...trackInit,
133
+ kind: this.kind,
134
+ label: [label, this.label]
135
+ .flatMap(a => (a ? [a] : []))
136
+ .join('|'),
137
+ input: this.input,
97
138
  constraints: getConstraints(),
98
- track: trackInit.track?.clone(),
139
+ track: this.track?.clone(),
140
+ // When Cloning a MediaTrack, overrideMute should be turned off since
141
+ // it might corrupt the logic of its original media pipeline
142
+ overrideMute: false,
99
143
  signals,
100
144
  });
101
145
  },
@@ -135,23 +179,20 @@ export const createMediaTrack = (trackInit) => {
135
179
  Object.assign(currentConstraints, resolvedConstraints);
136
180
  },
137
181
  async release() {
182
+ trackUnsubscribe?.();
138
183
  trackInit.track?.stop();
139
- if (trackInit.track) {
140
- trackInit.signals?.onTrackReleased?.emit(trackInit.track);
141
- trackInit.signals?.[trackInit.track.kind === 'audio'
142
- ? 'onAudioMuteStateChanged'
143
- : 'onVideoMuteStateChanged']?.emit(undefined);
144
- }
145
- await trackInit.previousMediaTrack?.release();
146
184
  await trackInit.release?.();
185
+ await trackInit.previousMediaTrack?.release();
186
+ trackInit.signals?.onMediaTrackStopped?.emit(this);
147
187
  },
148
188
  toJSON() {
149
189
  return {
150
- label: trackInit.label,
151
- track: trackInit.track,
152
- previousMediaTrack: trackInit.previousMediaTrack,
153
- constraints: getConstraints(),
154
- settings: getSettings(),
190
+ label: this.label,
191
+ track: this.track,
192
+ input: this.input,
193
+ previousMediaTrack: this.previousMediaTrack,
194
+ constraints: this.getConstraints(),
195
+ settings: this.getSettings(),
155
196
  };
156
197
  },
157
198
  };
@@ -161,7 +202,7 @@ export const createTrackProcessingPipeline = (processors) => async (track) => {
161
202
  let lastTrack = track;
162
203
  for (const process of processors) {
163
204
  if (lastTrack.track?.readyState !== 'live') {
164
- return lastTrack;
205
+ continue;
165
206
  }
166
207
  lastTrack = await process(lastTrack);
167
208
  }
@@ -208,17 +249,22 @@ export const findExpectedInput = (devices, constraints, input, kind) => {
208
249
  * there is no track to check
209
250
  */
210
251
  export const isTrackMuted = (track) => {
211
- if (!track || track.readyState === 'ended') {
252
+ if (!track) {
212
253
  return undefined;
213
254
  }
214
255
  return track.muted || !track.enabled;
215
256
  };
257
+ export const isTrackEnded = (track) => track.readyState === 'ended';
216
258
  export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevicesChanged) => {
217
259
  const props = {
218
260
  originalConstraints: mediaInit.originalConstraints ?? mediaInit.constraints,
219
261
  status: mediaInit.status,
220
262
  devices: mediaInit.devices,
221
263
  stream: mediaInit.stream ?? new MediaStream(),
264
+ // `undefined` is used to avoid double state overriding,
265
+ // because 2 subsequent `susppend` events could invalidate the previous assignment
266
+ audioAlreadyMuted: undefined,
267
+ videoAlreadyMuted: undefined,
222
268
  audioTrack: mediaInit.tracks
223
269
  .flatMap(track => {
224
270
  return track.kind === 'audioinput' ? [track] : [];
@@ -228,74 +274,77 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
228
274
  .flatMap(track => (track.kind === 'videoinput' ? [track] : []))
229
275
  .at(0),
230
276
  };
231
- const subscribeTrackEvents = (track) => createStreamTrackEventSubscriptions(track, {
232
- ended: track => {
233
- mediaInit.signals?.[track.kind === 'audio'
234
- ? 'onAudioMuteStateChanged'
235
- : 'onVideoMuteStateChanged']?.emit(undefined);
236
- mediaInit.signals?.onStreamTrackEndedFinal?.emit(track);
237
- },
238
- mute: track => {
239
- mediaInit.signals?.[track.kind === 'audio'
240
- ? 'onAudioMuteStateChanged'
241
- : 'onVideoMuteStateChanged']?.emit(true);
242
- mediaInit.signals?.onStreamTrackMuted?.emit(track);
243
- },
244
- unmute: track => {
245
- mediaInit.signals?.[track.kind === 'audio'
246
- ? 'onAudioMuteStateChanged'
247
- : 'onVideoMuteStateChanged']?.emit(!track.enabled);
248
- mediaInit.signals?.onStreamTrackUnmuted?.emit(track);
249
- },
250
- });
251
- const subscribeTrackAndSourceTrackEvents = (track) => {
252
- let trackUnsubscribe;
253
- let sourceTrackUnsubscribe;
254
- if (track.track) {
255
- trackUnsubscribe = subscribeTrackEvents(track.track);
256
- if (track.source.track && track.source.track !== track.track) {
257
- sourceTrackUnsubscribe = subscribeTrackEvents(track.source.track);
258
- }
259
- }
260
- return () => {
261
- trackUnsubscribe?.();
262
- sourceTrackUnsubscribe?.();
263
- };
264
- };
265
- const trackSubscriptions = new Map(mediaInit.tracks.flatMap(track => {
266
- if (track.track) {
267
- return [
268
- [track.track, subscribeTrackAndSourceTrackEvents(track)],
269
- ];
270
- }
271
- return [];
272
- }));
273
277
  const getConstraints = () => {
274
278
  const audio = props.audioTrack?.getConstraints() ?? mediaInit.constraints.audio;
275
279
  const video = props.videoTrack?.getConstraints() ?? mediaInit.constraints.video;
276
280
  return { audio: audio, video: video };
277
281
  };
278
- let unsubscribe = onDevicesChanged.add(devices => {
282
+ const updateDevices = (devices) => {
279
283
  props.devices = devices;
280
- });
281
- const muteTrack = (track, mute) => {
282
- const previousMuteState = track.muted;
283
- const previousEnabledState = track.track?.enabled;
284
- track.mute(mute);
285
- const currentMuteState = track.muted;
286
- const currentEnabledState = track.track?.enabled;
287
- if (track.track &&
288
- previousEnabledState !== undefined &&
289
- currentEnabledState !== undefined &&
290
- previousEnabledState !== currentEnabledState) {
291
- mediaInit.signals?.onStreamTrackEnabled?.emit(track.track);
284
+ };
285
+ // Two-hand control to avoid leaking of the media
286
+ // When the track is suspended by the system, we also mute it if it is not already muted.
287
+ // As it is possible that the track could still flow data even if it is system muted
288
+ const handleSuspended = (track) => {
289
+ switch (track.kind) {
290
+ case 'audioinput': {
291
+ if (props.audioAlreadyMuted === undefined) {
292
+ props.audioAlreadyMuted = props.audioTrack?.muted === true;
293
+ }
294
+ if (!props.audioAlreadyMuted) {
295
+ props.audioTrack?.mute(true, true);
296
+ }
297
+ break;
298
+ }
299
+ case 'videoinput': {
300
+ if (props.videoAlreadyMuted === undefined) {
301
+ props.videoAlreadyMuted = props.videoTrack?.muted === true;
302
+ }
303
+ if (!props.videoAlreadyMuted) {
304
+ props.videoTrack?.mute(true, true);
305
+ }
306
+ break;
307
+ }
292
308
  }
293
- if (previousMuteState !== currentMuteState) {
294
- mediaInit.signals?.[track.kind === 'audioinput'
295
- ? 'onAudioMuteStateChanged'
296
- : 'onVideoMuteStateChanged']?.emit(currentMuteState);
309
+ };
310
+ // When the track is resumed by the system, we only unmute the track
311
+ // if an only if it is muted by the suspended event
312
+ const handleResumed = (track) => {
313
+ switch (track.kind) {
314
+ case 'audioinput': {
315
+ assert(props.audioAlreadyMuted !== undefined, 'Audio Resume should not be triggerred before Suspended');
316
+ if (props.audioTrack?.suspended === false) {
317
+ if (!props.audioAlreadyMuted) {
318
+ props.audioTrack.mute(false);
319
+ }
320
+ props.audioAlreadyMuted = undefined;
321
+ }
322
+ break;
323
+ }
324
+ case 'videoinput': {
325
+ assert(props.videoAlreadyMuted !== undefined, 'Video Resume should not be triggerred before Suspended');
326
+ if (props.videoTrack?.suspended === false) {
327
+ if (!props.videoAlreadyMuted) {
328
+ props.videoTrack.mute(false);
329
+ }
330
+ props.videoAlreadyMuted = undefined;
331
+ }
332
+ break;
333
+ }
297
334
  }
298
335
  };
336
+ const subscribe = () => [
337
+ onDevicesChanged.add(updateDevices),
338
+ mediaInit.signals?.onMediaTrackSuspended?.add(handleSuspended),
339
+ mediaInit.signals?.onMediaTrackResumed?.add(handleResumed),
340
+ ];
341
+ let subscriptions = subscribe();
342
+ const unsubscribe = () => {
343
+ for (const unsubscribe of subscriptions) {
344
+ unsubscribe?.();
345
+ }
346
+ subscriptions.length = 0;
347
+ };
299
348
  return {
300
349
  get id() {
301
350
  return props.stream.id;
@@ -360,16 +409,16 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
360
409
  props.devices.size('videoinput') > 0));
361
410
  },
362
411
  muteAudio(mute) {
363
- if (!props.audioTrack) {
412
+ if (!props.audioTrack || props.audioTrack.suspended) {
364
413
  return;
365
414
  }
366
- muteTrack(props.audioTrack, mute);
415
+ props.audioTrack.mute(mute);
367
416
  },
368
417
  muteVideo(mute) {
369
- if (!props.videoTrack) {
418
+ if (!props.videoTrack || props.videoTrack.suspended) {
370
419
  return;
371
420
  }
372
- muteTrack(props.videoTrack, mute);
421
+ props.videoTrack?.mute(mute);
373
422
  },
374
423
  applyConstraints: async (constraints) => {
375
424
  await Promise.all([
@@ -382,12 +431,7 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
382
431
  ]);
383
432
  },
384
433
  async release() {
385
- unsubscribe?.();
386
- unsubscribe = undefined;
387
- for (const unsubscribe of trackSubscriptions.values()) {
388
- unsubscribe();
389
- }
390
- trackSubscriptions.clear();
434
+ unsubscribe();
391
435
  await Promise.all([
392
436
  props.audioTrack?.release(),
393
437
  props.videoTrack?.release(),
@@ -397,26 +441,25 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
397
441
  audio: props.audioTrack?.getSettings(),
398
442
  video: props.videoTrack?.getSettings(),
399
443
  }),
400
- clone(signals) {
444
+ clone(signals, label) {
401
445
  const tracks = [props.audioTrack, props.videoTrack].flatMap(track => {
402
446
  if (!track?.source) {
403
447
  return [];
404
448
  }
405
- const cloned = track.source.clone(signals);
406
- // Restore the enabled state for all cloned track
407
- cloned.mute(false);
408
- return cloned;
449
+ const cloned = track.source.clone(signals, label);
450
+ return [cloned];
409
451
  });
410
452
  const stream = this.stream &&
411
453
  new MediaStream(tracks.flatMap(track => (track.track ? [track.track] : [])));
412
454
  const clonedMedia = buildMedia({
413
- tracks,
414
- devices: this.devices,
415
- status: this.status,
416
- stream,
417
455
  constraints: this.getConstraints(),
456
+ devices: this.devices,
418
457
  originalConstraints: this.getOriginalConstraints(),
419
458
  permission: mediaInit.permission,
459
+ signals,
460
+ status: this.status,
461
+ stream,
462
+ tracks,
420
463
  });
421
464
  return clonedMedia;
422
465
  },
@@ -429,6 +472,9 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
429
472
  getVideoTracks() {
430
473
  return props.videoTrack ? [props.videoTrack] : [];
431
474
  },
475
+ isCurrentTrack(track) {
476
+ return track === props.audioTrack || track === props.videoTrack;
477
+ },
432
478
  addTrack(track) {
433
479
  switch (track.kind) {
434
480
  case 'audioinput':
@@ -446,13 +492,22 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
446
492
  default:
447
493
  break;
448
494
  }
495
+ if (subscriptions.length === 0) {
496
+ subscriptions = subscribe();
497
+ }
449
498
  if (track.track) {
450
499
  props.stream.addTrack(track.track);
451
- if (!trackSubscriptions.has(track.track)) {
452
- trackSubscriptions.set(track.track, subscribeTrackAndSourceTrackEvents(track));
453
- }
454
500
  mediaInit.signals?.onAddTrack?.emit(track.track);
455
501
  props.stream.dispatchEvent(new MediaStreamTrackEvent('addtrack', { track: track.track }));
502
+ if (track.muted) {
503
+ mediaInit.signals?.onMediaTrackMuted?.emit(track);
504
+ }
505
+ if (track.suspended) {
506
+ mediaInit.signals?.onMediaTrackSuspended?.emit(track);
507
+ }
508
+ if (track.stopped) {
509
+ mediaInit.signals?.onMediaTrackStopped?.emit(track);
510
+ }
456
511
  }
457
512
  },
458
513
  removeTrack(track) {
@@ -467,13 +522,14 @@ export const buildMedia = (mediaInit, onDevicesChanged = internalSignals.onDevic
467
522
  }
468
523
  if (removed && track.track) {
469
524
  props.stream.removeTrack(track.track);
470
- trackSubscriptions.get(track.track)?.();
471
- trackSubscriptions.delete(track.track);
472
525
  mediaInit.signals?.onRemoveTrack?.emit(track.track);
473
526
  props.stream.dispatchEvent(new MediaStreamTrackEvent('removetrack', {
474
527
  track: track.track,
475
528
  }));
476
529
  }
530
+ if (subscriptions.length) {
531
+ unsubscribe();
532
+ }
477
533
  },
478
534
  toJSON() {
479
535
  return {
@@ -634,15 +690,20 @@ export const mergeSettings = (settingsA, settingsB) => {
634
690
  * @param percentage - The percentage of image height to calculate the blur
635
691
  * kernel size
636
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
637
694
  * @param max - The upper bound
638
695
  *
639
696
  * @returns blur kernel size
640
697
  */
641
- export const getBlurKernelSize = (percentage, height, max = calculateMaxBlurPass(height)) => {
698
+ export const getBlurKernelSize = (percentage, height, lowestProcessingHeight, max = calculateMaxBlurPass(height)) => {
642
699
  if (height <= 0 || percentage <= 0 || max <= 0) {
643
700
  return 0;
644
701
  }
645
- return Math.min(Math.ceil(percentage * 0.01 * max), max);
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);
646
707
  };
647
708
  /**
648
709
  * Apply the content hint to the track
@@ -1,10 +1,10 @@
1
- import type { VideoProcessor, SegmentationTransform, SegmentationModel, RenderBackend } from '@pexip/media-processor';
1
+ import type { RenderBackend, SegmentationModel, SegmentationTransform, VideoProcessor } from '@pexip/media-processor';
2
2
  import type { MediaDeviceRequest } from '@pexip/media-control';
3
- import type { TrackProcessor, VideoRenderParams, Segmenters, VideoStreamTrackProcessorAPIs, VideoContentHint } from './types';
3
+ import type { MediaSignals, Segmenters, TrackProcessor, VideoContentHint, VideoRenderParams, VideoStreamTrackProcessorAPIs } from './types';
4
4
  interface ProcessorDeps {
5
- videoProcessor?: () => VideoProcessor;
6
- transformer?: SegmentationTransform;
7
5
  segmenters: Partial<Segmenters>;
6
+ transformer?: SegmentationTransform;
7
+ videoProcessor?: () => VideoProcessor;
8
8
  videoSegmentationModel?: SegmentationModel;
9
9
  }
10
10
  interface VideoStreamProcessOptions extends Partial<VideoRenderParams>, Omit<ProcessorDeps, 'videoProcessor'> {
@@ -18,14 +18,17 @@ 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
22
  processingWidth: number;
22
23
  processingHeight: number;
23
24
  hasInitializedDeps?: boolean;
24
25
  width?: number;
25
26
  height?: number;
26
27
  label?: string;
28
+ stopAsMute?: () => boolean;
27
29
  dynamicProcessingDimensions?: () => boolean;
28
30
  gpuAPI?: () => RenderBackend;
31
+ signals?: MediaSignals;
29
32
  }
30
33
  interface VideoStreamProcessProps extends Partial<VideoRenderParams>, Required<ProcessorDeps> {
31
34
  hasInitialized: boolean;
@@ -35,5 +38,5 @@ declare const FEATURE_KEYS: readonly ["backgroundBlurAmount", "backgroundImageUr
35
38
  type FeaturePropKeys = (typeof FEATURE_KEYS)[number];
36
39
  type FeatureProps = Pick<Partial<VideoStreamProcessProps>, FeaturePropKeys>;
37
40
  export declare const updateFeatureProps: (constraints: MediaDeviceRequest["video"], props: FeatureProps) => FeatureProps;
38
- export declare const createVideoStreamProcess: ({ trackProcessorAPI, processingWidth, processingHeight, shouldEnable, frameRate, videoSegmentation, foregroundThreshold, backgroundImageUrl, maskCombineRatio, edgeBlurAmount, label, dynamicProcessingDimensions, gpuAPI, ...options }: VideoStreamProcessOptions) => TrackProcessor;
41
+ export declare const createVideoStreamProcess: ({ backgroundImageUrl, dynamicProcessingDimensions, edgeBlurAmount, foregroundThreshold, frameRate, gpuAPI, label, lowestProcessingHeight, maskCombineRatio, processingHeight, processingWidth, shouldEnable, stopAsMute, trackProcessorAPI, videoSegmentation, signals, ...options }: VideoStreamProcessOptions) => TrackProcessor;
39
42
  export {};