@remotion/web-renderer 4.0.403 → 4.0.404

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.
@@ -0,0 +1,9 @@
1
+ import { type LogLevel } from 'remotion';
2
+ export type BackgroundKeepalive = {
3
+ waitForTick: () => Promise<void>;
4
+ [Symbol.dispose]: () => void;
5
+ };
6
+ export declare function createBackgroundKeepalive({ fps, logLevel }: {
7
+ fps: number;
8
+ logLevel: LogLevel;
9
+ }): BackgroundKeepalive;
@@ -0,0 +1,9 @@
1
+ import type { Precompositing } from './calculate-transforms';
2
+ export type ElementAndBounds = {
3
+ element: Element;
4
+ bounds: DOMRect;
5
+ transform: DOMMatrix;
6
+ parentRect: DOMRect;
7
+ precompositing: Precompositing;
8
+ establishes3DRenderingContext: DOMMatrix | null;
9
+ };
@@ -1 +1 @@
1
- export declare function getPreTransformRect(targetRect: DOMRect, matrix: DOMMatrix): DOMRect;
1
+ export declare function getPreTransformRect(targetRect: DOMRect, matrix: DOMMatrix): DOMRect | null;
@@ -2,10 +2,10 @@ export declare const getPrecomposeRectFor3DTransform: ({ element, parentRect, ma
2
2
  element: HTMLElement | SVGElement;
3
3
  parentRect: DOMRect;
4
4
  matrix: DOMMatrix;
5
- }) => DOMRect;
6
- export declare const handle3dTransform: ({ matrix, precomposeRect, tempCanvas, rectAfterTransforms, internalState, }: {
5
+ }) => DOMRect | null;
6
+ export declare const handle3dTransform: ({ matrix, sourceRect, tempCanvas, rectAfterTransforms, internalState, }: {
7
7
  matrix: DOMMatrix;
8
- precomposeRect: DOMRect;
8
+ sourceRect: DOMRect;
9
9
  tempCanvas: OffscreenCanvas;
10
10
  rectAfterTransforms: DOMRect;
11
11
  internalState: {
@@ -0,0 +1,33 @@
1
+ import type { Precompositing } from './calculate-transforms';
2
+ import type { ElementAndBounds } from './elements-and-bounds';
3
+ export declare const precompose: ({ element, logLevel, parentRect, internalState, precompositing, totalMatrix, rect, isIn3dRenderingContext, }: {
4
+ element: HTMLElement | SVGElement;
5
+ logLevel: "error" | "info" | "trace" | "verbose" | "warn";
6
+ parentRect: DOMRect;
7
+ internalState: {
8
+ getDrawn3dPixels: () => number;
9
+ getPrecomposedTiles: () => number;
10
+ addPrecompose: ({ canvasWidth, canvasHeight, }: {
11
+ canvasWidth: number;
12
+ canvasHeight: number;
13
+ }) => void;
14
+ helperCanvasState: import("../internal-state").HelperCanvasState;
15
+ [Symbol.dispose]: () => void;
16
+ getWaitForReadyTime: () => number;
17
+ addWaitForReadyTime: (time: number) => void;
18
+ getAddSampleTime: () => number;
19
+ addAddSampleTime: (time: number) => void;
20
+ getCreateFrameTime: () => number;
21
+ addCreateFrameTime: (time: number) => void;
22
+ getAudioMixingTime: () => number;
23
+ addAudioMixingTime: (time: number) => void;
24
+ };
25
+ precompositing: Precompositing;
26
+ totalMatrix: DOMMatrix;
27
+ rect: DOMRect;
28
+ isIn3dRenderingContext: DOMMatrix | null;
29
+ }) => Promise<{
30
+ drawable: OffscreenCanvas;
31
+ rectAfterTransforms: DOMRect;
32
+ elementsToBeRenderedIndependently: ElementAndBounds[];
33
+ } | null>;
File without changes
@@ -0,0 +1,125 @@
1
+ /** Image format for encoded screenshot output. */
2
+ export type ImageFormat = 'png' | 'jpeg' | 'webp';
3
+ /** Options for rendering a screenshot to canvas. */
4
+ export interface RenderOptions {
5
+ /**
6
+ * Canvas background color. Set to `null` (or omit) for a transparent canvas.
7
+ * When provided, the string is passed directly to `fillStyle`.
8
+ */
9
+ backgroundColor?: string | null;
10
+ /**
11
+ * Optional existing canvas to render into.
12
+ * When omitted, a new canvas element is created.
13
+ */
14
+ canvas?: HTMLCanvasElement;
15
+ /**
16
+ * Rendering scale factor. Defaults to `window.devicePixelRatio` (or `1`).
17
+ */
18
+ scale?: number;
19
+ /**
20
+ * Crop origin X (CSS pixels) relative to the element's left edge.
21
+ * Defaults to the element's left edge.
22
+ */
23
+ x?: number;
24
+ /**
25
+ * Crop origin Y (CSS pixels) relative to the element's top edge.
26
+ * Defaults to the element's top edge.
27
+ */
28
+ y?: number;
29
+ /**
30
+ * Output width in CSS pixels. Defaults to the element's width.
31
+ */
32
+ width?: number;
33
+ /**
34
+ * Output height in CSS pixels. Defaults to the element's height.
35
+ */
36
+ height?: number;
37
+ /**
38
+ * Controls how `position: fixed` elements outside the captured subtree are
39
+ * handled.
40
+ *
41
+ * - `none` – ignore all fixed elements outside `element`.
42
+ * - `intersecting` – include fixed elements whose bounding rect intersects the capture rect.
43
+ * - `all` – include all fixed elements that overlap the viewport.
44
+ */
45
+ includeFixed?: 'none' | 'intersecting' | 'all';
46
+ /**
47
+ * CSS selector used to skip elements from rendering.
48
+ * Defaults to `[data-screenshot-ignore]`. Set to `null` or an empty string
49
+ * to disable selector-based skipping.
50
+ */
51
+ ignoreSelector?: string | null;
52
+ }
53
+ /** Options for encoding a canvas to an image format. */
54
+ export interface EncodeOptions {
55
+ /**
56
+ * Image format to encode. Defaults to `'png'`.
57
+ */
58
+ format?: ImageFormat;
59
+ /**
60
+ * Image quality for lossy formats (`jpeg`, `webp`). A number between `0` and `1`.
61
+ * Ignored for `png`. Defaults to `0.92`.
62
+ */
63
+ quality?: number;
64
+ }
65
+ /** Combined options for one-shot screenshot methods that render and encode. */
66
+ export type ScreenshotOptions = RenderOptions & EncodeOptions;
67
+ /**
68
+ * A promise-like object representing a screenshot capture. The underlying
69
+ * render happens once; subsequent calls to `.canvas()`, `.blob()`, or `.url()`
70
+ * reuse the same rendered canvas.
71
+ */
72
+ export interface ScreenshotTask extends Promise<HTMLCanvasElement> {
73
+ /** Returns the rendered canvas. */
74
+ canvas(): Promise<HTMLCanvasElement>;
75
+ /** Encodes the rendered canvas to a Blob. */
76
+ blob(options?: EncodeOptions): Promise<Blob>;
77
+ /**
78
+ * Encodes the rendered canvas to a Blob and creates an object URL.
79
+ * Remember to call `URL.revokeObjectURL(url)` when done to avoid memory leaks.
80
+ */
81
+ url(options?: EncodeOptions): Promise<string>;
82
+ }
83
+ /**
84
+ * Renders a DOM element into a canvas using modern browser features.
85
+ *
86
+ * Returns a `ScreenshotTask` that is both a Promise and provides methods
87
+ * to encode the rendered canvas. The underlying render happens once;
88
+ * subsequent calls to `.canvas()`, `.blob()`, or `.url()` reuse the same result.
89
+ *
90
+ * This implementation targets evergreen browsers only and assumes a real DOM +
91
+ * Canvas2D environment (not Node.js).
92
+ *
93
+ * @example
94
+ * // Capture handle pattern - render once, encode multiple ways
95
+ * const shot = screenshot(element, { scale: 2 })
96
+ * const canvas = await shot.canvas()
97
+ * const pngBlob = await shot.blob({ format: 'png' })
98
+ * const webpUrl = await shot.url({ format: 'webp', quality: 0.9 })
99
+ *
100
+ * @example
101
+ * // Direct await returns the canvas
102
+ * const canvas = await screenshot(element)
103
+ *
104
+ * @example
105
+ * // One-shot convenience methods
106
+ * const canvas = await screenshot.canvas(element, { scale: 2 })
107
+ * const blob = await screenshot.blob(element, { format: 'jpeg', quality: 0.85 })
108
+ * const url = await screenshot.url(element, { format: 'png' })
109
+ */
110
+ declare function screenshot(target: Element | string, options?: RenderOptions): ScreenshotTask;
111
+ declare namespace screenshot {
112
+ var canvas: (target: string | Element, options?: RenderOptions | undefined) => Promise<HTMLCanvasElement>;
113
+ }
114
+ declare namespace screenshot {
115
+ var blob: (target: string | Element, options?: ScreenshotOptions | undefined) => Promise<Blob>;
116
+ }
117
+ declare namespace screenshot {
118
+ var url: (target: string | Element, options?: ScreenshotOptions | undefined) => Promise<string>;
119
+ }
120
+ export { screenshot };
121
+ declare global {
122
+ interface CanvasRenderingContext2D {
123
+ _filterPolyfillValue?: string;
124
+ }
125
+ }
@@ -1,9 +1,9 @@
1
1
  import type { HelperCanvasState } from '../internal-state';
2
- export declare const transformIn3d: ({ matrix, sourceCanvas, untransformedRect, rectAfterTransforms, internalState, }: {
3
- untransformedRect: DOMRect;
2
+ export declare const transformIn3d: ({ matrix, sourceCanvas, sourceRect, destRect, internalState, }: {
3
+ sourceRect: DOMRect;
4
4
  matrix: DOMMatrix;
5
5
  sourceCanvas: OffscreenCanvas;
6
- rectAfterTransforms: DOMRect;
6
+ destRect: DOMRect;
7
7
  internalState: {
8
8
  getDrawn3dPixels: () => number;
9
9
  getPrecomposedTiles: () => number;
@@ -22,7 +22,4 @@ export declare const transformIn3d: ({ matrix, sourceCanvas, untransformedRect,
22
22
  getAudioMixingTime: () => number;
23
23
  addAudioMixingTime: (time: number) => void;
24
24
  };
25
- }) => {
26
- canvas: OffscreenCanvas;
27
- rect: DOMRect;
28
- };
25
+ }) => OffscreenCanvas;
@@ -0,0 +1,21 @@
1
+ export type Transform = {
2
+ matrices: DOMMatrix[];
3
+ element: Element;
4
+ transformOrigin: string;
5
+ boundingClientRect: DOMRect | null;
6
+ };
7
+ export declare const parseTransformOriginOrPerspectiveOrigin: (transformOrigin: string) => {
8
+ x: number;
9
+ y: number;
10
+ } | null;
11
+ export declare const getInternalOrigin: (origin: string, boundingClientRect: DOMRect) => {
12
+ x: number;
13
+ y: number;
14
+ };
15
+ export declare const getGlobalOrigin: ({ origin, boundingClientRect, }: {
16
+ origin: string;
17
+ boundingClientRect: DOMRect;
18
+ }) => {
19
+ x: number;
20
+ y: number;
21
+ };
@@ -0,0 +1,17 @@
1
+ export type Transform = {
2
+ matrices: DOMMatrix[];
3
+ element: Element;
4
+ transformOrigin: string;
5
+ boundingClientRect: DOMRect | null;
6
+ };
7
+ export declare const parseTransformOriginOrPerspectiveOrigin: (transformOrigin: string) => {
8
+ x: number;
9
+ y: number;
10
+ } | null;
11
+ export declare const getAbsoluteOrigin: ({ origin, boundingClientRect, }: {
12
+ origin: string;
13
+ boundingClientRect: DOMRect;
14
+ }) => {
15
+ x: number;
16
+ y: number;
17
+ };
@@ -359,7 +359,7 @@ var getEncodableAudioCodecs = async (container, options) => {
359
359
  };
360
360
  // src/render-media-on-web.tsx
361
361
  import { BufferTarget, StreamTarget } from "mediabunny";
362
- import { Internals as Internals7 } from "remotion";
362
+ import { Internals as Internals8 } from "remotion";
363
363
 
364
364
  // src/add-sample.ts
365
365
  import { AudioSample, VideoSample } from "mediabunny";
@@ -488,6 +488,92 @@ var onlyInlineAudio = ({
488
488
  });
489
489
  };
490
490
 
491
+ // src/background-keepalive.ts
492
+ import { Internals } from "remotion";
493
+ var WORKER_CODE = `
494
+ let intervalId = null;
495
+ self.onmessage = (e) => {
496
+ if (e.data.type === 'start') {
497
+ if (intervalId !== null) {
498
+ clearInterval(intervalId);
499
+ }
500
+ intervalId = setInterval(() => self.postMessage('tick'), e.data.intervalMs);
501
+ } else if (e.data.type === 'stop') {
502
+ if (intervalId !== null) {
503
+ clearInterval(intervalId);
504
+ intervalId = null;
505
+ }
506
+ }
507
+ };
508
+ `;
509
+ function createBackgroundKeepalive({
510
+ fps,
511
+ logLevel
512
+ }) {
513
+ const intervalMs = Math.round(1000 / fps);
514
+ let pendingResolvers = [];
515
+ let worker = null;
516
+ let disposed = false;
517
+ if (typeof Worker === "undefined") {
518
+ Internals.Log.warn({ logLevel, tag: "@remotion/web-renderer" }, "Web Workers not available. Rendering may pause when tab is backgrounded.");
519
+ return {
520
+ waitForTick: () => {
521
+ return new Promise((resolve) => {
522
+ setTimeout(resolve, intervalMs);
523
+ });
524
+ },
525
+ [Symbol.dispose]: () => {}
526
+ };
527
+ }
528
+ const blob = new Blob([WORKER_CODE], { type: "application/javascript" });
529
+ const workerUrl = URL.createObjectURL(blob);
530
+ worker = new Worker(workerUrl);
531
+ worker.onmessage = () => {
532
+ const resolvers = pendingResolvers;
533
+ pendingResolvers = [];
534
+ for (const resolve of resolvers) {
535
+ resolve();
536
+ }
537
+ };
538
+ worker.onerror = (event) => {
539
+ Internals.Log.error({ logLevel, tag: "@remotion/web-renderer" }, "Background keepalive worker encountered an error and will be terminated.", event);
540
+ const resolvers = pendingResolvers;
541
+ pendingResolvers = [];
542
+ for (const resolve of resolvers) {
543
+ resolve();
544
+ }
545
+ if (!disposed) {
546
+ disposed = true;
547
+ worker?.terminate();
548
+ worker = null;
549
+ URL.revokeObjectURL(workerUrl);
550
+ }
551
+ };
552
+ worker.postMessage({ type: "start", intervalMs });
553
+ return {
554
+ waitForTick: () => {
555
+ return new Promise((resolve) => {
556
+ pendingResolvers.push(resolve);
557
+ });
558
+ },
559
+ [Symbol.dispose]: () => {
560
+ if (disposed) {
561
+ return;
562
+ }
563
+ disposed = true;
564
+ worker?.postMessage({ type: "stop" });
565
+ worker?.terminate();
566
+ worker = null;
567
+ URL.revokeObjectURL(workerUrl);
568
+ const resolvers = pendingResolvers;
569
+ pendingResolvers = [];
570
+ for (const resolve of resolvers) {
571
+ resolve();
572
+ }
573
+ }
574
+ };
575
+ }
576
+
491
577
  // src/create-audio-sample-source.ts
492
578
  import { AudioSampleSource } from "mediabunny";
493
579
  var createAudioSampleSource = ({
@@ -509,12 +595,12 @@ var createAudioSampleSource = ({
509
595
  import { createRef } from "react";
510
596
  import { flushSync as flushSync2 } from "react-dom";
511
597
  import ReactDOM from "react-dom/client";
512
- import { Internals as Internals2 } from "remotion";
598
+ import { Internals as Internals3 } from "remotion";
513
599
 
514
600
  // src/update-time.tsx
515
601
  import { useImperativeHandle, useState } from "react";
516
602
  import { flushSync } from "react-dom";
517
- import { Internals } from "remotion";
603
+ import { Internals as Internals2 } from "remotion";
518
604
  import { jsx } from "react/jsx-runtime";
519
605
  var UpdateTime = ({
520
606
  children,
@@ -533,7 +619,7 @@ var UpdateTime = ({
533
619
  });
534
620
  }
535
621
  }));
536
- return /* @__PURE__ */ jsx(Internals.RemotionRootContexts, {
622
+ return /* @__PURE__ */ jsx(Internals2.RemotionRootContexts, {
537
623
  audioEnabled,
538
624
  videoEnabled,
539
625
  logLevel,
@@ -596,7 +682,7 @@ async function createScaffold({
596
682
  div.style.pointerEvents = "none";
597
683
  const scaffoldClassName = `remotion-scaffold-${Math.random().toString(36).substring(2, 15)}`;
598
684
  div.className = scaffoldClassName;
599
- const cleanupCSS = Internals2.CSSUtils.injectCSS(Internals2.CSSUtils.makeDefaultPreviewCSS(`.${scaffoldClassName}`, "white"));
685
+ const cleanupCSS = Internals3.CSSUtils.injectCSS(Internals3.CSSUtils.makeDefaultPreviewCSS(`.${scaffoldClassName}`, "white"));
600
686
  document.body.appendChild(div);
601
687
  const { promise, resolve, reject } = withResolvers();
602
688
  const root = ReactDOM.createRoot(div, {
@@ -614,9 +700,9 @@ async function createScaffold({
614
700
  const timeUpdater = createRef();
615
701
  const collectAssets = createRef();
616
702
  flushSync2(() => {
617
- root.render(/* @__PURE__ */ jsx2(Internals2.MaxMediaCacheSizeContext.Provider, {
703
+ root.render(/* @__PURE__ */ jsx2(Internals3.MaxMediaCacheSizeContext.Provider, {
618
704
  value: mediaCacheSizeInBytes,
619
- children: /* @__PURE__ */ jsx2(Internals2.RemotionEnvironmentContext.Provider, {
705
+ children: /* @__PURE__ */ jsx2(Internals3.RemotionEnvironmentContext.Provider, {
620
706
  value: {
621
707
  isStudio: false,
622
708
  isRendering: true,
@@ -624,9 +710,9 @@ async function createScaffold({
624
710
  isReadOnlyStudio: false,
625
711
  isClientSideRendering: true
626
712
  },
627
- children: /* @__PURE__ */ jsx2(Internals2.DelayRenderContextType.Provider, {
713
+ children: /* @__PURE__ */ jsx2(Internals3.DelayRenderContextType.Provider, {
628
714
  value: delayRenderScope,
629
- children: /* @__PURE__ */ jsx2(Internals2.CompositionManager.Provider, {
715
+ children: /* @__PURE__ */ jsx2(Internals3.CompositionManager.Provider, {
630
716
  value: {
631
717
  compositions: [
632
718
  {
@@ -662,7 +748,7 @@ async function createScaffold({
662
748
  },
663
749
  folders: []
664
750
  },
665
- children: /* @__PURE__ */ jsx2(Internals2.RenderAssetManagerProvider, {
751
+ children: /* @__PURE__ */ jsx2(Internals3.RenderAssetManagerProvider, {
666
752
  collectAssets,
667
753
  children: /* @__PURE__ */ jsx2(UpdateTime, {
668
754
  audioEnabled,
@@ -671,7 +757,7 @@ async function createScaffold({
671
757
  compId: id,
672
758
  initialFrame,
673
759
  timeUpdater,
674
- children: /* @__PURE__ */ jsx2(Internals2.CanUseRemotionHooks.Provider, {
760
+ children: /* @__PURE__ */ jsx2(Internals3.CanUseRemotionHooks.Provider, {
675
761
  value: true,
676
762
  children: /* @__PURE__ */ jsx2(Component, {
677
763
  ...resolvedProps
@@ -799,58 +885,77 @@ function isNetworkError(error) {
799
885
  return false;
800
886
  }
801
887
  var HOST = "https://www.remotion.pro";
888
+ var DEFAULT_MAX_RETRIES = 3;
889
+ var exponentialBackoffMs = (attempt) => {
890
+ return 1000 * 2 ** (attempt - 1);
891
+ };
892
+ var sleep = (ms) => {
893
+ return new Promise((resolve) => {
894
+ setTimeout(resolve, ms);
895
+ });
896
+ };
802
897
  var registerUsageEvent = async ({
803
898
  host,
804
899
  succeeded,
805
900
  event,
806
901
  ...apiOrLicenseKey
807
902
  }) => {
808
- const abortController = new AbortController;
809
- const timeout = setTimeout(() => {
810
- abortController.abort();
811
- }, 1e4);
812
903
  const apiKey = "apiKey" in apiOrLicenseKey ? apiOrLicenseKey.apiKey : null;
813
904
  const licenseKey = "licenseKey" in apiOrLicenseKey ? apiOrLicenseKey.licenseKey : null;
814
- try {
815
- const res = await fetch(`${HOST}/api/track/register-usage-point`, {
816
- method: "POST",
817
- body: JSON.stringify({
818
- event,
819
- apiKey: licenseKey ?? apiKey,
820
- host,
821
- succeeded
822
- }),
823
- headers: {
824
- "Content-Type": "application/json"
825
- },
826
- signal: abortController.signal
827
- });
828
- clearTimeout(timeout);
829
- const json = await res.json();
830
- if (json.success) {
831
- return {
832
- billable: json.billable,
833
- classification: json.classification
834
- };
835
- }
836
- if (!res.ok) {
837
- throw new Error(json.error);
838
- }
839
- throw new Error("Unexpected response from server");
840
- } catch (err) {
841
- if (isNetworkError(err)) {
842
- console.log("Failed to send usage event", err);
843
- }
844
- clearTimeout(timeout);
845
- if (err instanceof Error && err.name === "AbortError") {
846
- throw new Error("Request timed out after 10 seconds");
905
+ let lastError;
906
+ const totalAttempts = DEFAULT_MAX_RETRIES + 1;
907
+ for (let attempt = 1;attempt <= totalAttempts; attempt++) {
908
+ const abortController = new AbortController;
909
+ const timeout = setTimeout(() => {
910
+ abortController.abort();
911
+ }, 1e4);
912
+ try {
913
+ const res = await fetch(`${HOST}/api/track/register-usage-point`, {
914
+ method: "POST",
915
+ body: JSON.stringify({
916
+ event,
917
+ apiKey: licenseKey ?? apiKey,
918
+ host,
919
+ succeeded
920
+ }),
921
+ headers: {
922
+ "Content-Type": "application/json"
923
+ },
924
+ signal: abortController.signal
925
+ });
926
+ clearTimeout(timeout);
927
+ const json = await res.json();
928
+ if (json.success) {
929
+ return {
930
+ billable: json.billable,
931
+ classification: json.classification
932
+ };
933
+ }
934
+ if (!res.ok) {
935
+ throw new Error(json.error);
936
+ }
937
+ throw new Error(`Unexpected response from server: ${JSON.stringify(json)}`);
938
+ } catch (err) {
939
+ clearTimeout(timeout);
940
+ const error = err;
941
+ const isTimeout = error.name === "AbortError";
942
+ const isRetryable = isNetworkError(error) || isTimeout;
943
+ if (!isRetryable) {
944
+ throw err;
945
+ }
946
+ lastError = isTimeout ? new Error("Request timed out after 10 seconds") : error;
947
+ if (attempt < totalAttempts) {
948
+ const backoffMs = exponentialBackoffMs(attempt);
949
+ console.log(`Failed to send usage event (attempt ${attempt}/${totalAttempts}), retrying in ${backoffMs}ms...`, err);
950
+ await sleep(backoffMs);
951
+ }
847
952
  }
848
- throw err;
849
953
  }
954
+ throw lastError;
850
955
  };
851
956
 
852
957
  // src/send-telemetry-event.ts
853
- import { Internals as Internals3 } from "remotion";
958
+ import { Internals as Internals4 } from "remotion";
854
959
  var sendUsageEvent = async ({
855
960
  licenseKey,
856
961
  succeeded,
@@ -861,7 +966,7 @@ var sendUsageEvent = async ({
861
966
  return;
862
967
  }
863
968
  if (licenseKey === null) {
864
- Internals3.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.`);
969
+ Internals4.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.`);
865
970
  }
866
971
  await registerUsageEvent({
867
972
  licenseKey: licenseKey === "free-license" ? null : licenseKey,
@@ -1207,7 +1312,7 @@ var drawDomElement = (node) => {
1207
1312
  };
1208
1313
 
1209
1314
  // src/drawing/process-node.ts
1210
- import { Internals as Internals5 } from "remotion";
1315
+ import { Internals as Internals6 } from "remotion";
1211
1316
 
1212
1317
  // src/drawing/has-transform.ts
1213
1318
  var hasTransformCssValue = (style) => {
@@ -2256,7 +2361,7 @@ var drawBorder = ({
2256
2361
  };
2257
2362
 
2258
2363
  // src/drawing/draw-box-shadow.ts
2259
- import { Internals as Internals4 } from "remotion";
2364
+ import { Internals as Internals5 } from "remotion";
2260
2365
  var parseBoxShadow = (boxShadowValue) => {
2261
2366
  if (!boxShadowValue || boxShadowValue === "none") {
2262
2367
  return [];
@@ -2321,7 +2426,7 @@ var drawBorderRadius = ({
2321
2426
  throw new Error("Failed to get context");
2322
2427
  }
2323
2428
  if (shadow.inset) {
2324
- Internals4.Log.warn({
2429
+ Internals5.Log.warn({
2325
2430
  logLevel,
2326
2431
  tag: "@remotion/web-renderer"
2327
2432
  }, 'Detected "box-shadow" with "inset". This is not yet supported in @remotion/web-renderer');
@@ -2619,55 +2724,81 @@ var getBiggestBoundingClientRect = (element) => {
2619
2724
 
2620
2725
  // src/drawing/get-pretransform-rect.ts
2621
2726
  var MAX_SCALE_FACTOR = 100;
2622
- function getPreTransformRect(targetRect, matrix) {
2727
+ var isScaleTooBig = (matrix) => {
2623
2728
  const origin = new DOMPoint(0, 0).matrixTransform(matrix);
2624
2729
  const unitX = new DOMPoint(1, 0).matrixTransform(matrix);
2625
2730
  const unitY = new DOMPoint(0, 1).matrixTransform(matrix);
2626
2731
  const basisX = { x: unitX.x - origin.x, y: unitX.y - origin.y };
2627
2732
  const basisY = { x: unitY.x - origin.x, y: unitY.y - origin.y };
2628
- const scaleX = Math.hypot(basisX.x, basisX.y);
2629
- const scaleY = Math.hypot(basisY.x, basisY.y);
2630
- const minScale = Math.min(scaleX, scaleY);
2631
- if (minScale < 1 / MAX_SCALE_FACTOR) {
2632
- return new DOMRect(0, 0, 0, 0);
2633
- }
2634
- const effective2D = new DOMMatrix([
2635
- basisX.x,
2636
- basisX.y,
2637
- basisY.x,
2638
- basisY.y,
2639
- origin.x,
2640
- origin.y
2641
- ]);
2642
- const inverse2D = effective2D.inverse();
2643
- const wasNotInvertible = isNaN(inverse2D.m11);
2644
- if (wasNotInvertible) {
2645
- return new DOMRect(0, 0, 0, 0);
2733
+ const scaleX = 1 / Math.hypot(basisX.x, basisX.y);
2734
+ const scaleY = 1 / Math.hypot(basisY.x, basisY.y);
2735
+ const maxScale = Math.max(scaleX, scaleY);
2736
+ if (maxScale > MAX_SCALE_FACTOR) {
2737
+ return true;
2738
+ }
2739
+ return false;
2740
+ };
2741
+ function invertProjectivePoint(xp, yp, matrix) {
2742
+ const A = matrix.m11 - xp * matrix.m14;
2743
+ const B = matrix.m21 - xp * matrix.m24;
2744
+ const C = xp * matrix.m44 - matrix.m41;
2745
+ const D = matrix.m12 - yp * matrix.m14;
2746
+ const E = matrix.m22 - yp * matrix.m24;
2747
+ const F = yp * matrix.m44 - matrix.m42;
2748
+ const det = A * E - B * D;
2749
+ if (Math.abs(det) < 0.0000000001) {
2750
+ return null;
2751
+ }
2752
+ const x = (C * E - B * F) / det;
2753
+ const y = (A * F - C * D) / det;
2754
+ return { x, y };
2755
+ }
2756
+ function getPreTransformRect(targetRect, matrix) {
2757
+ if (isScaleTooBig(matrix)) {
2758
+ return null;
2646
2759
  }
2647
2760
  const corners = [
2648
- new DOMPoint(targetRect.x, targetRect.y),
2649
- new DOMPoint(targetRect.x + targetRect.width, targetRect.y),
2650
- new DOMPoint(targetRect.x + targetRect.width, targetRect.y + targetRect.height),
2651
- new DOMPoint(targetRect.x, targetRect.y + targetRect.height)
2761
+ { x: targetRect.x, y: targetRect.y },
2762
+ { x: targetRect.x + targetRect.width, y: targetRect.y },
2763
+ { x: targetRect.x + targetRect.width, y: targetRect.y + targetRect.height },
2764
+ { x: targetRect.x, y: targetRect.y + targetRect.height }
2652
2765
  ];
2653
- const transformedCorners = corners.map((c) => c.matrixTransform(inverse2D));
2654
- const xs = transformedCorners.map((p) => p.x);
2655
- const ys = transformedCorners.map((p) => p.y);
2656
- return new DOMRect(Math.min(...xs), Math.min(...ys), Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys));
2766
+ const invertedCorners = [];
2767
+ for (const corner of corners) {
2768
+ const inverted = invertProjectivePoint(corner.x, corner.y, matrix);
2769
+ if (inverted === null) {
2770
+ return null;
2771
+ }
2772
+ invertedCorners.push(inverted);
2773
+ }
2774
+ const xCoords = invertedCorners.map((p) => p.x);
2775
+ const yCoords = invertedCorners.map((p) => p.y);
2776
+ return new DOMRect(Math.min(...xCoords), Math.min(...yCoords), Math.max(...xCoords) - Math.min(...xCoords), Math.max(...yCoords) - Math.min(...yCoords));
2657
2777
  }
2658
2778
 
2659
2779
  // src/drawing/transform-in-3d.ts
2660
2780
  var vsSource = `
2661
- attribute vec2 aPosition;
2662
- attribute vec2 aTexCoord;
2663
- uniform mat4 uTransform;
2664
- uniform mat4 uProjection;
2665
- varying vec2 vTexCoord;
2781
+ attribute vec2 aPosition;
2782
+ attribute vec2 aTexCoord;
2783
+ uniform mat4 uTransform;
2784
+ uniform vec2 uResolution;
2785
+ uniform vec2 uOffset;
2786
+ varying vec2 vTexCoord;
2666
2787
 
2667
- void main() {
2668
- gl_Position = uProjection * uTransform * vec4(aPosition, 0.0, 1.0);
2669
- vTexCoord = aTexCoord;
2670
- }
2788
+ void main() {
2789
+ vec4 pos = uTransform * vec4(aPosition, 0.0, 1.0);
2790
+ pos.xy = pos.xy + uOffset * pos.w;
2791
+
2792
+ // Convert homogeneous coords to clip space
2793
+ gl_Position = vec4(
2794
+ (pos.x / uResolution.x) * 2.0 - pos.w, // x
2795
+ pos.w - (pos.y / uResolution.y) * 2.0, // y (flipped)
2796
+ 0.0,
2797
+ pos.w
2798
+ );
2799
+
2800
+ vTexCoord = aTexCoord;
2801
+ }
2671
2802
  `;
2672
2803
  var fsSource = `
2673
2804
  precision mediump float;
@@ -2730,7 +2861,8 @@ var createHelperCanvas = ({
2730
2861
  aPosition: gl.getAttribLocation(program, "aPosition"),
2731
2862
  aTexCoord: gl.getAttribLocation(program, "aTexCoord"),
2732
2863
  uTransform: gl.getUniformLocation(program, "uTransform"),
2733
- uProjection: gl.getUniformLocation(program, "uProjection"),
2864
+ uResolution: gl.getUniformLocation(program, "uResolution"),
2865
+ uOffset: gl.getUniformLocation(program, "uOffset"),
2734
2866
  uTexture: gl.getUniformLocation(program, "uTexture")
2735
2867
  };
2736
2868
  gl.deleteShader(vertexShader);
@@ -2748,55 +2880,60 @@ var createHelperCanvas = ({
2748
2880
  var transformIn3d = ({
2749
2881
  matrix,
2750
2882
  sourceCanvas,
2751
- untransformedRect,
2752
- rectAfterTransforms,
2883
+ sourceRect,
2884
+ destRect,
2753
2885
  internalState
2754
2886
  }) => {
2755
2887
  const { canvas, gl, program, locations } = createHelperCanvas({
2756
- canvasWidth: rectAfterTransforms.width,
2757
- canvasHeight: rectAfterTransforms.height,
2888
+ canvasWidth: destRect.width,
2889
+ canvasHeight: destRect.height,
2758
2890
  helperCanvasState: internalState.helperCanvasState
2759
2891
  });
2760
2892
  gl.useProgram(program);
2761
- gl.viewport(0, 0, rectAfterTransforms.width, rectAfterTransforms.height);
2893
+ gl.viewport(0, 0, destRect.width, destRect.height);
2762
2894
  gl.clearColor(0, 0, 0, 0);
2763
2895
  gl.clear(gl.COLOR_BUFFER_BIT);
2764
2896
  gl.enable(gl.BLEND);
2765
2897
  gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
2766
2898
  gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
2767
- const vertexBuffer = gl.createBuffer();
2768
- gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
2769
- const vertices = new Float32Array([
2770
- untransformedRect.x,
2771
- untransformedRect.y,
2899
+ const positionBuffer = gl.createBuffer();
2900
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
2901
+ const positions = new Float32Array([
2902
+ sourceRect.x,
2903
+ sourceRect.y,
2904
+ sourceRect.x + sourceRect.width,
2905
+ sourceRect.y,
2906
+ sourceRect.x,
2907
+ sourceRect.y + sourceRect.height,
2908
+ sourceRect.x,
2909
+ sourceRect.y + sourceRect.height,
2910
+ sourceRect.x + sourceRect.width,
2911
+ sourceRect.y,
2912
+ sourceRect.x + sourceRect.width,
2913
+ sourceRect.y + sourceRect.height
2914
+ ]);
2915
+ gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
2916
+ gl.enableVertexAttribArray(locations.aPosition);
2917
+ gl.vertexAttribPointer(locations.aPosition, 2, gl.FLOAT, false, 0, 0);
2918
+ const texCoordBuffer = gl.createBuffer();
2919
+ gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
2920
+ const texCoords = new Float32Array([
2772
2921
  0,
2773
2922
  0,
2774
- untransformedRect.x + untransformedRect.width,
2775
- untransformedRect.y,
2776
2923
  1,
2777
2924
  0,
2778
- untransformedRect.x,
2779
- untransformedRect.y + untransformedRect.height,
2780
2925
  0,
2781
2926
  1,
2782
- untransformedRect.x,
2783
- untransformedRect.y + untransformedRect.height,
2784
2927
  0,
2785
2928
  1,
2786
- untransformedRect.x + untransformedRect.width,
2787
- untransformedRect.y,
2788
2929
  1,
2789
2930
  0,
2790
- untransformedRect.x + untransformedRect.width,
2791
- untransformedRect.y + untransformedRect.height,
2792
2931
  1,
2793
2932
  1
2794
2933
  ]);
2795
- gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
2796
- gl.enableVertexAttribArray(locations.aPosition);
2797
- gl.vertexAttribPointer(locations.aPosition, 2, gl.FLOAT, false, 4 * 4, 0);
2934
+ gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);
2798
2935
  gl.enableVertexAttribArray(locations.aTexCoord);
2799
- gl.vertexAttribPointer(locations.aTexCoord, 2, gl.FLOAT, false, 4 * 4, 2 * 4);
2936
+ gl.vertexAttribPointer(locations.aTexCoord, 2, gl.FLOAT, false, 0, 0);
2800
2937
  const texture = gl.createTexture();
2801
2938
  gl.bindTexture(gl.TEXTURE_2D, texture);
2802
2939
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
@@ -2805,41 +2942,21 @@ var transformIn3d = ({
2805
2942
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
2806
2943
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, sourceCanvas);
2807
2944
  const transformMatrix = matrix.toFloat32Array();
2808
- const zScale = 1e9;
2809
- const projectionMatrix = new Float32Array([
2810
- 2 / rectAfterTransforms.width,
2811
- 0,
2812
- 0,
2813
- 0,
2814
- 0,
2815
- -2 / rectAfterTransforms.height,
2816
- 0,
2817
- 0,
2818
- 0,
2819
- 0,
2820
- -2 / zScale,
2821
- 0,
2822
- -1 + 2 * -rectAfterTransforms.x / rectAfterTransforms.width,
2823
- 1 - 2 * -rectAfterTransforms.y / rectAfterTransforms.height,
2824
- 0,
2825
- 1
2826
- ]);
2827
2945
  gl.uniformMatrix4fv(locations.uTransform, false, transformMatrix);
2828
- gl.uniformMatrix4fv(locations.uProjection, false, projectionMatrix);
2946
+ gl.uniform2f(locations.uResolution, destRect.width, destRect.height);
2947
+ gl.uniform2f(locations.uOffset, -destRect.x, -destRect.y);
2829
2948
  gl.uniform1i(locations.uTexture, 0);
2830
2949
  gl.drawArrays(gl.TRIANGLES, 0, 6);
2831
2950
  gl.disableVertexAttribArray(locations.aPosition);
2832
2951
  gl.disableVertexAttribArray(locations.aTexCoord);
2833
2952
  gl.deleteTexture(texture);
2834
- gl.deleteBuffer(vertexBuffer);
2953
+ gl.deleteBuffer(positionBuffer);
2954
+ gl.deleteBuffer(texCoordBuffer);
2835
2955
  gl.bindTexture(gl.TEXTURE_2D, null);
2836
2956
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
2837
2957
  gl.disable(gl.BLEND);
2838
2958
  gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
2839
- return {
2840
- canvas,
2841
- rect: rectAfterTransforms
2842
- };
2959
+ return canvas;
2843
2960
  };
2844
2961
 
2845
2962
  // src/drawing/handle-3d-transform.ts
@@ -2850,6 +2967,9 @@ var getPrecomposeRectFor3DTransform = ({
2850
2967
  }) => {
2851
2968
  const unclampedBiggestBoundingClientRect = getBiggestBoundingClientRect(element);
2852
2969
  const biggestPossiblePretransformRect = getPreTransformRect(parentRect, matrix);
2970
+ if (!biggestPossiblePretransformRect) {
2971
+ return null;
2972
+ }
2853
2973
  const preTransformRect = getNarrowerRect({
2854
2974
  firstRect: unclampedBiggestBoundingClientRect,
2855
2975
  secondRect: biggestPossiblePretransformRect
@@ -2858,21 +2978,21 @@ var getPrecomposeRectFor3DTransform = ({
2858
2978
  };
2859
2979
  var handle3dTransform = ({
2860
2980
  matrix,
2861
- precomposeRect,
2981
+ sourceRect,
2862
2982
  tempCanvas,
2863
2983
  rectAfterTransforms,
2864
2984
  internalState
2865
2985
  }) => {
2866
- const { canvas: transformed, rect: transformedRect } = transformIn3d({
2867
- untransformedRect: precomposeRect,
2986
+ if (rectAfterTransforms.width <= 0 || rectAfterTransforms.height <= 0) {
2987
+ return null;
2988
+ }
2989
+ const transformed = transformIn3d({
2990
+ sourceRect,
2868
2991
  matrix,
2869
2992
  sourceCanvas: tempCanvas,
2870
- rectAfterTransforms,
2993
+ destRect: rectAfterTransforms,
2871
2994
  internalState
2872
2995
  });
2873
- if (transformedRect.width <= 0 || transformedRect.height <= 0) {
2874
- return null;
2875
- }
2876
2996
  return transformed;
2877
2997
  };
2878
2998
 
@@ -2973,20 +3093,21 @@ var processNode = async ({
2973
3093
  const start = Date.now();
2974
3094
  let precomposeRect = null;
2975
3095
  if (precompositing.needsMaskImage) {
2976
- precomposeRect = getWiderRectAndExpand({
2977
- firstRect: precomposeRect,
2978
- secondRect: getPrecomposeRectForMask(element)
2979
- });
3096
+ precomposeRect = roundToExpandRect(getPrecomposeRectForMask(element));
2980
3097
  }
2981
3098
  if (precompositing.needs3DTransformViaWebGL) {
2982
- precomposeRect = getWiderRectAndExpand({
2983
- firstRect: precomposeRect,
2984
- secondRect: getPrecomposeRectFor3DTransform({
2985
- element,
2986
- parentRect,
2987
- matrix: totalMatrix
2988
- })
3099
+ const tentativePrecomposeRect = getPrecomposeRectFor3DTransform({
3100
+ element,
3101
+ parentRect,
3102
+ matrix: totalMatrix
2989
3103
  });
3104
+ if (!tentativePrecomposeRect) {
3105
+ return { type: "continue", cleanupAfterChildren: null };
3106
+ }
3107
+ precomposeRect = roundToExpandRect(getWiderRectAndExpand({
3108
+ firstRect: precomposeRect,
3109
+ secondRect: tentativePrecomposeRect
3110
+ }));
2990
3111
  }
2991
3112
  if (!precomposeRect) {
2992
3113
  throw new Error("Precompose rect not found");
@@ -3019,7 +3140,7 @@ var processNode = async ({
3019
3140
  if (precompositing.needs3DTransformViaWebGL) {
3020
3141
  const t = handle3dTransform({
3021
3142
  matrix: totalMatrix,
3022
- precomposeRect,
3143
+ sourceRect: precomposeRect,
3023
3144
  tempCanvas: drawable,
3024
3145
  rectAfterTransforms,
3025
3146
  internalState
@@ -3033,7 +3154,7 @@ var processNode = async ({
3033
3154
  context.setTransform(new DOMMatrix);
3034
3155
  context.drawImage(drawable, 0, drawable.height - rectAfterTransforms.height, rectAfterTransforms.width, rectAfterTransforms.height, rectAfterTransforms.left - parentRect.x, rectAfterTransforms.top - parentRect.y, rectAfterTransforms.width, rectAfterTransforms.height);
3035
3156
  context.setTransform(previousTransform);
3036
- Internals5.Log.trace({
3157
+ Internals6.Log.trace({
3037
3158
  logLevel,
3038
3159
  tag: "@remotion/web-renderer"
3039
3160
  }, `Transforming element in 3D - canvas size: ${precomposeRect.width}x${precomposeRect.height} - compose: ${Date.now() - start}ms - helper canvas: ${drawable.width}x${drawable.height}`);
@@ -3065,7 +3186,7 @@ var processNode = async ({
3065
3186
  };
3066
3187
 
3067
3188
  // src/drawing/text/draw-text.ts
3068
- import { Internals as Internals6 } from "remotion";
3189
+ import { Internals as Internals7 } from "remotion";
3069
3190
 
3070
3191
  // src/drawing/text/apply-text-transform.ts
3071
3192
  var applyTextTransform = (text, transform) => {
@@ -3238,7 +3359,7 @@ var drawText = ({
3238
3359
  } = computedStyle;
3239
3360
  const isVertical = writingMode !== "horizontal-tb";
3240
3361
  if (isVertical) {
3241
- Internals6.Log.warn({
3362
+ Internals7.Log.warn({
3242
3363
  logLevel,
3243
3364
  tag: "@remotion/web-renderer"
3244
3365
  }, 'Detected "writing-mode" CSS property. Vertical text is not yet supported in @remotion/web-renderer');
@@ -3546,7 +3667,8 @@ var waitForReady = ({
3546
3667
  scope,
3547
3668
  signal,
3548
3669
  apiName,
3549
- internalState
3670
+ internalState,
3671
+ keepalive
3550
3672
  }) => {
3551
3673
  const start = performance.now();
3552
3674
  const { promise, resolve, reject } = withResolvers();
@@ -3578,9 +3700,16 @@ var waitForReady = ({
3578
3700
  reject(new Error(Object.values(scope.remotion_delayRenderTimeouts).map((d) => d.label).join(", ")));
3579
3701
  return;
3580
3702
  }
3581
- requestAnimationFrame(check);
3703
+ scheduleNextCheck();
3704
+ };
3705
+ const scheduleNextCheck = () => {
3706
+ const rafTick = new Promise((res) => {
3707
+ requestAnimationFrame(() => res());
3708
+ });
3709
+ const backgroundSafeTick = keepalive ? Promise.race([rafTick, keepalive.waitForTick()]) : rafTick;
3710
+ backgroundSafeTick.then(check);
3582
3711
  };
3583
- requestAnimationFrame(check);
3712
+ scheduleNextCheck();
3584
3713
  return promise;
3585
3714
  };
3586
3715
 
@@ -3671,11 +3800,11 @@ var internalRenderMediaOnWeb = async ({
3671
3800
  if (issue.severity === "error") {
3672
3801
  return Promise.reject(new Error(issue.message));
3673
3802
  }
3674
- Internals7.Log.warn({ logLevel, tag: "@remotion/web-renderer" }, issue.message);
3803
+ Internals8.Log.warn({ logLevel, tag: "@remotion/web-renderer" }, issue.message);
3675
3804
  }
3676
3805
  finalAudioCodec = audioResult.codec;
3677
3806
  }
3678
- const resolved = await Internals7.resolveVideoConfig({
3807
+ const resolved = await Internals8.resolveVideoConfig({
3679
3808
  calculateMetadata: composition.calculateMetadata ?? null,
3680
3809
  signal: signal ?? new AbortController().signal,
3681
3810
  defaultProps: composition.defaultProps ?? {},
@@ -3710,6 +3839,10 @@ var internalRenderMediaOnWeb = async ({
3710
3839
  }), 0);
3711
3840
  const { delayRenderScope, div, timeUpdater, collectAssets } = scaffold;
3712
3841
  const internalState = __using(__stack2, makeInternalState(), 0);
3842
+ const keepalive = __using(__stack2, createBackgroundKeepalive({
3843
+ fps: resolved.fps,
3844
+ logLevel
3845
+ }), 0);
3713
3846
  const artifactsHandler = handleArtifacts();
3714
3847
  const webFsTarget = outputTarget === "web-fs" ? await createWebFsTarget() : null;
3715
3848
  const target = webFsTarget ? new StreamTarget(webFsTarget.stream) : new BufferTarget;
@@ -3728,7 +3861,8 @@ var internalRenderMediaOnWeb = async ({
3728
3861
  scope: delayRenderScope,
3729
3862
  signal,
3730
3863
  apiName: "renderMediaOnWeb",
3731
- internalState
3864
+ internalState,
3865
+ keepalive
3732
3866
  });
3733
3867
  if (signal?.aborted) {
3734
3868
  throw new Error("renderMediaOnWeb() was cancelled");
@@ -3770,6 +3904,7 @@ var internalRenderMediaOnWeb = async ({
3770
3904
  scope: delayRenderScope,
3771
3905
  signal,
3772
3906
  apiName: "renderMediaOnWeb",
3907
+ keepalive,
3773
3908
  internalState
3774
3909
  });
3775
3910
  if (signal?.aborted) {
@@ -3838,7 +3973,7 @@ var internalRenderMediaOnWeb = async ({
3838
3973
  videoSampleSource.videoSampleSource.close();
3839
3974
  audioSampleSource?.audioSampleSource.close();
3840
3975
  await outputWithCleanup.output.finalize();
3841
- Internals7.Log.verbose({ logLevel, tag: "web-renderer" }, `Render timings: waitForReady=${internalState.getWaitForReadyTime().toFixed(2)}ms, createFrame=${internalState.getCreateFrameTime().toFixed(2)}ms, addSample=${internalState.getAddSampleTime().toFixed(2)}ms, audioMixing=${internalState.getAudioMixingTime().toFixed(2)}ms`);
3976
+ Internals8.Log.verbose({ logLevel, tag: "web-renderer" }, `Render timings: waitForReady=${internalState.getWaitForReadyTime().toFixed(2)}ms, createFrame=${internalState.getCreateFrameTime().toFixed(2)}ms, addSample=${internalState.getAddSampleTime().toFixed(2)}ms, audioMixing=${internalState.getAudioMixingTime().toFixed(2)}ms`);
3842
3977
  if (webFsTarget) {
3843
3978
  sendUsageEvent({
3844
3979
  licenseKey: licenseKey ?? null,
@@ -3882,7 +4017,7 @@ var internalRenderMediaOnWeb = async ({
3882
4017
  licenseKey: licenseKey ?? null,
3883
4018
  apiName: "renderMediaOnWeb"
3884
4019
  }).catch((err2) => {
3885
- Internals7.Log.error({ logLevel: "error", tag: "web-renderer" }, "Failed to send usage event", err2);
4020
+ Internals8.Log.error({ logLevel: "error", tag: "web-renderer" }, "Failed to send usage event", err2);
3886
4021
  });
3887
4022
  }
3888
4023
  throw err;
@@ -3923,7 +4058,7 @@ var renderMediaOnWeb = (options) => {
3923
4058
  };
3924
4059
  // src/render-still-on-web.tsx
3925
4060
  import {
3926
- Internals as Internals8
4061
+ Internals as Internals9
3927
4062
  } from "remotion";
3928
4063
  async function internalRenderStillOnWeb({
3929
4064
  frame,
@@ -3940,7 +4075,7 @@ async function internalRenderStillOnWeb({
3940
4075
  }) {
3941
4076
  let __stack = [];
3942
4077
  try {
3943
- const resolved = await Internals8.resolveVideoConfig({
4078
+ const resolved = await Internals9.resolveVideoConfig({
3944
4079
  calculateMetadata: composition.calculateMetadata ?? null,
3945
4080
  signal: signal ?? new AbortController().signal,
3946
4081
  defaultProps: composition.defaultProps ?? {},
@@ -3984,7 +4119,8 @@ async function internalRenderStillOnWeb({
3984
4119
  scope: delayRenderScope,
3985
4120
  signal,
3986
4121
  apiName: "renderStillOnWeb",
3987
- internalState: null
4122
+ internalState: null,
4123
+ keepalive: null
3988
4124
  });
3989
4125
  if (signal?.aborted) {
3990
4126
  throw new Error("renderStillOnWeb() was cancelled");
@@ -4014,7 +4150,7 @@ async function internalRenderStillOnWeb({
4014
4150
  licenseKey: licenseKey ?? null,
4015
4151
  apiName: "renderStillOnWeb"
4016
4152
  }).catch((err2) => {
4017
- Internals8.Log.error({ logLevel: "error", tag: "web-renderer" }, "Failed to send usage event", err2);
4153
+ Internals9.Log.error({ logLevel: "error", tag: "web-renderer" }, "Failed to send usage event", err2);
4018
4154
  });
4019
4155
  }
4020
4156
  throw err;
@@ -6,7 +6,8 @@ type HelperCanvas = {
6
6
  aPosition: number;
7
7
  aTexCoord: number;
8
8
  uTransform: WebGLUniformLocation | null;
9
- uProjection: WebGLUniformLocation | null;
9
+ uResolution: WebGLUniformLocation | null;
10
+ uOffset: WebGLUniformLocation | null;
10
11
  uTexture: WebGLUniformLocation | null;
11
12
  };
12
13
  cleanup: () => void;
@@ -1,5 +1,6 @@
1
1
  import type { DelayRenderScope } from 'remotion';
2
- export declare const waitForReady: ({ timeoutInMilliseconds, scope, signal, apiName, internalState, }: {
2
+ import type { BackgroundKeepalive } from './background-keepalive';
3
+ export declare const waitForReady: ({ timeoutInMilliseconds, scope, signal, apiName, internalState, keepalive, }: {
3
4
  timeoutInMilliseconds: number;
4
5
  scope: DelayRenderScope;
5
6
  signal: AbortSignal | null;
@@ -22,4 +23,5 @@ export declare const waitForReady: ({ timeoutInMilliseconds, scope, signal, apiN
22
23
  getAudioMixingTime: () => number;
23
24
  addAudioMixingTime: (time: number) => void;
24
25
  } | null;
26
+ keepalive: BackgroundKeepalive | null;
25
27
  }) => Promise<void>;
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.403",
6
+ "version": "4.0.404",
7
7
  "main": "dist/index.js",
8
8
  "type": "module",
9
9
  "sideEffects": false,
@@ -18,14 +18,14 @@
18
18
  "author": "Remotion <jonny@remotion.dev>",
19
19
  "license": "UNLICENSED",
20
20
  "dependencies": {
21
- "@remotion/licensing": "4.0.403",
22
- "remotion": "4.0.403",
21
+ "@remotion/licensing": "4.0.404",
22
+ "remotion": "4.0.404",
23
23
  "mediabunny": "1.27.3"
24
24
  },
25
25
  "devDependencies": {
26
- "@remotion/eslint-config-internal": "4.0.403",
27
- "@remotion/player": "4.0.403",
28
- "@remotion/media": "4.0.403",
26
+ "@remotion/eslint-config-internal": "4.0.404",
27
+ "@remotion/player": "4.0.404",
28
+ "@remotion/media": "4.0.404",
29
29
  "@typescript/native-preview": "7.0.0-dev.20260105.1",
30
30
  "@vitejs/plugin-react": "4.1.0",
31
31
  "@vitest/browser-playwright": "4.0.9",