@remotion/web-renderer 4.0.375 → 4.0.377

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 (46) hide show
  1. package/dist/artifact.d.ts +24 -0
  2. package/dist/artifact.js +59 -0
  3. package/dist/calculate-transforms.d.ts +9 -0
  4. package/dist/calculate-transforms.js +61 -0
  5. package/dist/composable.d.ts +3 -0
  6. package/dist/compose-canvas.d.ts +1 -0
  7. package/dist/compose-canvas.js +18 -0
  8. package/dist/compose-svg.d.ts +1 -0
  9. package/dist/compose-svg.js +34 -0
  10. package/dist/compose.js +2 -42
  11. package/dist/create-scaffold.d.ts +31 -0
  12. package/dist/create-scaffold.js +92 -0
  13. package/dist/esm/index.mjs +7846 -179
  14. package/dist/find-capturable-elements.d.ts +2 -0
  15. package/dist/find-capturable-elements.js +28 -0
  16. package/dist/frame-range.d.ts +2 -0
  17. package/dist/frame-range.js +15 -0
  18. package/dist/index.d.ts +5 -0
  19. package/dist/index.js +1 -0
  20. package/dist/mediabunny-mappings.d.ts +9 -0
  21. package/dist/mediabunny-mappings.js +53 -0
  22. package/dist/parse-transform-origin.d.ts +4 -0
  23. package/dist/parse-transform-origin.js +7 -0
  24. package/dist/props-if-has-props.d.ts +37 -0
  25. package/dist/props-if-has-props.js +1 -0
  26. package/dist/render-media-on-web.d.ts +45 -0
  27. package/dist/render-media-on-web.js +190 -0
  28. package/dist/render-still-on-web.d.ts +17 -12
  29. package/dist/render-still-on-web.js +69 -104
  30. package/dist/take-screenshot.d.ts +12 -0
  31. package/dist/take-screenshot.js +18 -0
  32. package/dist/throttle-progress.d.ts +6 -0
  33. package/dist/throttle-progress.js +43 -0
  34. package/dist/update-time.d.ts +14 -0
  35. package/dist/update-time.js +17 -0
  36. package/dist/validate-video-frame.d.ts +16 -0
  37. package/dist/validate-video-frame.js +34 -0
  38. package/dist/wait-for-ready.d.ts +6 -1
  39. package/dist/wait-for-ready.js +18 -14
  40. package/package.json +13 -8
  41. package/dist/error-boundary.d.ts +0 -16
  42. package/dist/error-boundary.js +0 -20
  43. package/dist/find-canvas-elements.d.ts +0 -2
  44. package/dist/find-canvas-elements.js +0 -12
  45. package/dist/find-svg-elements.d.ts +0 -2
  46. package/dist/find-svg-elements.js +0 -12
@@ -0,0 +1,12 @@
1
+ import type { RenderStillOnWebImageFormat } from './render-still-on-web';
2
+ export declare const createFrame: ({ div, width, height, }: {
3
+ div: HTMLDivElement;
4
+ width: number;
5
+ height: number;
6
+ }) => Promise<OffscreenCanvas>;
7
+ export declare const takeScreenshot: ({ div, width, height, imageFormat, }: {
8
+ div: HTMLDivElement;
9
+ width: number;
10
+ height: number;
11
+ imageFormat: RenderStillOnWebImageFormat;
12
+ }) => Promise<Blob>;
@@ -0,0 +1,18 @@
1
+ import { compose } from './compose';
2
+ import { findCapturableElements } from './find-capturable-elements';
3
+ 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;
11
+ };
12
+ export const takeScreenshot = async ({ div, width, height, imageFormat, }) => {
13
+ const frame = await createFrame({ div, width, height });
14
+ const imageData = await frame.convertToBlob({
15
+ type: `image/${imageFormat}`,
16
+ });
17
+ return imageData;
18
+ };
@@ -0,0 +1,6 @@
1
+ import type { RenderMediaOnWebProgressCallback } from './render-media-on-web';
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 declare const createThrottledProgressCallback: (callback: RenderMediaOnWebProgressCallback | null, throttleMs?: number) => RenderMediaOnWebProgressCallback | null;
@@ -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,14 @@
1
+ import React from 'react';
2
+ import type { LogLevel } from 'remotion';
3
+ export type TimeUpdaterRef = {
4
+ update: (frame: number) => void;
5
+ };
6
+ export declare const UpdateTime: React.FC<{
7
+ readonly children: React.ReactNode;
8
+ readonly audioEnabled: boolean;
9
+ readonly videoEnabled: boolean;
10
+ readonly logLevel: LogLevel;
11
+ readonly compId: string;
12
+ readonly initialFrame: number;
13
+ readonly timeUpdater: React.RefObject<TimeUpdaterRef | null>;
14
+ }>;
@@ -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,16 @@
1
+ /**
2
+ * Validates and processes a VideoFrame returned from an onFrame callback
3
+ */
4
+ export type OnFrameCallback = (frame: VideoFrame) => VideoFrame | Promise<VideoFrame>;
5
+ export type ValidateVideoFrameOptions = {
6
+ originalFrame: VideoFrame;
7
+ returnedFrame: VideoFrame;
8
+ expectedWidth: number;
9
+ expectedHeight: number;
10
+ expectedTimestamp: number;
11
+ };
12
+ /**
13
+ * Validates that a VideoFrame returned from onFrame callback matches expected dimensions and timestamp
14
+ * If validation fails, closes both frames and throws an error
15
+ */
16
+ export declare const validateVideoFrame: ({ originalFrame, returnedFrame, expectedWidth, expectedHeight, expectedTimestamp, }: ValidateVideoFrameOptions) => VideoFrame;
@@ -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
+ };
@@ -1,2 +1,7 @@
1
1
  import type { _InternalTypes } from 'remotion';
2
- export declare const waitForReady: (timeoutInMilliseconds: number, scope: _InternalTypes["DelayRenderScope"]) => Promise<void>;
2
+ export declare const waitForReady: ({ timeoutInMilliseconds, scope, signal, apiName, }: {
3
+ timeoutInMilliseconds: number;
4
+ scope: _InternalTypes["DelayRenderScope"];
5
+ signal: AbortSignal | null;
6
+ apiName: "renderMediaOnWeb" | "renderStillOnWeb";
7
+ }) => Promise<void>;
@@ -1,31 +1,35 @@
1
1
  import { withResolvers } from './with-resolvers';
2
- export const waitForReady = (timeoutInMilliseconds, scope) => {
3
- if (scope.remotion_renderReady === true) {
4
- return Promise.resolve();
5
- }
2
+ export const waitForReady = ({ timeoutInMilliseconds, scope, signal, apiName, }) => {
6
3
  const start = Date.now();
7
4
  const { promise, resolve, reject } = withResolvers();
8
- const interval = setInterval(() => {
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
+ reject(new Error(`${apiName}() was cancelled`));
13
+ return;
14
+ }
9
15
  if (scope.remotion_renderReady === true) {
10
- // Wait for useEffects() to apply
11
- requestAnimationFrame(() => {
12
- resolve();
13
- });
14
- clearInterval(interval);
16
+ resolve();
15
17
  return;
16
18
  }
17
19
  if (scope.remotion_cancelledError !== undefined) {
20
+ cancelled = true;
18
21
  reject(scope.remotion_cancelledError);
19
- clearInterval(interval);
20
22
  return;
21
23
  }
22
24
  if (Date.now() - start > timeoutInMilliseconds + 3000) {
23
- // TODO: Error message should be just as good
25
+ cancelled = true;
24
26
  reject(new Error(Object.values(scope.remotion_delayRenderTimeouts)
25
27
  .map((d) => d.label)
26
28
  .join(', ')));
27
- clearInterval(interval);
29
+ return;
28
30
  }
29
- }, 50);
31
+ requestAnimationFrame(check);
32
+ };
33
+ requestAnimationFrame(check);
30
34
  return promise;
31
35
  };
package/package.json CHANGED
@@ -3,30 +3,35 @@
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.375",
6
+ "version": "4.0.377",
7
7
  "main": "dist/index.js",
8
8
  "sideEffects": false,
9
9
  "scripts": {
10
10
  "formatting": "prettier --experimental-cli src --check",
11
11
  "lint": "eslint src",
12
12
  "make": "tsc -d && bun --env-file=../.env.bundle bundle.ts",
13
- "test": "node src/test/execute.mjs"
13
+ "testwebrenderer": "vitest src/test --browser --run",
14
+ "studio": "cd ../example && bunx remotion studio ../web-renderer/src/test/studio.ts"
14
15
  },
15
16
  "author": "Remotion <jonny@remotion.dev>",
16
17
  "license": "UNLICENSED",
17
18
  "dependencies": {
18
- "remotion": "4.0.375"
19
+ "remotion": "4.0.377",
20
+ "mediabunny": "1.24.5"
19
21
  },
20
22
  "devDependencies": {
21
- "@remotion/eslint-config-internal": "4.0.375",
22
- "@remotion/player": "4.0.375",
23
+ "@remotion/eslint-config-internal": "4.0.377",
24
+ "@remotion/player": "4.0.377",
25
+ "@remotion/media": "4.0.377",
23
26
  "@vitejs/plugin-react": "^5.1.0",
24
- "@vitest/browser-webdriverio": "4.0.7",
27
+ "@vitest/browser-playwright": "4.0.9",
28
+ "playwright": "1.55.1",
25
29
  "eslint": "9.19.0",
26
30
  "react": "19.0.0",
27
31
  "react-dom": "19.0.0",
28
- "vitest": "4.0.7",
29
- "vitest-browser-react": "^2.0.2"
32
+ "vitest": "4.0.9",
33
+ "vitest-browser-react": "^2.0.2",
34
+ "zod": "3.22.3"
30
35
  },
31
36
  "peerDependencies": {
32
37
  "react": ">=18.0.0",
@@ -1,16 +0,0 @@
1
- import React from 'react';
2
- export declare class ErrorBoundary extends React.Component<{
3
- onError: (error: Error) => void;
4
- children: React.ReactNode;
5
- }, {
6
- hasError: Error | null;
7
- }> {
8
- state: {
9
- hasError: null;
10
- };
11
- static getDerivedStateFromError(): {
12
- hasError: boolean;
13
- };
14
- componentDidCatch(error: Error): void;
15
- render(): React.ReactNode;
16
- }
@@ -1,20 +0,0 @@
1
- import React from 'react';
2
- export class ErrorBoundary extends React.Component {
3
- constructor() {
4
- super(...arguments);
5
- this.state = { hasError: null };
6
- }
7
- static getDerivedStateFromError() {
8
- // Update state so the next render will show the fallback UI.
9
- return { hasError: true };
10
- }
11
- componentDidCatch(error) {
12
- this.props.onError(error);
13
- }
14
- render() {
15
- if (this.state.hasError) {
16
- return null;
17
- }
18
- return this.props.children;
19
- }
20
- }
@@ -1,2 +0,0 @@
1
- import type { Composable } from './composable';
2
- export declare const findCanvasElements: (element: HTMLDivElement) => Composable[];
@@ -1,12 +0,0 @@
1
- export const findCanvasElements = (element) => {
2
- const canvasElements = element.querySelectorAll('canvas');
3
- const composables = [];
4
- Array.from(canvasElements).forEach((canvasElement) => {
5
- const canvas = canvasElement;
6
- composables.push({
7
- type: 'canvas',
8
- element: canvas,
9
- });
10
- });
11
- return composables;
12
- };
@@ -1,2 +0,0 @@
1
- import type { Composable } from './composable';
2
- export declare const findSvgElements: (element: HTMLDivElement) => Composable[];
@@ -1,12 +0,0 @@
1
- export const findSvgElements = (element) => {
2
- const svgElements = element.querySelectorAll('svg');
3
- const composables = [];
4
- Array.from(svgElements).forEach((svgElement) => {
5
- const svg = svgElement;
6
- composables.push({
7
- type: 'svg',
8
- element: svg,
9
- });
10
- });
11
- return composables;
12
- };