@pexip/media-processor 21.0.0 → 22.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.
Files changed (115) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +17 -10
  3. package/api-docs/README.mdx +37 -18
  4. package/api-docs/functions/assertMediaStreamVideoTrack.mdx +13 -0
  5. package/api-docs/functions/createFrameCallbackRequest.mdx +8 -2
  6. package/api-docs/functions/createMediaStreamTrackInputProcessAdaptor.mdx +10 -0
  7. package/api-docs/functions/createReadWritableStreamFallbackInputProcessAdaptor.mdx +17 -0
  8. package/api-docs/functions/createReadWritableStreamInputProcessAdaptor.mdx +10 -0
  9. package/api-docs/functions/createVideoProcessor.mdx +3 -3
  10. package/api-docs/functions/getDefaultInputProcessAdaptor.mdx +17 -0
  11. package/api-docs/functions/isMediaStreamVideoTrack.mdx +13 -0
  12. package/api-docs/functions/playVideo.mdx +2 -2
  13. package/api-docs/interfaces/Analyzer.mdx +4 -0
  14. package/api-docs/interfaces/Benchmark.mdx +14 -2
  15. package/api-docs/interfaces/FrameTransformStats.mdx +9 -0
  16. package/api-docs/interfaces/Gain.mdx +4 -0
  17. package/api-docs/interfaces/ImageSegmenterOptions.mdx +0 -2
  18. package/api-docs/interfaces/InputProcessAdaptor.mdx +28 -0
  19. package/api-docs/interfaces/Process.mdx +8 -3
  20. package/api-docs/interfaces/ProcessEventMute.mdx +6 -0
  21. package/api-docs/interfaces/ProcessWorkerEventOpened.mdx +7 -0
  22. package/api-docs/interfaces/ProcessWorkerEventStats.mdx +6 -0
  23. package/api-docs/interfaces/ProcessWorkerEventUpdated.mdx +7 -0
  24. package/api-docs/interfaces/ProcessorEvent.mdx +1 -1
  25. package/api-docs/interfaces/ProcessorOptions.mdx +0 -1
  26. package/api-docs/interfaces/ProcessorProcessOptions.mdx +3 -1
  27. package/api-docs/interfaces/ProcessorUpdateOptions.mdx +15 -0
  28. package/api-docs/interfaces/ReadWritableStream.mdx +12 -0
  29. package/api-docs/interfaces/Renderer.mdx +2 -2
  30. package/api-docs/interfaces/RendererOptions.mdx +0 -4
  31. package/api-docs/interfaces/Segmenter.mdx +89 -10
  32. package/api-docs/interfaces/SegmenterOptions.mdx +0 -1
  33. package/api-docs/interfaces/VideoProcessor.mdx +49 -5
  34. package/api-docs/type-aliases/ProcessEvents.mdx +1 -0
  35. package/api-docs/type-aliases/ProcessInputType.mdx +3 -1
  36. package/api-docs/type-aliases/ProcessWorkerEvents.mdx +6 -4
  37. package/api-docs/type-aliases/ProcessorWorkerEvents.mdx +1 -1
  38. package/api-docs/type-aliases/Struct.mdx +9 -0
  39. package/api-docs/variables/FRAME_RATE.mdx +1 -1
  40. package/api-docs/variables/ImageFetchError.mdx +3 -0
  41. package/api-docs/variables/MediaStreamTrackTransferable.mdx +5 -0
  42. package/api-docs/variables/RENDERER_OPTIONS_STRUCT.mdx +3 -0
  43. package/api-docs/variables/cloneImageRecord.mdx +19 -0
  44. package/api-docs/variables/compareImageRecords.mdx +18 -0
  45. package/api-docs/variables/createRemoteImageBitmap.mdx +1 -1
  46. package/api-docs/variables/isRenderOptionsKey.mdx +13 -0
  47. package/api-docs/variables/isValidGpuAPI.mdx +13 -0
  48. package/dist/common/backends/webgl/renderer.js +2 -12
  49. package/dist/common/backends/webgl/texture.d.ts +1 -2
  50. package/dist/common/backends/webgl/texture.js +2 -2
  51. package/dist/common/backends/webgl/webglUtils.js +8 -4
  52. package/dist/common/backends/webgpu/renderer.js +2 -12
  53. package/dist/common/backends/webgpu/tentBlur.js +1 -1
  54. package/dist/common/backends/webgpu/texture.d.ts +1 -2
  55. package/dist/common/backends/webgpu/texture.js +6 -2
  56. package/dist/common/constants.d.ts +3 -0
  57. package/dist/common/constants.js +4 -0
  58. package/dist/common/index.d.ts +1 -0
  59. package/dist/common/index.js +1 -0
  60. package/dist/common/tsconfig.tsbuildinfo +1 -1
  61. package/dist/common/typeGuards.d.ts +2 -0
  62. package/dist/common/typeGuards.js +7 -0
  63. package/dist/common/types/media.d.ts +5 -1
  64. package/dist/common/types/messageEvents.d.ts +31 -4
  65. package/dist/common/types/processor.d.ts +6 -4
  66. package/dist/common/types/render.d.ts +5 -2
  67. package/dist/common/types/render.js +23 -1
  68. package/dist/common/types/segmentation.d.ts +0 -12
  69. package/dist/common/utils.d.ts +13 -2
  70. package/dist/common/utils.js +54 -10
  71. package/dist/main/audio.js +3 -3
  72. package/dist/main/benchUtils.d.ts +2 -2
  73. package/dist/main/benchUtils.js +4 -4
  74. package/dist/main/index.d.ts +1 -1
  75. package/dist/main/index.js +1 -1
  76. package/dist/main/tsconfig.tsbuildinfo +1 -1
  77. package/dist/main/types.d.ts +0 -6
  78. package/dist/main/utils.d.ts +4 -0
  79. package/dist/main/utils.js +31 -0
  80. package/dist/main/video/constants.d.ts +1 -4
  81. package/dist/main/video/constants.js +1 -5
  82. package/dist/main/video/index.d.ts +2 -3
  83. package/dist/main/video/index.js +2 -3
  84. package/dist/main/video/inputProcessAdaptor.d.ts +30 -0
  85. package/dist/main/video/inputProcessAdaptor.js +198 -0
  86. package/dist/main/video/segmenter.d.ts +35 -0
  87. package/dist/main/video/{segmenters/mediapipe.js → segmenter.js} +93 -53
  88. package/dist/main/video/typeGuards.d.ts +2 -0
  89. package/dist/main/video/typeGuards.js +8 -1
  90. package/dist/main/video/types.d.ts +36 -16
  91. package/dist/main/video/utils.d.ts +5 -4
  92. package/dist/main/video/utils.js +49 -44
  93. package/dist/main/video/video.d.ts +29 -7
  94. package/dist/main/video/video.js +323 -28
  95. package/dist/workers/mediaWorker.js +23 -23
  96. package/dist/workers/tsconfig.tsbuildinfo +1 -1
  97. package/dist/worklets/tsconfig.tsbuildinfo +1 -1
  98. package/package.json +7 -7
  99. package/api-docs/functions/createCanvasTransform.mdx +0 -14
  100. package/api-docs/functions/createVideoTrackProcessor.mdx +0 -9
  101. package/api-docs/functions/createVideoTrackProcessorWithFallback.mdx +0 -15
  102. package/api-docs/interfaces/Detector.mdx +0 -81
  103. package/api-docs/interfaces/SegmentationParams.mdx +0 -35
  104. package/api-docs/interfaces/Transform.mdx +0 -62
  105. package/api-docs/type-aliases/ProcessVideoTrack.mdx +0 -15
  106. package/api-docs/type-aliases/SegmentationTransform.mdx +0 -3
  107. package/dist/main/transformer.d.ts +0 -1
  108. package/dist/main/transformer.js +0 -4
  109. package/dist/main/video/canvasTransform.d.ts +0 -17
  110. package/dist/main/video/canvasTransform.js +0 -341
  111. package/dist/main/video/segmenters/mediapipe.d.ts +0 -22
  112. package/dist/main/video/transformer.d.ts +0 -9
  113. package/dist/main/video/transformer.js +0 -68
  114. package/dist/main/video/videoStreamTrackProcessor.d.ts +0 -20
  115. package/dist/main/video/videoStreamTrackProcessor.js +0 -86
@@ -1,17 +0,0 @@
1
- import type { ImageRecord } from '../../common/types/media';
2
- import type { RendererOptions } from '../../common/types/render';
3
- import type { Segmenter, SegmentationTransform } from './types';
4
- interface Options extends RendererOptions {
5
- selfManageSegmenter?: boolean;
6
- outputCanvas?: HTMLCanvasElement;
7
- outputDelegate?: OffscreenCanvas;
8
- backgroundImage?: ImageRecord;
9
- width?: number;
10
- height?: number;
11
- }
12
- export declare const createTransform: (segmenter: Segmenter, { width, height, foregroundThreshold, backgroundThreshold, backgroundBlurAmount, edgeBlurAmount, maskCombineRatio, backgroundImageUrl, sigmaSpace, sigmaRangeLo, sigmaRangeHi, effects, backend, selfManageSegmenter, personCenter, excludeBystanders, morphPass, morphErodeRadiusPx, morphDilateRadiusPx, lightWrapIntensity, downSampleFactor, lightWrapTightness, lightWrapBlurAmount, lightWrapEdgeBand, ...options }?: Partial<Options>) => SegmentationTransform & {
13
- outputCanvas: HTMLCanvasElement | undefined;
14
- outputDelegate: OffscreenCanvas | undefined;
15
- backgroundImage?: ImageRecord;
16
- };
17
- export {};
@@ -1,341 +0,0 @@
1
- import { PROCESSING_HEIGHT, PROCESSING_WIDTH, FOREGROUND_THRESHOLD, BACKGROUND_THRESHOLD, BACKGROUND_BLUR_AMOUNT, EDGE_BLUR_AMOUNT, MASK_COMBINE_RATIO, SIGMA_RANGE, SIGMA_SPACE, PERSON_CENTER, EXCLUDE_BYSTANDERS, MORPH_PASS, MORPH_DILATE_RADIUS_PX, MORPH_ERODE_RADIUS_PX, LIGHT_WRAP_INTENSITY, DOWN_SAMPLE_FACTOR, LIGHT_WRAP_TIGHTNESS, LIGHT_WRAP_BLUR_AMOUNT, LIGHT_WRAP_EDGE_BAND, } from '../../common/constants';
2
- import { createCanvasRenderUtils } from '../../common/canvasRenderUtils';
3
- import { clamping, createObjectUpdater, nearestPowerOfTwo, } from '../../common/utils';
4
- import { assert } from '@pexip/utils';
5
- const clampInRangeFrom0To1 = clamping(0, 1);
6
- const clampInRangeFrom0To9 = clamping(0, 9);
7
- export const createTransform = (segmenter, { width = PROCESSING_WIDTH, height = PROCESSING_HEIGHT, foregroundThreshold = FOREGROUND_THRESHOLD, backgroundThreshold = BACKGROUND_THRESHOLD, backgroundBlurAmount = BACKGROUND_BLUR_AMOUNT, edgeBlurAmount = EDGE_BLUR_AMOUNT, maskCombineRatio = MASK_COMBINE_RATIO, backgroundImageUrl = '', sigmaSpace = SIGMA_SPACE, sigmaRangeLo = SIGMA_RANGE[0], sigmaRangeHi = SIGMA_RANGE[1], effects = 'none', backend = 'webgl', selfManageSegmenter, personCenter = PERSON_CENTER, excludeBystanders = EXCLUDE_BYSTANDERS, morphPass = MORPH_PASS, morphErodeRadiusPx = MORPH_ERODE_RADIUS_PX, morphDilateRadiusPx = MORPH_DILATE_RADIUS_PX, lightWrapIntensity = LIGHT_WRAP_INTENSITY, downSampleFactor = DOWN_SAMPLE_FACTOR, lightWrapTightness = LIGHT_WRAP_TIGHTNESS, lightWrapBlurAmount = LIGHT_WRAP_BLUR_AMOUNT, lightWrapEdgeBand = LIGHT_WRAP_EDGE_BAND, ...options } = {}) => {
8
- const props = {
9
- segmenter,
10
- processingWidth: width,
11
- processingHeight: height,
12
- backgroundImage: options.backgroundImage,
13
- status: 'new',
14
- outputCanvas: options.outputCanvas,
15
- outputDelegate: options.outputDelegate,
16
- unsubscribe: null,
17
- utils: undefined,
18
- renderOptions: {
19
- foregroundThreshold,
20
- backgroundThreshold,
21
- backgroundBlurAmount,
22
- edgeBlurAmount,
23
- backgroundImageUrl,
24
- effects,
25
- backend,
26
- maskCombineRatio,
27
- sigmaRangeHi,
28
- sigmaRangeLo,
29
- sigmaSpace,
30
- personCenter,
31
- excludeBystanders,
32
- morphPass,
33
- morphErodeRadiusPx,
34
- morphDilateRadiusPx,
35
- lightWrapIntensity,
36
- downSampleFactor,
37
- lightWrapTightness,
38
- lightWrapBlurAmount,
39
- lightWrapEdgeBand,
40
- },
41
- };
42
- const updateProps = createObjectUpdater(props);
43
- const handleContextRestored = () => {
44
- assert(props.utils);
45
- props.utils.reset();
46
- };
47
- const toVideoFrameLike = (input) => {
48
- // Standardize the input
49
- assert(props.utils);
50
- const image = input instanceof ImageData
51
- ? props.utils.renderImageDataToOffScreenCanvas(input, 'drawImageDataCanvas')
52
- : input;
53
- // Pre-precessing the input to resize to the processing size
54
- const canvas = props.utils.renderImageToOffScreenCanvas(image, 'inputCanvas');
55
- if ('close' in input) {
56
- input.close();
57
- }
58
- return {
59
- frame: canvas.transferToImageBitmap(),
60
- width: props.processingWidth,
61
- height: props.processingHeight,
62
- timestamp: performance.now(),
63
- };
64
- };
65
- const processInput = async (input) => {
66
- if (props.segmenter.status === 'new' ||
67
- props.segmenter.status === 'closed') {
68
- await props.segmenter.open({
69
- output: props.outputDelegate,
70
- processingWidth: props.processingWidth,
71
- processingHeight: props.processingHeight,
72
- backgroundImage: props.backgroundImage,
73
- renderOptions: props.renderOptions,
74
- });
75
- }
76
- if (!props.utils) {
77
- props.utils = createCanvasRenderUtils(props.processingWidth, props.processingHeight);
78
- }
79
- if (props.segmenter.status === 'opening') {
80
- return undefined;
81
- }
82
- const inputFrame = toVideoFrameLike(input);
83
- return props.segmenter.process(inputFrame, props.renderOptions);
84
- };
85
- const canUpdate = () => props.segmenter.status !== 'new' &&
86
- props.segmenter.status !== 'closed' &&
87
- props.segmenter.status !== 'closing';
88
- const updateProcessingDimensions = (width, height) => {
89
- let newDimensions = false;
90
- if (width && props.processingWidth !== width) {
91
- props.processingWidth = width;
92
- newDimensions = true;
93
- }
94
- if (height && props.processingHeight !== height) {
95
- props.processingHeight = height;
96
- newDimensions = true;
97
- }
98
- if (newDimensions) {
99
- props.utils = createCanvasRenderUtils(props.processingWidth, props.processingHeight);
100
- }
101
- };
102
- return {
103
- get width() {
104
- return props.processingWidth;
105
- },
106
- get height() {
107
- return props.processingHeight;
108
- },
109
- get status() {
110
- return props.status;
111
- },
112
- get foregroundThreshold() {
113
- return props.renderOptions.foregroundThreshold;
114
- },
115
- get backgroundThreshold() {
116
- return props.renderOptions.backgroundThreshold;
117
- },
118
- get backgroundBlurAmount() {
119
- return props.renderOptions.backgroundBlurAmount;
120
- },
121
- get edgeBlurAmount() {
122
- return props.renderOptions.edgeBlurAmount;
123
- },
124
- get effects() {
125
- return props.renderOptions.effects;
126
- },
127
- get maskCombineRatio() {
128
- return props.renderOptions.maskCombineRatio;
129
- },
130
- get backend() {
131
- return props.renderOptions.backend;
132
- },
133
- get sigmaSpace() {
134
- return props.renderOptions.sigmaSpace;
135
- },
136
- get sigmaRangeLo() {
137
- return props.renderOptions.sigmaRangeLo;
138
- },
139
- get sigmaRangeHi() {
140
- return props.renderOptions.sigmaRangeHi;
141
- },
142
- get personCenter() {
143
- return props.renderOptions.personCenter;
144
- },
145
- get excludeBystanders() {
146
- return props.renderOptions.excludeBystanders;
147
- },
148
- get morphPass() {
149
- return props.renderOptions.morphPass;
150
- },
151
- get morphErodeRadiusPx() {
152
- return props.renderOptions.morphErodeRadiusPx;
153
- },
154
- get morphDilateRadiusPx() {
155
- return props.renderOptions.morphDilateRadiusPx;
156
- },
157
- get lightWrapIntensity() {
158
- return props.renderOptions.lightWrapIntensity;
159
- },
160
- get lightWrapTightness() {
161
- return props.renderOptions.lightWrapTightness;
162
- },
163
- get lightWrapBlurAmount() {
164
- return props.renderOptions.lightWrapBlurAmount;
165
- },
166
- get lightWrapEdgeBand() {
167
- return props.renderOptions.lightWrapEdgeBand;
168
- },
169
- get downSampleFactor() {
170
- return props.renderOptions.downSampleFactor;
171
- },
172
- set morphPass(value) {
173
- props.renderOptions.morphPass = value;
174
- },
175
- set morphErodeRadiusPx(value) {
176
- props.renderOptions.morphErodeRadiusPx = value;
177
- },
178
- set morphDilateRadiusPx(value) {
179
- props.renderOptions.morphDilateRadiusPx = value;
180
- },
181
- set personCenter(value) {
182
- props.renderOptions.personCenter = value;
183
- },
184
- set excludeBystanders(value) {
185
- props.renderOptions.excludeBystanders = value;
186
- },
187
- set foregroundThreshold(value) {
188
- assert(value >= props.renderOptions.backgroundThreshold);
189
- props.renderOptions.foregroundThreshold =
190
- clampInRangeFrom0To1(value);
191
- },
192
- set backgroundThreshold(value) {
193
- assert(value <= props.renderOptions.foregroundThreshold);
194
- props.renderOptions.backgroundThreshold =
195
- clampInRangeFrom0To1(value);
196
- },
197
- set backgroundBlurAmount(value) {
198
- props.renderOptions.backgroundBlurAmount =
199
- clampInRangeFrom0To9(value);
200
- },
201
- set edgeBlurAmount(value) {
202
- props.renderOptions.edgeBlurAmount = clampInRangeFrom0To9(value);
203
- },
204
- set effects(value) {
205
- props.renderOptions.effects = value;
206
- },
207
- set backgroundImageUrl(value) {
208
- props.renderOptions.backgroundImageUrl = value;
209
- },
210
- get backgroundImageUrl() {
211
- return props.renderOptions.backgroundImageUrl;
212
- },
213
- set maskCombineRatio(value) {
214
- props.renderOptions.maskCombineRatio = clampInRangeFrom0To1(value);
215
- },
216
- set sigmaRangeLo(value) {
217
- props.renderOptions.sigmaRangeLo = value;
218
- },
219
- set sigmaRangeHi(value) {
220
- props.renderOptions.sigmaRangeHi = value;
221
- },
222
- set sigmaSpace(value) {
223
- props.renderOptions.sigmaSpace = value;
224
- },
225
- set lightWrapIntensity(value) {
226
- props.renderOptions.lightWrapIntensity = value;
227
- },
228
- set lightWrapTightness(value) {
229
- props.renderOptions.lightWrapTightness = value;
230
- },
231
- set lightWrapBlurAmount(value) {
232
- props.renderOptions.lightWrapBlurAmount = value;
233
- },
234
- set lightWrapEdgeBand(value) {
235
- props.renderOptions.lightWrapEdgeBand = value;
236
- },
237
- set downSampleFactor(value) {
238
- props.renderOptions.downSampleFactor = nearestPowerOfTwo(value, 0, 2);
239
- },
240
- get outputCanvas() {
241
- return props.outputCanvas;
242
- },
243
- get backgroundImage() {
244
- return props.backgroundImage;
245
- },
246
- set backgroundImage(image) {
247
- if (image &&
248
- image.key !== props.backgroundImage?.key &&
249
- image.image !== props.backgroundImage?.image) {
250
- props.backgroundImage = image;
251
- if (canUpdate()) {
252
- void props.segmenter.update({ backgroundImage: image });
253
- }
254
- }
255
- },
256
- set outputCanvas(canvas) {
257
- props.outputCanvas = canvas;
258
- },
259
- get outputDelegate() {
260
- return props.outputDelegate;
261
- },
262
- set outputDelegate(canvas) {
263
- props.outputDelegate = canvas;
264
- },
265
- get segmenter() {
266
- return props.segmenter;
267
- },
268
- set segmenter(value) {
269
- if (value !== props.segmenter) {
270
- props.segmenter = value;
271
- }
272
- },
273
- set backend(value) {
274
- if (value === props.renderOptions.backend) {
275
- return;
276
- }
277
- props.renderOptions.backend = value;
278
- if (canUpdate()) {
279
- segmenter.update({
280
- renderOptions: props.renderOptions,
281
- });
282
- }
283
- },
284
- init: (options = {}) => {
285
- updateProps(options);
286
- props.status = 'opened';
287
- props.unsubscribe = segmenter.subscribe('ContextRestored', handleContextRestored);
288
- return Promise.resolve();
289
- },
290
- update: async (options) => {
291
- updateProcessingDimensions(options.processingWidth, options.processingHeight);
292
- // update props needs to happen after dimension comparison
293
- updateProps(options);
294
- if (canUpdate()) {
295
- await segmenter.update(options);
296
- }
297
- },
298
- transform: async (videoFrame, controller) => {
299
- switch (props.renderOptions.effects) {
300
- case 'overlay':
301
- case 'blur': {
302
- const result = await processInput(videoFrame);
303
- const processedFrame = props.outputCanvas ?? result?.frame;
304
- if (props.status === 'closed' ||
305
- // Avoid processing disposed frame
306
- !processedFrame ||
307
- ('displayWidth' in processedFrame &&
308
- processedFrame.displayWidth === 0) ||
309
- ('width' in processedFrame &&
310
- processedFrame.width === 0)) {
311
- break;
312
- }
313
- if ('close' in videoFrame) {
314
- videoFrame.close();
315
- }
316
- controller.enqueue(props.outputCanvas ?? result?.frame);
317
- result?.frame.close();
318
- break;
319
- }
320
- case 'none': {
321
- controller.enqueue(videoFrame);
322
- break;
323
- }
324
- }
325
- props.status = 'processing';
326
- },
327
- close: () => {
328
- if (!selfManageSegmenter) {
329
- segmenter.close();
330
- }
331
- props.unsubscribe?.();
332
- props.status = 'closed';
333
- },
334
- destroy: async () => {
335
- if (!selfManageSegmenter) {
336
- await segmenter.destroy();
337
- }
338
- props.status = 'destroyed';
339
- },
340
- };
341
- };
@@ -1,22 +0,0 @@
1
- import type { Segmenter } from '../types';
2
- import type { Delegate, SegmenterOptions, ImageSegmenterOptions, SegmentationModelAsset } from '../../../common/types/segmentation';
3
- interface Options extends Omit<ImageSegmenterOptions, 'delegate'> {
4
- /**
5
- * The URL for the worker script.
6
- */
7
- workerScriptUrl?: URL;
8
- /**
9
- * Request Credentials for the worker.
10
- *
11
- * @defaultValue 'same-origin'
12
- */
13
- credentials?: RequestCredentials;
14
- /**
15
- * @see `SegmentationModelAsset`
16
- */
17
- modelAsset: SegmentationModelAsset;
18
- delegate?: () => Delegate;
19
- log?: (message: string, meta?: unknown) => void;
20
- }
21
- export declare const createSegmenter: (basePath?: SegmenterOptions["basePath"], { workerScriptUrl, credentials, delegate, log, ...options }?: Options) => Segmenter;
22
- export {};
@@ -1,9 +0,0 @@
1
- import type { InputFrame } from './types';
2
- export declare const nullTransformController: (controller: Pick<TransformStreamDefaultController<CanvasImageSource>, "enqueue" | "terminate">) => TransformStreamDefaultController<CanvasImageSource>;
3
- export declare const adaptInputFrameTransformer: (transformer: Transformer<InputFrame, InputFrame>) => {
4
- transform: (frame: VideoFrame, controller: TransformStreamDefaultController<VideoFrame>) => void | PromiseLike<void> | undefined;
5
- flush?: TransformerFlushCallback<InputFrame> | undefined;
6
- readableType?: undefined;
7
- start?: TransformerStartCallback<InputFrame> | undefined;
8
- writableType?: undefined;
9
- };
@@ -1,68 +0,0 @@
1
- const wrapTransformController = (videoFrame, controller) => {
2
- return {
3
- get desiredSize() {
4
- return controller.desiredSize;
5
- },
6
- enqueue: frame => {
7
- if (!frame) {
8
- return;
9
- }
10
- if (frame instanceof VideoFrame) {
11
- return controller.enqueue(frame);
12
- }
13
- if (('OffscreenCanvas' in window &&
14
- frame instanceof OffscreenCanvas) ||
15
- frame instanceof HTMLCanvasElement ||
16
- frame instanceof HTMLVideoElement ||
17
- frame instanceof HTMLImageElement ||
18
- frame instanceof ImageBitmap) {
19
- const timestamp = videoFrame.timestamp ?? 0;
20
- try {
21
- // Constructing a new VideoFrame with the modified frame
22
- // could throw an error
23
- const newFrame = new VideoFrame(frame, {
24
- timestamp,
25
- alpha: 'discard',
26
- });
27
- controller.enqueue(newFrame);
28
- videoFrame.close();
29
- }
30
- catch {
31
- controller.enqueue(videoFrame);
32
- }
33
- return;
34
- }
35
- throw new Error('Unexpected input frame');
36
- },
37
- error: reason => controller.error(reason),
38
- terminate: () => controller.terminate(),
39
- };
40
- };
41
- export const nullTransformController = (controller) => {
42
- return {
43
- get desiredSize() {
44
- return 0;
45
- },
46
- enqueue: frame => {
47
- if (('OffscreenCanvas' in window &&
48
- frame instanceof OffscreenCanvas) ||
49
- frame instanceof HTMLCanvasElement ||
50
- frame instanceof HTMLVideoElement ||
51
- frame instanceof HTMLImageElement ||
52
- frame instanceof ImageBitmap) {
53
- return controller.enqueue(frame);
54
- }
55
- throw new Error('Unexpected input frame');
56
- },
57
- error: reason => {
58
- throw new Error(reason);
59
- },
60
- terminate: () => controller.terminate(),
61
- };
62
- };
63
- export const adaptInputFrameTransformer = (transformer) => {
64
- const transform = (frame, controller) => {
65
- return transformer.transform?.(frame, wrapTransformController(frame, controller));
66
- };
67
- return { ...transformer, transform };
68
- };
@@ -1,20 +0,0 @@
1
- import type { InputFrame } from './types';
2
- type Track = MediaStreamVideoTrack | MediaStreamVideoTrackGenerator;
3
- interface Options {
4
- signal?: AbortSignal;
5
- }
6
- export type ProcessVideoTrack = (track: MediaStreamVideoTrack, transformers: Transformer<InputFrame, InputFrame>[], options?: Options) => Promise<Track>;
7
- /**
8
- * Video track processor using MediaStreamTrackProcessor API
9
- */
10
- export declare const createVideoTrackProcessor: () => ProcessVideoTrack;
11
- interface FallbackOptions {
12
- width?: number;
13
- height?: number;
14
- frameRate?: number;
15
- }
16
- /**
17
- * Video track processor using Canvas[captureStream] API
18
- */
19
- export declare const createVideoTrackProcessorWithFallback: ({ width, height, frameRate, }?: FallbackOptions) => ProcessVideoTrack;
20
- export {};
@@ -1,86 +0,0 @@
1
- import { assert } from '@pexip/utils';
2
- import { createMediaStreamTrackProcessor } from '../processor';
3
- import { createMediaStreamTrackGenerator } from '../generator';
4
- import { createStreamTransformer } from '../transformer';
5
- import { stopStreamTracks } from '../utils';
6
- import { PROCESSING_HEIGHT, PROCESSING_WIDTH } from '../../common/constants';
7
- import { getImageSize } from '../../common/canvasRenderUtils';
8
- import { adaptInputFrameTransformer, nullTransformController, } from './transformer';
9
- import { toVideoElement, playVideo, createFrameCallbackRequest, createCanvas, getCanvasRenderingContext2D, } from './utils';
10
- import { AbortReason, FRAME_RATE } from './constants';
11
- /**
12
- * Video track processor using MediaStreamTrackProcessor API
13
- */
14
- export const createVideoTrackProcessor = () => (track, transformers, { signal } = {}) => {
15
- if (!transformers.length) {
16
- return Promise.resolve(track);
17
- }
18
- const processor = createMediaStreamTrackProcessor({ track });
19
- const trackGenerator = createMediaStreamTrackGenerator({ kind: 'video' });
20
- let readable = processor.readable;
21
- for (const transformer of transformers) {
22
- readable = readable.pipeThrough(createStreamTransformer(adaptInputFrameTransformer(transformer)), { signal });
23
- }
24
- // Promise returned from `ReadableStream['pipeTo']` is only resolved
25
- // when the streaming is finished
26
- readable
27
- .pipeTo(trackGenerator.writable, {
28
- signal,
29
- })
30
- .catch(error => {
31
- // Ignore abort error
32
- if (signal &&
33
- !(signal.aborted &&
34
- (signal.reason === AbortReason.Close ||
35
- // AbortSignal['reason'] is only supported from Chromium v98 or Firefox v97
36
- !('reason' in AbortSignal.prototype)))) {
37
- throw error;
38
- }
39
- });
40
- return Promise.resolve(trackGenerator);
41
- };
42
- /**
43
- * Video track processor using Canvas[captureStream] API
44
- */
45
- export const createVideoTrackProcessorWithFallback = ({ width = PROCESSING_WIDTH, height = PROCESSING_HEIGHT, frameRate = FRAME_RATE, } = {}) => async (track, transformers, { signal } = {}) => {
46
- const [transformer] = transformers;
47
- if (!transformer?.transform) {
48
- return track;
49
- }
50
- assert(transformers.length <= 1, 'Multi-transformer is NOT supported');
51
- const outputCanvas = createCanvas(width, height);
52
- const ctx = getCanvasRenderingContext2D(outputCanvas, { alpha: false });
53
- const render = (input) => {
54
- assert(transformer.transform);
55
- return Promise.resolve(transformer.transform(input, nullTransformController({
56
- enqueue: frame => {
57
- if (!frame) {
58
- return;
59
- }
60
- const frameSize = getImageSize(frame);
61
- if (frameSize.height !== outputCanvas.height ||
62
- frameSize.width !== outputCanvas.width) {
63
- outputCanvas.height = frameSize.height;
64
- outputCanvas.width = frameSize.width;
65
- }
66
- ctx.drawImage(frame, 0, 0, outputCanvas.width, outputCanvas.height);
67
- },
68
- terminate: () => {
69
- stop();
70
- },
71
- })));
72
- };
73
- const runner = createFrameCallbackRequest(render, frameRate);
74
- const videoElement = toVideoElement(new MediaStream([track]), width, height);
75
- await playVideo(videoElement);
76
- await runner.start(videoElement);
77
- const stream = outputCanvas.captureStream(frameRate);
78
- const [trackGenerated] = stream.getVideoTracks();
79
- assert(trackGenerated, 'Canvas captureStream returns no video track');
80
- const stop = () => {
81
- stopStreamTracks(stream);
82
- runner.stop();
83
- };
84
- signal?.addEventListener('abort', stop);
85
- return trackGenerated;
86
- };