@remotion/web-renderer 4.0.428 → 4.0.430

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 (71) hide show
  1. package/README.md +7 -7
  2. package/dist/add-sample.js +20 -0
  3. package/dist/artifact.js +56 -0
  4. package/dist/audio.js +42 -0
  5. package/dist/can-use-webfs-target.js +19 -0
  6. package/dist/compose.js +85 -0
  7. package/dist/create-scaffold.js +104 -0
  8. package/dist/drawing/border-radius.js +151 -0
  9. package/dist/drawing/calculate-object-fit.js +208 -0
  10. package/dist/drawing/calculate-transforms.js +127 -0
  11. package/dist/drawing/clamp-rect-to-parent-bounds.js +18 -0
  12. package/dist/drawing/do-rects-intersect.js +6 -0
  13. package/dist/drawing/draw-background.js +62 -0
  14. package/dist/drawing/draw-border.js +353 -0
  15. package/dist/drawing/draw-box-shadow.js +103 -0
  16. package/dist/drawing/draw-dom-element.js +85 -0
  17. package/dist/drawing/draw-element.js +84 -0
  18. package/dist/drawing/draw-outline.js +93 -0
  19. package/dist/drawing/draw-rounded.js +34 -0
  20. package/dist/drawing/drawn-fn.js +1 -0
  21. package/dist/drawing/fit-svg-into-its-dimensions.js +35 -0
  22. package/dist/drawing/get-clipped-background.d.ts +8 -0
  23. package/dist/drawing/get-clipped-background.js +14 -0
  24. package/dist/drawing/get-padding-box.js +30 -0
  25. package/dist/drawing/get-pretransform-rect.js +49 -0
  26. package/dist/drawing/handle-3d-transform.js +26 -0
  27. package/dist/drawing/handle-mask.js +21 -0
  28. package/dist/drawing/has-transform.js +14 -0
  29. package/dist/drawing/mask-image.js +14 -0
  30. package/dist/drawing/opacity.js +7 -0
  31. package/dist/drawing/overflow.js +14 -0
  32. package/dist/drawing/parse-linear-gradient.js +260 -0
  33. package/dist/drawing/parse-transform-origin.js +7 -0
  34. package/dist/drawing/precompose.d.ts +11 -0
  35. package/dist/drawing/precompose.js +14 -0
  36. package/dist/drawing/process-node.js +122 -0
  37. package/dist/drawing/round-to-expand-rect.js +7 -0
  38. package/dist/drawing/text/apply-text-transform.js +12 -0
  39. package/dist/drawing/text/draw-text.js +53 -0
  40. package/dist/drawing/text/find-line-breaks.text.js +118 -0
  41. package/dist/drawing/text/get-collapsed-text.d.ts +1 -0
  42. package/dist/drawing/text/get-collapsed-text.js +46 -0
  43. package/dist/drawing/text/handle-text-node.js +24 -0
  44. package/dist/drawing/text/parse-paint-order.d.ts +8 -0
  45. package/dist/drawing/transform-in-3d.js +177 -0
  46. package/dist/drawing/transform-rect-with-matrix.js +19 -0
  47. package/dist/drawing/transform.js +10 -0
  48. package/dist/drawing/turn-svg-into-drawable.js +41 -0
  49. package/dist/esm/index.mjs +37 -4
  50. package/dist/frame-range.js +15 -0
  51. package/dist/get-audio-encoding-config.js +18 -0
  52. package/dist/get-biggest-bounding-client-rect.js +43 -0
  53. package/dist/index.js +2 -0
  54. package/dist/internal-state.js +36 -0
  55. package/dist/mediabunny-mappings.js +63 -0
  56. package/dist/output-target.js +1 -0
  57. package/dist/props-if-has-props.js +1 -0
  58. package/dist/render-media-on-web.js +304 -0
  59. package/dist/render-operations-queue.js +3 -0
  60. package/dist/render-still-on-web.js +110 -0
  61. package/dist/send-telemetry-event.js +22 -0
  62. package/dist/take-screenshot.js +30 -0
  63. package/dist/throttle-progress.js +43 -0
  64. package/dist/tree-walker-cleanup-after-children.js +33 -0
  65. package/dist/update-time.js +17 -0
  66. package/dist/validate-video-frame.js +34 -0
  67. package/dist/wait-for-ready.js +39 -0
  68. package/dist/walk-tree.js +14 -0
  69. package/dist/web-fs-target.js +41 -0
  70. package/dist/with-resolvers.js +9 -0
  71. package/package.json +10 -9
@@ -0,0 +1,110 @@
1
+ import { Internals, } from 'remotion';
2
+ import { handleArtifacts } from './artifact';
3
+ import { createScaffold } from './create-scaffold';
4
+ import { makeInternalState } from './internal-state';
5
+ import { onlyOneRenderAtATimeQueue } from './render-operations-queue';
6
+ import { sendUsageEvent } from './send-telemetry-event';
7
+ import { takeScreenshot } from './take-screenshot';
8
+ import { waitForReady } from './wait-for-ready';
9
+ async function internalRenderStillOnWeb({ frame, delayRenderTimeoutInMilliseconds, logLevel, inputProps, schema, imageFormat, mediaCacheSizeInBytes, composition, signal, onArtifact, licenseKey, }) {
10
+ var _a, _b, _c, _d, _e, _f;
11
+ const resolved = await Internals.resolveVideoConfig({
12
+ calculateMetadata: (_a = composition.calculateMetadata) !== null && _a !== void 0 ? _a : null,
13
+ signal: signal !== null && signal !== void 0 ? signal : new AbortController().signal,
14
+ defaultProps: (_b = composition.defaultProps) !== null && _b !== void 0 ? _b : {},
15
+ inputProps: inputProps !== null && inputProps !== void 0 ? inputProps : {},
16
+ compositionId: composition.id,
17
+ compositionDurationInFrames: (_c = composition.durationInFrames) !== null && _c !== void 0 ? _c : null,
18
+ compositionFps: (_d = composition.fps) !== null && _d !== void 0 ? _d : null,
19
+ compositionHeight: (_e = composition.height) !== null && _e !== void 0 ? _e : null,
20
+ compositionWidth: (_f = composition.width) !== null && _f !== void 0 ? _f : null,
21
+ });
22
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
23
+ return Promise.reject(new Error('renderStillOnWeb() was cancelled'));
24
+ }
25
+ const internalState = makeInternalState();
26
+ const { delayRenderScope, div, cleanupScaffold, collectAssets } = await createScaffold({
27
+ width: resolved.width,
28
+ height: resolved.height,
29
+ delayRenderTimeoutInMilliseconds,
30
+ logLevel,
31
+ resolvedProps: resolved.props,
32
+ id: resolved.id,
33
+ mediaCacheSizeInBytes,
34
+ audioEnabled: false,
35
+ Component: composition.component,
36
+ videoEnabled: true,
37
+ durationInFrames: resolved.durationInFrames,
38
+ fps: resolved.fps,
39
+ schema: schema !== null && schema !== void 0 ? schema : null,
40
+ initialFrame: frame,
41
+ defaultCodec: resolved.defaultCodec,
42
+ defaultOutName: resolved.defaultOutName,
43
+ });
44
+ const artifactsHandler = handleArtifacts();
45
+ try {
46
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
47
+ throw new Error('renderStillOnWeb() was cancelled');
48
+ }
49
+ await waitForReady({
50
+ timeoutInMilliseconds: delayRenderTimeoutInMilliseconds,
51
+ scope: delayRenderScope,
52
+ signal,
53
+ apiName: 'renderStillOnWeb',
54
+ internalState: null,
55
+ });
56
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
57
+ throw new Error('renderStillOnWeb() was cancelled');
58
+ }
59
+ const imageData = await takeScreenshot({
60
+ div,
61
+ width: resolved.width,
62
+ height: resolved.height,
63
+ imageFormat,
64
+ logLevel,
65
+ internalState,
66
+ });
67
+ const assets = collectAssets.current.collectAssets();
68
+ if (onArtifact) {
69
+ await artifactsHandler.handle({ imageData, frame, assets, onArtifact });
70
+ }
71
+ sendUsageEvent({
72
+ licenseKey: licenseKey !== null && licenseKey !== void 0 ? licenseKey : null,
73
+ succeeded: true,
74
+ apiName: 'renderStillOnWeb',
75
+ });
76
+ return { blob: imageData, internalState };
77
+ }
78
+ catch (err) {
79
+ sendUsageEvent({
80
+ succeeded: false,
81
+ licenseKey: licenseKey !== null && licenseKey !== void 0 ? licenseKey : null,
82
+ apiName: 'renderStillOnWeb',
83
+ }).catch((err2) => {
84
+ Internals.Log.error({ logLevel: 'error', tag: 'web-renderer' }, 'Failed to send usage event', err2);
85
+ });
86
+ throw err;
87
+ }
88
+ finally {
89
+ internalState.cleanup();
90
+ cleanupScaffold();
91
+ }
92
+ }
93
+ export const renderStillOnWeb = (options) => {
94
+ onlyOneRenderAtATimeQueue.ref = onlyOneRenderAtATimeQueue.ref
95
+ .catch(() => Promise.resolve())
96
+ .then(() => {
97
+ var _a, _b, _c, _d, _e, _f, _g, _h;
98
+ return internalRenderStillOnWeb({
99
+ ...options,
100
+ delayRenderTimeoutInMilliseconds: (_a = options.delayRenderTimeoutInMilliseconds) !== null && _a !== void 0 ? _a : 30000,
101
+ logLevel: (_c = (_b = options.logLevel) !== null && _b !== void 0 ? _b : window.remotion_logLevel) !== null && _c !== void 0 ? _c : 'info',
102
+ schema: (_d = options.schema) !== null && _d !== void 0 ? _d : undefined,
103
+ mediaCacheSizeInBytes: (_e = options.mediaCacheSizeInBytes) !== null && _e !== void 0 ? _e : null,
104
+ signal: (_f = options.signal) !== null && _f !== void 0 ? _f : null,
105
+ onArtifact: (_g = options.onArtifact) !== null && _g !== void 0 ? _g : null,
106
+ licenseKey: (_h = options.licenseKey) !== null && _h !== void 0 ? _h : undefined,
107
+ });
108
+ });
109
+ return onlyOneRenderAtATimeQueue.ref;
110
+ };
@@ -0,0 +1,22 @@
1
+ import { registerUsageEvent } from '@remotion/licensing';
2
+ import { Internals } from 'remotion';
3
+ export const sendUsageEvent = async ({ licenseKey, succeeded, apiName, }) => {
4
+ var _a;
5
+ const host = typeof window === 'undefined'
6
+ ? null
7
+ : typeof window.location === 'undefined'
8
+ ? null
9
+ : ((_a = window.location.origin) !== null && _a !== void 0 ? _a : null);
10
+ if (host === null) {
11
+ return;
12
+ }
13
+ if (licenseKey === null) {
14
+ Internals.Log.warn({ logLevel: 'warn', tag: 'web-renderer' }, `Pass "licenseKey" to ${apiName}(). If you qualify for the Free License (https://remotion.dev/license), pass "free-license" instead.`);
15
+ }
16
+ await registerUsageEvent({
17
+ licenseKey: licenseKey === 'free-license' ? null : licenseKey,
18
+ event: 'webcodec-conversion',
19
+ host,
20
+ succeeded,
21
+ });
22
+ };
@@ -0,0 +1,30 @@
1
+ import { compose } from './compose';
2
+ export const createFrame = async ({ div, width, height, logLevel, internalState, }) => {
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({
9
+ element: div,
10
+ context,
11
+ logLevel,
12
+ parentRect: new DOMRect(0, 0, width, height),
13
+ internalState,
14
+ onlyBackgroundClip: false,
15
+ });
16
+ return canvas;
17
+ };
18
+ export const takeScreenshot = async ({ div, width, height, imageFormat, logLevel, internalState, }) => {
19
+ const frame = await createFrame({
20
+ div,
21
+ width,
22
+ height,
23
+ logLevel,
24
+ internalState,
25
+ });
26
+ const imageData = await frame.convertToBlob({
27
+ type: `image/${imageFormat}`,
28
+ });
29
+ return imageData;
30
+ };
@@ -0,0 +1,43 @@
1
+ const DEFAULT_THROTTLE_MS = 250;
2
+ /**
3
+ * Creates a throttled version of a progress callback that ensures it's not called
4
+ * more frequently than the specified interval (default: 250ms)
5
+ */
6
+ export const createThrottledProgressCallback = (callback, throttleMs = DEFAULT_THROTTLE_MS) => {
7
+ if (!callback) {
8
+ return null;
9
+ }
10
+ let lastCallTime = 0;
11
+ let pendingUpdate = null;
12
+ let timeoutId = null;
13
+ const throttled = (progress) => {
14
+ const now = Date.now();
15
+ const timeSinceLastCall = now - lastCallTime;
16
+ // Always store the latest progress
17
+ pendingUpdate = progress;
18
+ // If enough time has passed, call immediately
19
+ if (timeSinceLastCall >= throttleMs) {
20
+ lastCallTime = now;
21
+ callback(progress);
22
+ pendingUpdate = null;
23
+ // Clear any pending timeout
24
+ if (timeoutId !== null) {
25
+ clearTimeout(timeoutId);
26
+ timeoutId = null;
27
+ }
28
+ }
29
+ else if (timeoutId === null) {
30
+ // Schedule a call for when the throttle period expires
31
+ const remainingTime = throttleMs - timeSinceLastCall;
32
+ timeoutId = setTimeout(() => {
33
+ if (pendingUpdate !== null) {
34
+ lastCallTime = Date.now();
35
+ callback(pendingUpdate);
36
+ pendingUpdate = null;
37
+ }
38
+ timeoutId = null;
39
+ }, remainingTime);
40
+ }
41
+ };
42
+ return throttled;
43
+ };
@@ -0,0 +1,33 @@
1
+ export const createTreeWalkerCleanupAfterChildren = (treeWalker) => {
2
+ const cleanupAfterChildren = [];
3
+ const checkCleanUpAtBeginningOfIteration = () => {
4
+ for (let i = 0; i < cleanupAfterChildren.length;) {
5
+ const cleanup = cleanupAfterChildren[i];
6
+ if (!(cleanup.element === treeWalker.currentNode ||
7
+ cleanup.element.contains(treeWalker.currentNode))) {
8
+ cleanup.cleanupFn();
9
+ cleanupAfterChildren.splice(i, 1);
10
+ }
11
+ else {
12
+ i++;
13
+ }
14
+ }
15
+ };
16
+ const addCleanup = (element, cleanupFn) => {
17
+ // Last registered must be cleaned up first
18
+ cleanupAfterChildren.unshift({
19
+ element,
20
+ cleanupFn,
21
+ });
22
+ };
23
+ const cleanupInTheEndOfTheIteration = () => {
24
+ for (const cleanup of cleanupAfterChildren) {
25
+ cleanup.cleanupFn();
26
+ }
27
+ };
28
+ return {
29
+ checkCleanUpAtBeginningOfIteration,
30
+ addCleanup,
31
+ cleanupInTheEndOfTheIteration,
32
+ };
33
+ };
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useImperativeHandle, useState } from 'react';
3
+ import { flushSync } from 'react-dom';
4
+ import { Internals } from 'remotion';
5
+ export const UpdateTime = ({ children, audioEnabled, videoEnabled, logLevel, compId, initialFrame, timeUpdater, }) => {
6
+ const [frame, setFrame] = useState(initialFrame);
7
+ useImperativeHandle(timeUpdater, () => ({
8
+ update: (f) => {
9
+ flushSync(() => {
10
+ setFrame(f);
11
+ });
12
+ },
13
+ }));
14
+ return (_jsx(Internals.RemotionRootContexts, { audioEnabled: audioEnabled, videoEnabled: videoEnabled, logLevel: logLevel, numberOfAudioTags: 0, audioLatencyHint: "interactive", frameState: {
15
+ [compId]: frame,
16
+ }, children: children }));
17
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Validates and processes a VideoFrame returned from an onFrame callback
3
+ */
4
+ /**
5
+ * Validates that a VideoFrame returned from onFrame callback matches expected dimensions and timestamp
6
+ * If validation fails, closes both frames and throws an error
7
+ */
8
+ export const validateVideoFrame = ({ originalFrame, returnedFrame, expectedWidth, expectedHeight, expectedTimestamp, }) => {
9
+ // Validate that the returned frame is actually a VideoFrame
10
+ if (!(returnedFrame instanceof VideoFrame)) {
11
+ originalFrame.close();
12
+ throw new Error('onFrame callback must return a VideoFrame or void');
13
+ }
14
+ // Check if it's the same frame (no validation needed)
15
+ if (returnedFrame === originalFrame) {
16
+ return returnedFrame;
17
+ }
18
+ // Validate dimensions
19
+ if (returnedFrame.displayWidth !== expectedWidth ||
20
+ returnedFrame.displayHeight !== expectedHeight) {
21
+ originalFrame.close();
22
+ returnedFrame.close();
23
+ throw new Error(`VideoFrame dimensions mismatch: expected ${expectedWidth}x${expectedHeight}, got ${returnedFrame.displayWidth}x${returnedFrame.displayHeight}`);
24
+ }
25
+ // Validate timestamp
26
+ if (returnedFrame.timestamp !== expectedTimestamp) {
27
+ originalFrame.close();
28
+ returnedFrame.close();
29
+ throw new Error(`VideoFrame timestamp mismatch: expected ${expectedTimestamp}, got ${returnedFrame.timestamp}`);
30
+ }
31
+ // If we got a different frame but it's valid, close the original and use the new one
32
+ originalFrame.close();
33
+ return returnedFrame;
34
+ };
@@ -0,0 +1,39 @@
1
+ import { withResolvers } from './with-resolvers';
2
+ export const waitForReady = ({ timeoutInMilliseconds, scope, signal, apiName, internalState, }) => {
3
+ const start = performance.now();
4
+ const { promise, resolve, reject } = withResolvers();
5
+ let cancelled = false;
6
+ const check = () => {
7
+ if (cancelled) {
8
+ return;
9
+ }
10
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
11
+ cancelled = true;
12
+ internalState === null || internalState === void 0 ? void 0 : internalState.addWaitForReadyTime(performance.now() - start);
13
+ reject(new Error(`${apiName}() was cancelled`));
14
+ return;
15
+ }
16
+ if (scope.remotion_renderReady === true) {
17
+ internalState === null || internalState === void 0 ? void 0 : internalState.addWaitForReadyTime(performance.now() - start);
18
+ resolve();
19
+ return;
20
+ }
21
+ if (scope.remotion_cancelledError !== undefined) {
22
+ cancelled = true;
23
+ internalState === null || internalState === void 0 ? void 0 : internalState.addWaitForReadyTime(performance.now() - start);
24
+ reject(scope.remotion_cancelledError);
25
+ return;
26
+ }
27
+ if (performance.now() - start > timeoutInMilliseconds + 3000) {
28
+ cancelled = true;
29
+ internalState === null || internalState === void 0 ? void 0 : internalState.addWaitForReadyTime(performance.now() - start);
30
+ reject(new Error(Object.values(scope.remotion_delayRenderTimeouts)
31
+ .map((d) => d.label)
32
+ .join(', ')));
33
+ return;
34
+ }
35
+ requestAnimationFrame(check);
36
+ };
37
+ requestAnimationFrame(check);
38
+ return promise;
39
+ };
@@ -0,0 +1,14 @@
1
+ export function skipToNextNonDescendant(treeWalker) {
2
+ // Try to go to next sibling
3
+ if (treeWalker.nextSibling()) {
4
+ return true;
5
+ }
6
+ // No sibling, go up to parent and try to find ancestor's sibling
7
+ while (treeWalker.parentNode()) {
8
+ if (treeWalker.nextSibling()) {
9
+ return true;
10
+ }
11
+ }
12
+ // No more nodes
13
+ return false;
14
+ }
@@ -0,0 +1,41 @@
1
+ let sessionId = null;
2
+ const getPrefix = () => {
3
+ if (!sessionId) {
4
+ sessionId = crypto.randomUUID();
5
+ }
6
+ return `__remotion_render:${sessionId}:`;
7
+ };
8
+ export const cleanupStaleOpfsFiles = async () => {
9
+ try {
10
+ const root = await navigator.storage.getDirectory();
11
+ for await (const [name] of root.entries()) {
12
+ if (name.startsWith('__remotion_render:') &&
13
+ !name.startsWith(getPrefix())) {
14
+ await root.removeEntry(name);
15
+ }
16
+ }
17
+ }
18
+ catch (_a) {
19
+ // Ignore, could already be closed
20
+ }
21
+ };
22
+ export const createWebFsTarget = async () => {
23
+ const directoryHandle = await navigator.storage.getDirectory();
24
+ const filename = `${getPrefix()}${crypto.randomUUID()}`;
25
+ const fileHandle = await directoryHandle.getFileHandle(filename, {
26
+ create: true,
27
+ });
28
+ const writable = await fileHandle.createWritable();
29
+ const stream = new WritableStream({
30
+ async write(chunk) {
31
+ await writable.seek(chunk.position);
32
+ await writable.write(chunk);
33
+ },
34
+ });
35
+ const getBlob = async () => {
36
+ const handle = await directoryHandle.getFileHandle(filename);
37
+ return handle.getFile();
38
+ };
39
+ const close = () => writable.close();
40
+ return { stream, getBlob, close };
41
+ };
@@ -0,0 +1,9 @@
1
+ export const withResolvers = function () {
2
+ let resolve;
3
+ let reject;
4
+ const promise = new Promise((res, rej) => {
5
+ resolve = res;
6
+ reject = rej;
7
+ });
8
+ return { promise, resolve: resolve, reject: reject };
9
+ };
package/package.json CHANGED
@@ -3,11 +3,12 @@
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.428",
6
+ "version": "4.0.430",
7
7
  "main": "dist/index.js",
8
8
  "type": "module",
9
9
  "scripts": {
10
- "formatting": "prettier src --check",
10
+ "formatting": "oxfmt src --check",
11
+ "format": "oxfmt src",
11
12
  "lint": "eslint src",
12
13
  "make": "tsgo && bun --env-file=../.env.bundle bundle.ts",
13
14
  "watch": "tsgo -w",
@@ -18,16 +19,16 @@
18
19
  "author": "Remotion <jonny@remotion.dev>",
19
20
  "license": "UNLICENSED",
20
21
  "dependencies": {
21
- "@remotion/licensing": "4.0.428",
22
- "remotion": "4.0.428",
23
- "mediabunny": "1.34.4"
22
+ "@remotion/licensing": "4.0.429",
23
+ "remotion": "4.0.429",
24
+ "mediabunny": "1.35.1"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@react-three/fiber": "9.2.0",
27
- "@remotion/eslint-config-internal": "4.0.428",
28
- "@remotion/player": "4.0.428",
29
- "@remotion/media": "4.0.428",
30
- "@remotion/three": "4.0.428",
28
+ "@remotion/eslint-config-internal": "4.0.429",
29
+ "@remotion/player": "4.0.429",
30
+ "@remotion/media": "4.0.429",
31
+ "@remotion/three": "4.0.429",
31
32
  "@types/three": "0.170.0",
32
33
  "@typescript/native-preview": "7.0.0-dev.20260217.1",
33
34
  "@vitejs/plugin-react": "4.3.4",