@remotion/web-renderer 4.0.383 → 4.0.385

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 (51) hide show
  1. package/dist/add-sample.d.ts +3 -0
  2. package/dist/add-sample.js +20 -0
  3. package/dist/artifact.d.ts +4 -5
  4. package/dist/artifact.js +12 -15
  5. package/dist/audio.d.ts +6 -0
  6. package/dist/audio.js +38 -0
  7. package/dist/border-radius.d.ts +31 -0
  8. package/dist/border-radius.js +152 -0
  9. package/dist/calculate-transforms.d.ts +2 -0
  10. package/dist/calculate-transforms.js +17 -0
  11. package/dist/composable.d.ts +2 -8
  12. package/dist/compose-canvas.js +28 -4
  13. package/dist/compose.d.ts +1 -6
  14. package/dist/compose.js +16 -11
  15. package/dist/drawing/border-radius.d.ts +31 -0
  16. package/dist/drawing/border-radius.js +152 -0
  17. package/dist/drawing/calculate-transforms.d.ts +10 -0
  18. package/dist/drawing/calculate-transforms.js +81 -0
  19. package/dist/drawing/compose-canvas.d.ts +1 -0
  20. package/dist/drawing/compose-canvas.js +36 -0
  21. package/dist/drawing/compose-svg.d.ts +1 -0
  22. package/dist/drawing/compose-svg.js +34 -0
  23. package/dist/drawing/compose.d.ts +5 -0
  24. package/dist/drawing/compose.js +6 -0
  25. package/dist/drawing/draw-border.d.ts +10 -0
  26. package/dist/drawing/draw-border.js +101 -0
  27. package/dist/drawing/draw-element-to-canvas.d.ts +4 -0
  28. package/dist/drawing/draw-element-to-canvas.js +72 -0
  29. package/dist/drawing/get-computed-style-cache.d.ts +0 -0
  30. package/dist/drawing/get-computed-style-cache.js +1 -0
  31. package/dist/drawing/opacity.d.ts +4 -0
  32. package/dist/drawing/opacity.js +7 -0
  33. package/dist/drawing/parse-transform-origin.d.ts +4 -0
  34. package/dist/drawing/parse-transform-origin.js +7 -0
  35. package/dist/drawing/transform.d.ts +4 -0
  36. package/dist/drawing/transform.js +6 -0
  37. package/dist/drawing/turn-svg-into-drawable.d.ts +1 -0
  38. package/dist/drawing/turn-svg-into-drawable.js +34 -0
  39. package/dist/esm/index.mjs +1630 -125
  40. package/dist/find-capturable-elements.d.ts +1 -1
  41. package/dist/find-capturable-elements.js +20 -22
  42. package/dist/get-audio-encoding-config.d.ts +2 -0
  43. package/dist/get-audio-encoding-config.js +18 -0
  44. package/dist/opacity.d.ts +4 -0
  45. package/dist/opacity.js +7 -0
  46. package/dist/render-media-on-web.js +42 -11
  47. package/dist/render-still-on-web.js +5 -5
  48. package/dist/take-screenshot.js +7 -8
  49. package/dist/transform.d.ts +4 -0
  50. package/dist/transform.js +6 -0
  51. package/package.json +6 -6
@@ -1,2 +1,2 @@
1
1
  import type { Composable } from './composable';
2
- export declare const findCapturableElements: (element: HTMLDivElement) => Composable[];
2
+ export declare const findCapturableElements: (element: HTMLDivElement, context: OffscreenCanvasRenderingContext2D) => Promise<Composable[]>;
@@ -1,28 +1,26 @@
1
- export const findCapturableElements = (element) => {
2
- const canvasAndSvgElements = element.querySelectorAll('svg,canvas,img');
3
- const composables = [];
4
- Array.from(canvasAndSvgElements).forEach((capturableElement) => {
5
- if (capturableElement.tagName.toLocaleLowerCase() === 'canvas') {
6
- const canvas = capturableElement;
7
- composables.push({
8
- type: 'canvas',
9
- element: canvas,
10
- });
1
+ import { drawElementToCanvas } from './drawing/draw-element-to-canvas';
2
+ export const findCapturableElements = async (element, context) => {
3
+ const treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, (node) => {
4
+ if (node instanceof Element) {
5
+ const computedStyle = getComputedStyle(node);
6
+ return computedStyle.display === 'none'
7
+ ? NodeFilter.FILTER_REJECT
8
+ : NodeFilter.FILTER_ACCEPT;
11
9
  }
12
- else if (capturableElement.tagName.toLocaleLowerCase() === 'svg') {
13
- const svg = capturableElement;
14
- composables.push({
15
- type: 'svg',
16
- element: svg,
17
- });
18
- }
19
- else if (capturableElement.tagName.toLocaleLowerCase() === 'img') {
20
- const img = capturableElement;
10
+ return NodeFilter.FILTER_ACCEPT;
11
+ });
12
+ const composables = [];
13
+ while (treeWalker.nextNode()) {
14
+ const node = treeWalker.currentNode;
15
+ if (node instanceof HTMLCanvasElement ||
16
+ node instanceof SVGSVGElement ||
17
+ node instanceof HTMLImageElement) {
18
+ await drawElementToCanvas(node, context);
21
19
  composables.push({
22
- type: 'img',
23
- element: img,
20
+ type: 'element',
21
+ element: node,
24
22
  });
25
23
  }
26
- });
24
+ }
27
25
  return composables;
28
26
  };
@@ -0,0 +1,2 @@
1
+ import { type AudioEncodingConfig } from 'mediabunny';
2
+ export declare const getDefaultAudioEncodingConfig: () => Promise<AudioEncodingConfig | null>;
@@ -0,0 +1,18 @@
1
+ import { canEncodeAudio, QUALITY_MEDIUM, } from 'mediabunny';
2
+ export const getDefaultAudioEncodingConfig = async () => {
3
+ const preferredDefaultAudioEncodingConfig = {
4
+ codec: 'aac',
5
+ bitrate: QUALITY_MEDIUM,
6
+ };
7
+ if (await canEncodeAudio(preferredDefaultAudioEncodingConfig.codec, preferredDefaultAudioEncodingConfig)) {
8
+ return preferredDefaultAudioEncodingConfig;
9
+ }
10
+ const backupDefaultAudioEncodingConfig = {
11
+ codec: 'opus',
12
+ bitrate: QUALITY_MEDIUM,
13
+ };
14
+ if (await canEncodeAudio(backupDefaultAudioEncodingConfig.codec, backupDefaultAudioEncodingConfig)) {
15
+ return backupDefaultAudioEncodingConfig;
16
+ }
17
+ return null;
18
+ };
@@ -0,0 +1,4 @@
1
+ export declare const setOpacity: ({ ctx, opacity, }: {
2
+ ctx: OffscreenCanvasRenderingContext2D;
3
+ opacity: number;
4
+ }) => () => void;
@@ -0,0 +1,7 @@
1
+ export const setOpacity = ({ ctx, opacity, }) => {
2
+ const previousAlpha = ctx.globalAlpha;
3
+ ctx.globalAlpha = opacity;
4
+ return () => {
5
+ ctx.globalAlpha = previousAlpha;
6
+ };
7
+ };
@@ -1,8 +1,11 @@
1
- import { BufferTarget, Output, VideoSample, VideoSampleSource } from 'mediabunny';
1
+ import { AudioSampleSource, BufferTarget, Output, VideoSampleSource, } from 'mediabunny';
2
2
  import { Internals } from 'remotion';
3
+ import { addAudioSample, addVideoSampleAndCloseFrame } from './add-sample';
3
4
  import { handleArtifacts } from './artifact';
5
+ import { onlyInlineAudio } from './audio';
4
6
  import { createScaffold } from './create-scaffold';
5
7
  import { getRealFrameRange } from './frame-range';
8
+ import { getDefaultAudioEncodingConfig } from './get-audio-encoding-config';
6
9
  import { codecToMediabunnyCodec, containerToMediabunnyContainer, getDefaultVideoCodecForContainer, getQualityForWebRendererQuality, } from './mediabunny-mappings';
7
10
  import { createFrame } from './take-screenshot';
8
11
  import { createThrottledProgressCallback } from './throttle-progress';
@@ -55,10 +58,7 @@ const internalRenderMediaOnWeb = async ({ composition, inputProps, delayRenderTi
55
58
  defaultCodec: resolved.defaultCodec,
56
59
  defaultOutName: resolved.defaultOutName,
57
60
  });
58
- const artifactsHandler = handleArtifacts({
59
- ref: collectAssets,
60
- onArtifact,
61
- });
61
+ const artifactsHandler = handleArtifacts();
62
62
  cleanupFns.push(() => {
63
63
  cleanupScaffold();
64
64
  });
@@ -100,6 +100,16 @@ const internalRenderMediaOnWeb = async ({ composition, inputProps, delayRenderTi
100
100
  videoSampleSource.close();
101
101
  });
102
102
  output.addVideoTrack(videoSampleSource);
103
+ // TODO: Should be able to customize
104
+ const defaultAudioEncodingConfig = await getDefaultAudioEncodingConfig();
105
+ if (!defaultAudioEncodingConfig) {
106
+ return Promise.reject(new Error('No default audio encoding config found'));
107
+ }
108
+ const audioSampleSource = new AudioSampleSource(defaultAudioEncodingConfig);
109
+ cleanupFns.push(() => {
110
+ audioSampleSource.close();
111
+ });
112
+ output.addAudioTrack(audioSampleSource);
103
113
  await output.start();
104
114
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
105
115
  throw new Error('renderMediaOnWeb() was cancelled');
@@ -109,8 +119,11 @@ const internalRenderMediaOnWeb = async ({ composition, inputProps, delayRenderTi
109
119
  encodedFrames: 0,
110
120
  };
111
121
  const throttledOnProgress = createThrottledProgressCallback(onProgress);
112
- for (let i = realFrameRange[0]; i <= realFrameRange[1]; i++) {
113
- (_g = timeUpdater.current) === null || _g === void 0 ? void 0 : _g.update(i);
122
+ for (let frame = realFrameRange[0]; frame <= realFrameRange[1]; frame++) {
123
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
124
+ throw new Error('renderMediaOnWeb() was cancelled');
125
+ }
126
+ (_g = timeUpdater.current) === null || _g === void 0 ? void 0 : _g.update(frame);
114
127
  await waitForReady({
115
128
  timeoutInMilliseconds: delayRenderTimeoutInMilliseconds,
116
129
  scope: delayRenderScope,
@@ -125,11 +138,23 @@ const internalRenderMediaOnWeb = async ({ composition, inputProps, delayRenderTi
125
138
  width: resolved.width,
126
139
  height: resolved.height,
127
140
  });
128
- await artifactsHandler.handle({ imageData, frame: i });
129
141
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
130
142
  throw new Error('renderMediaOnWeb() was cancelled');
131
143
  }
132
- const timestamp = Math.round(((i - realFrameRange[0]) / resolved.fps) * 1000000);
144
+ const assets = collectAssets.current.collectAssets();
145
+ if (onArtifact) {
146
+ await artifactsHandler.handle({
147
+ imageData,
148
+ frame,
149
+ assets,
150
+ onArtifact,
151
+ });
152
+ }
153
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
154
+ throw new Error('renderMediaOnWeb() was cancelled');
155
+ }
156
+ const audio = onlyInlineAudio({ assets, fps: resolved.fps, frame });
157
+ const timestamp = Math.round(((frame - realFrameRange[0]) / resolved.fps) * 1000000);
133
158
  const videoFrame = new VideoFrame(imageData, {
134
159
  timestamp,
135
160
  });
@@ -139,6 +164,9 @@ const internalRenderMediaOnWeb = async ({ composition, inputProps, delayRenderTi
139
164
  let frameToEncode = videoFrame;
140
165
  if (onFrame) {
141
166
  const returnedFrame = await onFrame(videoFrame);
167
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
168
+ throw new Error('renderMediaOnWeb() was cancelled');
169
+ }
142
170
  frameToEncode = validateVideoFrame({
143
171
  originalFrame: videoFrame,
144
172
  returnedFrame,
@@ -147,10 +175,12 @@ const internalRenderMediaOnWeb = async ({ composition, inputProps, delayRenderTi
147
175
  expectedTimestamp: timestamp,
148
176
  });
149
177
  }
150
- await videoSampleSource.add(new VideoSample(frameToEncode));
178
+ await Promise.all([
179
+ addVideoSampleAndCloseFrame(frameToEncode, videoSampleSource),
180
+ audio ? addAudioSample(audio, audioSampleSource) : Promise.resolve(),
181
+ ]);
151
182
  progress.encodedFrames++;
152
183
  throttledOnProgress === null || throttledOnProgress === void 0 ? void 0 : throttledOnProgress({ ...progress });
153
- frameToEncode.close();
154
184
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
155
185
  throw new Error('renderMediaOnWeb() was cancelled');
156
186
  }
@@ -158,6 +188,7 @@ const internalRenderMediaOnWeb = async ({ composition, inputProps, delayRenderTi
158
188
  // Call progress one final time to ensure final state is reported
159
189
  onProgress === null || onProgress === void 0 ? void 0 : onProgress({ ...progress });
160
190
  videoSampleSource.close();
191
+ audioSampleSource.close();
161
192
  await output.finalize();
162
193
  return output.target.buffer;
163
194
  }
@@ -37,10 +37,7 @@ async function internalRenderStillOnWeb({ frame, delayRenderTimeoutInMillisecond
37
37
  defaultCodec: resolved.defaultCodec,
38
38
  defaultOutName: resolved.defaultOutName,
39
39
  });
40
- const artifactsHandler = handleArtifacts({
41
- ref: collectAssets,
42
- onArtifact,
43
- });
40
+ const artifactsHandler = handleArtifacts();
44
41
  try {
45
42
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
46
43
  throw new Error('renderStillOnWeb() was cancelled');
@@ -60,7 +57,10 @@ async function internalRenderStillOnWeb({ frame, delayRenderTimeoutInMillisecond
60
57
  height: resolved.height,
61
58
  imageFormat,
62
59
  });
63
- await artifactsHandler.handle({ imageData, frame });
60
+ const assets = collectAssets.current.collectAssets();
61
+ if (onArtifact) {
62
+ await artifactsHandler.handle({ imageData, frame, assets, onArtifact });
63
+ }
64
64
  return imageData;
65
65
  }
66
66
  finally {
@@ -1,13 +1,12 @@
1
1
  import { compose } from './compose';
2
- import { findCapturableElements } from './find-capturable-elements';
3
2
  export const createFrame = async ({ div, width, height, }) => {
4
- const composables = findCapturableElements(div);
5
- const composed = await compose({
6
- composables,
7
- width,
8
- height,
9
- });
10
- return composed;
3
+ const canvas = new OffscreenCanvas(width, height);
4
+ const context = canvas.getContext('2d');
5
+ if (!context) {
6
+ throw new Error('Could not get context');
7
+ }
8
+ await compose(div, context);
9
+ return canvas;
11
10
  };
12
11
  export const takeScreenshot = async ({ div, width, height, imageFormat, }) => {
13
12
  const frame = await createFrame({ div, width, height });
@@ -0,0 +1,4 @@
1
+ export declare const setTransform: ({ ctx, transform, }: {
2
+ ctx: OffscreenCanvasRenderingContext2D;
3
+ transform: DOMMatrix;
4
+ }) => () => void;
@@ -0,0 +1,6 @@
1
+ export const setTransform = ({ ctx, transform, }) => {
2
+ ctx.setTransform(transform);
3
+ return () => {
4
+ ctx.setTransform(new DOMMatrix());
5
+ };
6
+ };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/web-renderer"
4
4
  },
5
5
  "name": "@remotion/web-renderer",
6
- "version": "4.0.383",
6
+ "version": "4.0.385",
7
7
  "main": "dist/index.js",
8
8
  "sideEffects": false,
9
9
  "scripts": {
@@ -16,13 +16,13 @@
16
16
  "author": "Remotion <jonny@remotion.dev>",
17
17
  "license": "UNLICENSED",
18
18
  "dependencies": {
19
- "remotion": "4.0.383",
20
- "mediabunny": "1.25.3"
19
+ "remotion": "4.0.385",
20
+ "mediabunny": "1.25.8"
21
21
  },
22
22
  "devDependencies": {
23
- "@remotion/eslint-config-internal": "4.0.383",
24
- "@remotion/player": "4.0.383",
25
- "@remotion/media": "4.0.383",
23
+ "@remotion/eslint-config-internal": "4.0.385",
24
+ "@remotion/player": "4.0.385",
25
+ "@remotion/media": "4.0.385",
26
26
  "@vitejs/plugin-react": "4.1.0",
27
27
  "@vitest/browser-playwright": "4.0.9",
28
28
  "playwright": "1.55.1",