remotion 4.0.84 → 4.0.86

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.
@@ -14,7 +14,7 @@ const timeline_position_state_js_1 = require("./timeline-position-state.js");
14
14
  const duration_state_js_1 = require("./video/duration-state.js");
15
15
  const RemotionRoot = ({ children, numberOfAudioTags }) => {
16
16
  const [remotionRootId] = (0, react_1.useState)(() => String((0, random_js_1.random)(null)));
17
- const [frame, setFrame] = (0, react_1.useState)({});
17
+ const [frame, setFrame] = (0, react_1.useState)(() => (0, timeline_position_state_js_1.getInitialFrameState)());
18
18
  const [playing, setPlaying] = (0, react_1.useState)(false);
19
19
  const imperativePlaying = (0, react_1.useRef)(false);
20
20
  const [fastRefreshes, setFastRefreshes] = (0, react_1.useState)(0);
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ type Block = {
3
+ id: string;
4
+ };
5
+ type OnBufferingCallback = () => void;
6
+ type OnResumeCallback = () => void;
7
+ type ListenForBuffering = (callback: OnBufferingCallback) => {
8
+ remove: () => void;
9
+ };
10
+ type ListenForResume = (callback: OnResumeCallback) => {
11
+ remove: () => void;
12
+ };
13
+ type AddBlock = (block: Block) => {
14
+ unblock: () => void;
15
+ };
16
+ type BufferManager = {
17
+ blocks: Block[];
18
+ addBlock: AddBlock;
19
+ listenForBuffering: ListenForBuffering;
20
+ listenForResume: ListenForResume;
21
+ };
22
+ export declare const BufferingContextReact: React.Context<BufferManager | null>;
23
+ export declare const BufferingProvider: React.FC<{
24
+ children: React.ReactNode;
25
+ }>;
26
+ export {};
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.BufferingProvider = exports.BufferingContextReact = void 0;
27
+ const jsx_runtime_1 = require("react/jsx-runtime");
28
+ const react_1 = __importStar(require("react"));
29
+ const createBufferManager = () => {
30
+ let blocks = [];
31
+ let onBufferingCallback = [];
32
+ let onResumeCallback = [];
33
+ const addBlock = (block) => {
34
+ blocks.push(block);
35
+ onBufferingCallback.forEach((callback) => callback());
36
+ return {
37
+ unblock: () => {
38
+ blocks = blocks.filter((b) => b !== block);
39
+ if (blocks.length === 0) {
40
+ onResumeCallback.forEach((callback) => callback());
41
+ }
42
+ },
43
+ };
44
+ };
45
+ const listenForBuffering = (callback) => {
46
+ onBufferingCallback.push(callback);
47
+ return {
48
+ remove: () => {
49
+ onBufferingCallback = onBufferingCallback.filter((c) => c !== callback);
50
+ },
51
+ };
52
+ };
53
+ const listenForResume = (callback) => {
54
+ onResumeCallback.push(callback);
55
+ return {
56
+ remove: () => {
57
+ onResumeCallback = onResumeCallback.filter((c) => c !== callback);
58
+ },
59
+ };
60
+ };
61
+ return {
62
+ blocks,
63
+ addBlock,
64
+ listenForBuffering,
65
+ listenForResume,
66
+ };
67
+ };
68
+ exports.BufferingContextReact = react_1.default.createContext(null);
69
+ const BufferingProvider = ({ children }) => {
70
+ const [bufferManager] = (0, react_1.useState)(() => {
71
+ return createBufferManager();
72
+ });
73
+ return ((0, jsx_runtime_1.jsx)(exports.BufferingContextReact.Provider, { value: bufferManager, children: children }));
74
+ };
75
+ exports.BufferingProvider = BufferingProvider;
@@ -84,7 +84,7 @@ export { interpolateColors } from './interpolate-colors.js';
84
84
  export { Loop } from './loop/index.js';
85
85
  export { ClipRegion } from './NativeLayers.js';
86
86
  export { EasingFunction, ExtrapolateType, interpolate, InterpolateOptions, random, RandomSeed, } from './no-react';
87
- export { prefetch } from './prefetch.js';
87
+ export { prefetch, PrefetchOnProgress } from './prefetch.js';
88
88
  export { registerRoot } from './register-root.js';
89
89
  export { LayoutAndStyle, Sequence, SequenceProps, SequencePropsWithoutDuration, } from './Sequence.js';
90
90
  export { Series } from './series/index.js';
@@ -112,7 +112,9 @@ export declare const Internals: {
112
112
  }>>;
113
113
  readonly REMOTION_STUDIO_CONTAINER_ELEMENT: "__remotion-studio-container";
114
114
  readonly RenderAssetManager: import("react").Context<import("./RenderAssetManager.js").RenderAssetManagerContext>;
115
- readonly persistCurrentFrame: (frame: number, composition: string) => void;
115
+ readonly persistCurrentFrame: (time: {
116
+ [x: string]: number;
117
+ }) => void;
116
118
  readonly useTimelineSetFrame: () => (u: import("react").SetStateAction<Record<string, number>>) => void;
117
119
  readonly FILE_TOKEN: "remotion-file:";
118
120
  readonly DATE_TOKEN: "remotion-date:";
@@ -3,6 +3,10 @@ type FetchAndPreload = {
3
3
  free: () => void;
4
4
  waitUntilDone: () => Promise<string>;
5
5
  };
6
+ export type PrefetchOnProgress = (options: {
7
+ totalBytes: number | null;
8
+ loadedBytes: number;
9
+ }) => void;
6
10
  /**
7
11
  * @description When you call the preFetch() function, an asset will be fetched and kept in memory so it is ready when you want to play it in a <Player>.
8
12
  * @see [Documentation](https://www.remotion.dev/docs/prefetch)
@@ -10,5 +14,6 @@ type FetchAndPreload = {
10
14
  export declare const prefetch: (src: string, options?: {
11
15
  method?: 'blob-url' | 'base64';
12
16
  contentType?: string;
17
+ onProgress?: PrefetchOnProgress;
13
18
  }) => FetchAndPreload;
14
19
  export {};
@@ -23,6 +23,31 @@ const blobToBase64 = function (blob) {
23
23
  reader.readAsDataURL(blob);
24
24
  });
25
25
  };
26
+ const getBlobFromReader = async ({ reader, contentType, contentLength, onProgress, }) => {
27
+ let receivedLength = 0;
28
+ const chunks = [];
29
+ // eslint-disable-next-line no-constant-condition
30
+ while (true) {
31
+ const { done, value } = await reader.read();
32
+ if (done) {
33
+ break;
34
+ }
35
+ chunks.push(value);
36
+ receivedLength += value.length;
37
+ if (onProgress) {
38
+ onProgress({ loadedBytes: receivedLength, totalBytes: contentLength });
39
+ }
40
+ }
41
+ const chunksAll = new Uint8Array(receivedLength);
42
+ let position = 0;
43
+ for (const chunk of chunks) {
44
+ chunksAll.set(chunk, position);
45
+ position += chunk.length;
46
+ }
47
+ return new Blob([chunksAll], {
48
+ type: contentType !== null && contentType !== void 0 ? contentType : undefined,
49
+ });
50
+ };
26
51
  /**
27
52
  * @description When you call the preFetch() function, an asset will be fetched and kept in memory so it is ready when you want to play it in a <Player>.
28
53
  * @see [Documentation](https://www.remotion.dev/docs/prefetch)
@@ -50,6 +75,7 @@ const prefetch = (src, options) => {
50
75
  signal: controller.signal,
51
76
  })
52
77
  .then((res) => {
78
+ var _a, _b, _c;
53
79
  canBeAborted = false;
54
80
  if (canceled) {
55
81
  return null;
@@ -57,19 +83,33 @@ const prefetch = (src, options) => {
57
83
  if (!res.ok) {
58
84
  throw new Error(`HTTP error, status = ${res.status}`);
59
85
  }
60
- return res.blob();
86
+ const headerContentType = res.headers.get('Content-Type');
87
+ const contentType = (_a = options === null || options === void 0 ? void 0 : options.contentType) !== null && _a !== void 0 ? _a : headerContentType;
88
+ const hasProperContentType = contentType &&
89
+ (contentType.startsWith('video/') ||
90
+ contentType.startsWith('audio/') ||
91
+ contentType.startsWith('image/'));
92
+ if (!hasProperContentType) {
93
+ // eslint-disable-next-line no-console
94
+ console.warn(`Called prefetch() on ${src} which returned a "Content-Type" of ${headerContentType}. Prefetched content should have a proper content type (video/... or audio/...) or a contentType passed the options of prefetch(). Otherwise, prefetching will not work properly in all browsers.`);
95
+ }
96
+ if (!res.body) {
97
+ throw new Error(`HTTP response of ${src} has no body`);
98
+ }
99
+ const reader = res.body.getReader();
100
+ return getBlobFromReader({
101
+ reader,
102
+ contentType: (_c = (_b = options === null || options === void 0 ? void 0 : options.contentType) !== null && _b !== void 0 ? _b : headerContentType) !== null && _c !== void 0 ? _c : null,
103
+ contentLength: res.headers.get('Content-Length')
104
+ ? parseInt(res.headers.get('Content-Length'), 10)
105
+ : null,
106
+ onProgress: options === null || options === void 0 ? void 0 : options.onProgress,
107
+ });
61
108
  })
62
109
  .then((buf) => {
63
110
  if (!buf) {
64
111
  return;
65
112
  }
66
- if (!buf.type.startsWith('video/') &&
67
- !buf.type.startsWith('audio/') &&
68
- !buf.type.startsWith('image/') &&
69
- !(options === null || options === void 0 ? void 0 : options.contentType)) {
70
- // eslint-disable-next-line no-console
71
- console.warn(`Called prefetch() on ${src} which returned a "Content-Type" of ${buf.type}. Prefetched content should have a proper content type (video/... or audio/...) or a contentType passed the options of prefetch(). Otherwise, prefetching will not work properly in all browsers.`);
72
- }
73
113
  const actualBlob = (options === null || options === void 0 ? void 0 : options.contentType)
74
114
  ? new Blob([buf], { type: options.contentType })
75
115
  : buf;
@@ -18,7 +18,9 @@ export type SetTimelineContextValue = {
18
18
  };
19
19
  export declare const TimelineContext: import("react").Context<TimelineContextValue>;
20
20
  export declare const SetTimelineContext: import("react").Context<SetTimelineContextValue>;
21
- export declare const persistCurrentFrame: (frame: number, composition: string) => void;
21
+ type CurrentTimePerComposition = Record<string, number>;
22
+ export declare const persistCurrentFrame: (time: CurrentTimePerComposition) => void;
23
+ export declare const getInitialFrameState: () => CurrentTimePerComposition;
22
24
  export declare const getFrameForComposition: (composition: string) => number;
23
25
  export declare const useTimelinePosition: () => number;
24
26
  export declare const useTimelineSetFrame: () => (u: React.SetStateAction<Record<string, number>>) => void;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.usePlayingState = exports.useTimelineSetFrame = exports.useTimelinePosition = exports.getFrameForComposition = exports.persistCurrentFrame = exports.SetTimelineContext = exports.TimelineContext = void 0;
3
+ exports.usePlayingState = exports.useTimelineSetFrame = exports.useTimelinePosition = exports.getFrameForComposition = exports.getInitialFrameState = exports.persistCurrentFrame = exports.SetTimelineContext = exports.TimelineContext = void 0;
4
4
  const react_1 = require("react");
5
5
  const use_video_js_1 = require("./use-video.js");
6
6
  exports.TimelineContext = (0, react_1.createContext)({
@@ -24,19 +24,27 @@ exports.SetTimelineContext = (0, react_1.createContext)({
24
24
  throw new Error('default');
25
25
  },
26
26
  });
27
- const makeKey = (composition) => {
28
- return `remotion.time.${composition}`;
27
+ const makeKey = () => {
28
+ return `remotion.time-all`;
29
29
  };
30
- const persistCurrentFrame = (frame, composition) => {
31
- localStorage.setItem(makeKey(composition), String(frame));
30
+ const persistCurrentFrame = (time) => {
31
+ localStorage.setItem(makeKey(), JSON.stringify(time));
32
32
  };
33
33
  exports.persistCurrentFrame = persistCurrentFrame;
34
+ const getInitialFrameState = () => {
35
+ var _a;
36
+ const item = (_a = localStorage.getItem(makeKey())) !== null && _a !== void 0 ? _a : '{}';
37
+ const obj = JSON.parse(item);
38
+ return obj;
39
+ };
40
+ exports.getInitialFrameState = getInitialFrameState;
34
41
  const getFrameForComposition = (composition) => {
35
- var _a, _b;
36
- const frame = localStorage.getItem(makeKey(composition));
37
- return frame
38
- ? Number(frame)
39
- : (_b = (typeof window === 'undefined' ? 0 : (_a = window.remotion_initialFrame) !== null && _a !== void 0 ? _a : 0)) !== null && _b !== void 0 ? _b : 0;
42
+ var _a, _b, _c;
43
+ const item = (_a = localStorage.getItem(makeKey())) !== null && _a !== void 0 ? _a : '{}';
44
+ const obj = JSON.parse(item);
45
+ return obj[composition]
46
+ ? Number(obj[composition])
47
+ : (_c = (typeof window === 'undefined' ? 0 : (_b = window.remotion_initialFrame) !== null && _b !== void 0 ? _b : 0)) !== null && _c !== void 0 ? _c : 0;
40
48
  };
41
49
  exports.getFrameForComposition = getFrameForComposition;
42
50
  const useTimelinePosition = () => {
@@ -0,0 +1,5 @@
1
+ export declare const useBuffer: () => {
2
+ delayPlayback: () => {
3
+ unblock: () => void;
4
+ };
5
+ };
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useBuffer = void 0;
4
+ const react_1 = require("react");
5
+ const buffering_1 = require("./buffering");
6
+ const useBuffer = () => {
7
+ const buffer = (0, react_1.useContext)(buffering_1.BufferingContextReact);
8
+ if (!buffer) {
9
+ throw new TypeError('Can only use useBuffer() inside a Remotion composition');
10
+ }
11
+ return {
12
+ delayPlayback: () => {
13
+ const { unblock } = buffer.addBlock({
14
+ id: String(Math.random()),
15
+ });
16
+ return { unblock };
17
+ },
18
+ };
19
+ };
20
+ exports.useBuffer = useBuffer;
@@ -57,12 +57,10 @@ const useMediaPlayback = ({ mediaRef, src, mediaType, playbackRate: localPlaybac
57
57
  mediaRef.current.playbackRate = playbackRateToSet;
58
58
  }
59
59
  const desiredUnclampedTime = (0, get_current_time_js_1.getMediaTime)({
60
- fps,
61
60
  frame,
62
- src,
63
61
  playbackRate: localPlaybackRate,
64
62
  startFrom: -mediaStartsAt,
65
- mediaType,
63
+ fps,
66
64
  });
67
65
  const { duration } = mediaRef.current;
68
66
  const shouldBeTime = !Number.isNaN(duration) && Number.isFinite(duration)
@@ -1 +1 @@
1
- export declare const VERSION = "4.0.84";
1
+ export declare const VERSION = "4.0.86";
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
4
  // Automatically generated on publish
5
- exports.VERSION = '4.0.84';
5
+ exports.VERSION = '4.0.86';
@@ -15,8 +15,8 @@ const timeline_position_state_js_1 = require("../timeline-position-state.js");
15
15
  const use_current_frame_js_1 = require("../use-current-frame.js");
16
16
  const use_unsafe_video_config_js_1 = require("../use-unsafe-video-config.js");
17
17
  const volume_prop_js_1 = require("../volume-prop.js");
18
- const warn_about_non_seekable_media_js_1 = require("../warn-about-non-seekable-media.js");
19
18
  const get_current_time_js_1 = require("./get-current-time.js");
19
+ const seek_until_right_js_1 = require("./seek-until-right.js");
20
20
  const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAmplificationDuringRender, playbackRate, onDuration, toneFrequency, name, acceptableTimeShiftInSeconds, ...props }, ref) => {
21
21
  const absoluteFrame = (0, timeline_position_state_js_1.useTimelinePosition)();
22
22
  const frame = (0, use_current_frame_js_1.useCurrentFrame)();
@@ -96,16 +96,12 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
96
96
  if (!current) {
97
97
  return;
98
98
  }
99
- const currentTime = (() => {
100
- return (0, get_current_time_js_1.getMediaTime)({
101
- fps: videoConfig.fps,
102
- frame,
103
- src: props.src,
104
- playbackRate: playbackRate || 1,
105
- startFrom: -mediaStartsAt,
106
- mediaType: 'video',
107
- });
108
- })();
99
+ const currentTime = (0, get_current_time_js_1.getMediaTime)({
100
+ frame,
101
+ playbackRate: playbackRate || 1,
102
+ startFrom: -mediaStartsAt,
103
+ fps: videoConfig.fps,
104
+ });
109
105
  const handle = (0, delay_render_js_1.delayRender)(`Rendering <Video /> with src="${props.src}"`);
110
106
  if (process.env.NODE_ENV === 'test') {
111
107
  (0, delay_render_js_1.continueRender)(handle);
@@ -124,25 +120,13 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
124
120
  current.removeEventListener('loadeddata', loadedDataHandler);
125
121
  };
126
122
  }
127
- current.currentTime = currentTime;
128
- const seekedHandler = () => {
129
- (0, warn_about_non_seekable_media_js_1.warnAboutNonSeekableMedia)(current, 'exception');
130
- if (window.navigator.platform.startsWith('Mac')) {
131
- // Improve me: This is ensures frame perfectness but slows down render.
132
- // Please see this issue for context: https://github.com/remotion-dev/remotion/issues/200
133
- // Only affects macOS since it uses VideoToolbox decoding.
134
- setTimeout(() => {
135
- (0, delay_render_js_1.continueRender)(handle);
136
- }, 100);
137
- }
138
- else {
139
- (0, delay_render_js_1.continueRender)(handle);
140
- }
141
- };
142
- current.addEventListener('seeked', seekedHandler, { once: true });
143
123
  const endedHandler = () => {
144
124
  (0, delay_render_js_1.continueRender)(handle);
145
125
  };
126
+ const seek = (0, seek_until_right_js_1.seekToTimeMultipleUntilRight)(current, currentTime, videoConfig.fps);
127
+ seek.prom.then(() => {
128
+ (0, delay_render_js_1.continueRender)(handle);
129
+ });
146
130
  current.addEventListener('ended', endedHandler, { once: true });
147
131
  const errorHandler = () => {
148
132
  var _a;
@@ -162,9 +146,9 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
162
146
  current.addEventListener('error', errorHandler, { once: true });
163
147
  // If video skips to another frame or unmounts, we clear the created handle
164
148
  return () => {
149
+ seek.cancel();
165
150
  current.removeEventListener('ended', endedHandler);
166
151
  current.removeEventListener('error', errorHandler);
167
- current.removeEventListener('seeked', seekedHandler);
168
152
  (0, delay_render_js_1.continueRender)(handle);
169
153
  };
170
154
  }, [
@@ -3,11 +3,9 @@ export declare const getExpectedMediaFrameUncorrected: ({ frame, playbackRate, s
3
3
  playbackRate: number;
4
4
  startFrom: number;
5
5
  }) => number;
6
- export declare const getMediaTime: ({ fps, frame, src, playbackRate, startFrom, mediaType, }: {
6
+ export declare const getMediaTime: ({ fps, frame, playbackRate, startFrom, }: {
7
7
  fps: number;
8
8
  frame: number;
9
- src: string;
10
9
  playbackRate: number;
11
10
  startFrom: number;
12
- mediaType: 'video' | 'audio';
13
11
  }) => number;
@@ -7,26 +7,13 @@ const getExpectedMediaFrameUncorrected = ({ frame, playbackRate, startFrom, }) =
7
7
  return (0, interpolate_js_1.interpolate)(frame, [-1, startFrom, startFrom + 1], [-1, startFrom, startFrom + playbackRate]);
8
8
  };
9
9
  exports.getExpectedMediaFrameUncorrected = getExpectedMediaFrameUncorrected;
10
- const getMediaTime = ({ fps, frame, src, playbackRate, startFrom, mediaType, }) => {
10
+ const getMediaTime = ({ fps, frame, playbackRate, startFrom, }) => {
11
11
  const expectedFrame = (0, exports.getExpectedMediaFrameUncorrected)({
12
12
  frame,
13
13
  playbackRate,
14
14
  startFrom,
15
15
  });
16
- const isChrome = typeof window !== 'undefined' &&
17
- window.navigator.userAgent.match(/Chrome\/([0-9]+)/);
18
- if (isChrome &&
19
- Number(isChrome[1]) < 112 &&
20
- mediaType === 'video' &&
21
- src.endsWith('.mp4')) {
22
- // In Chrome, for MP4s, if 30fps, the first frame is still displayed at 0.033333
23
- // even though after that it increases by 0.033333333 each.
24
- // So frame = 0 in Remotion is like frame = 1 for the browser
25
- return (expectedFrame + 1) / fps;
26
- }
27
- // For WebM videos, we need to add a little bit of shift to get the right frame.
28
16
  const msPerFrame = 1000 / fps;
29
- const msShift = msPerFrame / 2;
30
- return (expectedFrame * msPerFrame + msShift) / 1000;
17
+ return (expectedFrame * msPerFrame) / 1000;
31
18
  };
32
19
  exports.getMediaTime = getMediaTime;
@@ -4,14 +4,34 @@ exports.seekToTimeMultipleUntilRight = exports.seekToTime = void 0;
4
4
  const seekToTime = (element, desiredTime) => {
5
5
  element.currentTime = desiredTime;
6
6
  let cancel;
7
+ let cancelSeeked = null;
7
8
  const prom = new Promise((resolve) => {
8
- cancel = element.requestVideoFrameCallback((_cb, metadata) => {
9
- resolve(metadata.mediaTime);
9
+ cancel = element.requestVideoFrameCallback((now, metadata) => {
10
+ const displayIn = metadata.expectedDisplayTime - now;
11
+ if (displayIn <= 0) {
12
+ resolve(metadata.mediaTime);
13
+ return;
14
+ }
15
+ setTimeout(() => {
16
+ resolve(metadata.mediaTime);
17
+ }, displayIn + 50);
18
+ });
19
+ });
20
+ const waitForSeekedEvent = new Promise((resolve) => {
21
+ const onDone = () => {
22
+ resolve();
23
+ };
24
+ element.addEventListener('seeked', onDone, {
25
+ once: true,
10
26
  });
27
+ cancelSeeked = () => {
28
+ element.removeEventListener('seeked', onDone);
29
+ };
11
30
  });
12
31
  return {
13
- wait: prom,
32
+ wait: Promise.all([prom, waitForSeekedEvent]).then(([time]) => time),
14
33
  cancel: () => {
34
+ cancelSeeked === null || cancelSeeked === void 0 ? void 0 : cancelSeeked();
15
35
  element.cancelVideoFrameCallback(cancel);
16
36
  },
17
37
  };
@@ -24,7 +44,6 @@ const seekToTimeMultipleUntilRight = (element, desiredTime, fps) => {
24
44
  const firstSeek = (0, exports.seekToTime)(element, desiredTime + threshold);
25
45
  firstSeek.wait.then((seekedTo) => {
26
46
  const difference = Math.abs(desiredTime - seekedTo);
27
- const ident = Math.random();
28
47
  if (difference < threshold) {
29
48
  return resolve();
30
49
  }
@@ -34,7 +53,6 @@ const seekToTimeMultipleUntilRight = (element, desiredTime, fps) => {
34
53
  newSeek.wait
35
54
  .then((newTime) => {
36
55
  const newDifference = Math.abs(desiredTime - newTime);
37
- console.log(ident, 'before', seekedTo, 'after', newTime, 'desired', desiredTime);
38
56
  if (newDifference < threshold) {
39
57
  return resolve();
40
58
  }
@@ -18,7 +18,13 @@ const warnAboutNonSeekableMedia = (ref, type) => {
18
18
  }
19
19
  const range = { start: ref.seekable.start(0), end: ref.seekable.end(0) };
20
20
  if (range.start === 0 && range.end === 0) {
21
- const msg = `The media ${ref.src} cannot be seeked. This could be one of two reasons: 1) The media resource was replaced while the video is playing but it was not loaded yet. 2) The media does not support seeking. Please see https://remotion.dev/docs/non-seekable-media for assistance.`;
21
+ const msg = [
22
+ `The media ${ref.src} cannot be seeked. This could be one of few reasons:`,
23
+ '1) The media resource was replaced while the video is playing but it was not loaded yet.',
24
+ '2) The media does not support seeking.',
25
+ '3) The media was loaded with security headers prventing it from being included.',
26
+ 'Please see https://remotion.dev/docs/non-seekable-media for assistance.',
27
+ ].join('\n');
22
28
  if (type === 'console-error') {
23
29
  // eslint-disable-next-line no-console
24
30
  console.error(msg);
@@ -132,7 +132,7 @@ function truthy(value) {
132
132
  }
133
133
 
134
134
  // Automatically generated on publish
135
- const VERSION = '4.0.84';
135
+ const VERSION = '4.0.86';
136
136
 
137
137
  const checkMultipleRemotionVersions = () => {
138
138
  if (typeof globalThis === 'undefined') {
@@ -864,18 +864,25 @@ const SetTimelineContext = createContext({
864
864
  throw new Error('default');
865
865
  },
866
866
  });
867
- const makeKey = (composition) => {
868
- return `remotion.time.${composition}`;
867
+ const makeKey = () => {
868
+ return `remotion.time-all`;
869
869
  };
870
- const persistCurrentFrame = (frame, composition) => {
871
- localStorage.setItem(makeKey(composition), String(frame));
870
+ const persistCurrentFrame = (time) => {
871
+ localStorage.setItem(makeKey(), JSON.stringify(time));
872
+ };
873
+ const getInitialFrameState = () => {
874
+ var _a;
875
+ const item = (_a = localStorage.getItem(makeKey())) !== null && _a !== void 0 ? _a : '{}';
876
+ const obj = JSON.parse(item);
877
+ return obj;
872
878
  };
873
879
  const getFrameForComposition = (composition) => {
874
- var _a, _b;
875
- const frame = localStorage.getItem(makeKey(composition));
876
- return frame
877
- ? Number(frame)
878
- : (_b = (typeof window === 'undefined' ? 0 : (_a = window.remotion_initialFrame) !== null && _a !== void 0 ? _a : 0)) !== null && _b !== void 0 ? _b : 0;
880
+ var _a, _b, _c;
881
+ const item = (_a = localStorage.getItem(makeKey())) !== null && _a !== void 0 ? _a : '{}';
882
+ const obj = JSON.parse(item);
883
+ return obj[composition]
884
+ ? Number(obj[composition])
885
+ : (_c = (typeof window === 'undefined' ? 0 : (_b = window.remotion_initialFrame) !== null && _b !== void 0 ? _b : 0)) !== null && _c !== void 0 ? _c : 0;
879
886
  };
880
887
  const useTimelinePosition = () => {
881
888
  var _a, _b;
@@ -906,6 +913,7 @@ var TimelinePosition = /*#__PURE__*/Object.freeze({
906
913
  TimelineContext: TimelineContext,
907
914
  SetTimelineContext: SetTimelineContext,
908
915
  persistCurrentFrame: persistCurrentFrame,
916
+ getInitialFrameState: getInitialFrameState,
909
917
  getFrameForComposition: getFrameForComposition,
910
918
  useTimelinePosition: useTimelinePosition,
911
919
  useTimelineSetFrame: useTimelineSetFrame,
@@ -1258,6 +1266,31 @@ const blobToBase64 = function (blob) {
1258
1266
  reader.readAsDataURL(blob);
1259
1267
  });
1260
1268
  };
1269
+ const getBlobFromReader = async ({ reader, contentType, contentLength, onProgress, }) => {
1270
+ let receivedLength = 0;
1271
+ const chunks = [];
1272
+ // eslint-disable-next-line no-constant-condition
1273
+ while (true) {
1274
+ const { done, value } = await reader.read();
1275
+ if (done) {
1276
+ break;
1277
+ }
1278
+ chunks.push(value);
1279
+ receivedLength += value.length;
1280
+ if (onProgress) {
1281
+ onProgress({ loadedBytes: receivedLength, totalBytes: contentLength });
1282
+ }
1283
+ }
1284
+ const chunksAll = new Uint8Array(receivedLength);
1285
+ let position = 0;
1286
+ for (const chunk of chunks) {
1287
+ chunksAll.set(chunk, position);
1288
+ position += chunk.length;
1289
+ }
1290
+ return new Blob([chunksAll], {
1291
+ type: contentType !== null && contentType !== void 0 ? contentType : undefined,
1292
+ });
1293
+ };
1261
1294
  /**
1262
1295
  * @description When you call the preFetch() function, an asset will be fetched and kept in memory so it is ready when you want to play it in a <Player>.
1263
1296
  * @see [Documentation](https://www.remotion.dev/docs/prefetch)
@@ -1285,6 +1318,7 @@ const prefetch = (src, options) => {
1285
1318
  signal: controller.signal,
1286
1319
  })
1287
1320
  .then((res) => {
1321
+ var _a, _b, _c;
1288
1322
  canBeAborted = false;
1289
1323
  if (canceled) {
1290
1324
  return null;
@@ -1292,19 +1326,33 @@ const prefetch = (src, options) => {
1292
1326
  if (!res.ok) {
1293
1327
  throw new Error(`HTTP error, status = ${res.status}`);
1294
1328
  }
1295
- return res.blob();
1329
+ const headerContentType = res.headers.get('Content-Type');
1330
+ const contentType = (_a = options === null || options === void 0 ? void 0 : options.contentType) !== null && _a !== void 0 ? _a : headerContentType;
1331
+ const hasProperContentType = contentType &&
1332
+ (contentType.startsWith('video/') ||
1333
+ contentType.startsWith('audio/') ||
1334
+ contentType.startsWith('image/'));
1335
+ if (!hasProperContentType) {
1336
+ // eslint-disable-next-line no-console
1337
+ console.warn(`Called prefetch() on ${src} which returned a "Content-Type" of ${headerContentType}. Prefetched content should have a proper content type (video/... or audio/...) or a contentType passed the options of prefetch(). Otherwise, prefetching will not work properly in all browsers.`);
1338
+ }
1339
+ if (!res.body) {
1340
+ throw new Error(`HTTP response of ${src} has no body`);
1341
+ }
1342
+ const reader = res.body.getReader();
1343
+ return getBlobFromReader({
1344
+ reader,
1345
+ contentType: (_c = (_b = options === null || options === void 0 ? void 0 : options.contentType) !== null && _b !== void 0 ? _b : headerContentType) !== null && _c !== void 0 ? _c : null,
1346
+ contentLength: res.headers.get('Content-Length')
1347
+ ? parseInt(res.headers.get('Content-Length'), 10)
1348
+ : null,
1349
+ onProgress: options === null || options === void 0 ? void 0 : options.onProgress,
1350
+ });
1296
1351
  })
1297
1352
  .then((buf) => {
1298
1353
  if (!buf) {
1299
1354
  return;
1300
1355
  }
1301
- if (!buf.type.startsWith('video/') &&
1302
- !buf.type.startsWith('audio/') &&
1303
- !buf.type.startsWith('image/') &&
1304
- !(options === null || options === void 0 ? void 0 : options.contentType)) {
1305
- // eslint-disable-next-line no-console
1306
- console.warn(`Called prefetch() on ${src} which returned a "Content-Type" of ${buf.type}. Prefetched content should have a proper content type (video/... or audio/...) or a contentType passed the options of prefetch(). Otherwise, prefetching will not work properly in all browsers.`);
1307
- }
1308
1356
  const actualBlob = (options === null || options === void 0 ? void 0 : options.contentType)
1309
1357
  ? new Blob([buf], { type: options.contentType })
1310
1358
  : buf;
@@ -1795,27 +1843,14 @@ function interpolate(input, inputRange, outputRange, options) {
1795
1843
  const getExpectedMediaFrameUncorrected = ({ frame, playbackRate, startFrom, }) => {
1796
1844
  return interpolate(frame, [-1, startFrom, startFrom + 1], [-1, startFrom, startFrom + playbackRate]);
1797
1845
  };
1798
- const getMediaTime = ({ fps, frame, src, playbackRate, startFrom, mediaType, }) => {
1846
+ const getMediaTime = ({ fps, frame, playbackRate, startFrom, }) => {
1799
1847
  const expectedFrame = getExpectedMediaFrameUncorrected({
1800
1848
  frame,
1801
1849
  playbackRate,
1802
1850
  startFrom,
1803
1851
  });
1804
- const isChrome = typeof window !== 'undefined' &&
1805
- window.navigator.userAgent.match(/Chrome\/([0-9]+)/);
1806
- if (isChrome &&
1807
- Number(isChrome[1]) < 112 &&
1808
- mediaType === 'video' &&
1809
- src.endsWith('.mp4')) {
1810
- // In Chrome, for MP4s, if 30fps, the first frame is still displayed at 0.033333
1811
- // even though after that it increases by 0.033333333 each.
1812
- // So frame = 0 in Remotion is like frame = 1 for the browser
1813
- return (expectedFrame + 1) / fps;
1814
- }
1815
- // For WebM videos, we need to add a little bit of shift to get the right frame.
1816
1852
  const msPerFrame = 1000 / fps;
1817
- const msShift = msPerFrame / 2;
1818
- return (expectedFrame * msPerFrame + msShift) / 1000;
1853
+ return (expectedFrame * msPerFrame) / 1000;
1819
1854
  };
1820
1855
 
1821
1856
  const toSeconds = (time, fps) => {
@@ -1892,7 +1927,13 @@ const warnAboutNonSeekableMedia = (ref, type) => {
1892
1927
  }
1893
1928
  const range = { start: ref.seekable.start(0), end: ref.seekable.end(0) };
1894
1929
  if (range.start === 0 && range.end === 0) {
1895
- const msg = `The media ${ref.src} cannot be seeked. This could be one of two reasons: 1) The media resource was replaced while the video is playing but it was not loaded yet. 2) The media does not support seeking. Please see https://remotion.dev/docs/non-seekable-media for assistance.`;
1930
+ const msg = [
1931
+ `The media ${ref.src} cannot be seeked. This could be one of few reasons:`,
1932
+ '1) The media resource was replaced while the video is playing but it was not loaded yet.',
1933
+ '2) The media does not support seeking.',
1934
+ '3) The media was loaded with security headers prventing it from being included.',
1935
+ 'Please see https://remotion.dev/docs/non-seekable-media for assistance.',
1936
+ ].join('\n');
1896
1937
  if (type === 'console-error') {
1897
1938
  // eslint-disable-next-line no-console
1898
1939
  console.error(msg);
@@ -1955,12 +1996,10 @@ const useMediaPlayback = ({ mediaRef, src, mediaType, playbackRate: localPlaybac
1955
1996
  mediaRef.current.playbackRate = playbackRateToSet;
1956
1997
  }
1957
1998
  const desiredUnclampedTime = getMediaTime({
1958
- fps,
1959
1999
  frame,
1960
- src,
1961
2000
  playbackRate: localPlaybackRate,
1962
2001
  startFrom: -mediaStartsAt,
1963
- mediaType,
2002
+ fps,
1964
2003
  });
1965
2004
  const { duration } = mediaRef.current;
1966
2005
  const shouldBeTime = !Number.isNaN(duration) && Number.isFinite(duration)
@@ -3472,7 +3511,7 @@ const waitForRoot = (fn) => {
3472
3511
 
3473
3512
  const RemotionRoot = ({ children, numberOfAudioTags }) => {
3474
3513
  const [remotionRootId] = useState(() => String(random(null)));
3475
- const [frame, setFrame] = useState({});
3514
+ const [frame, setFrame] = useState(() => getInitialFrameState());
3476
3515
  const [playing, setPlaying] = useState(false);
3477
3516
  const imperativePlaying = useRef(false);
3478
3517
  const [fastRefreshes, setFastRefreshes] = useState(0);
@@ -4731,6 +4770,80 @@ const OffthreadVideo = (props) => {
4731
4770
  return (jsx(VideoForDevelopment, { _remotionInternalStack: stack !== null && stack !== void 0 ? stack : null, _remotionInternalNativeLoopPassed: false, onDuration: onDuration, onlyWarnForMediaSeekingError: true, ...withoutTransparent }));
4732
4771
  };
4733
4772
 
4773
+ const seekToTime = (element, desiredTime) => {
4774
+ element.currentTime = desiredTime;
4775
+ let cancel;
4776
+ let cancelSeeked = null;
4777
+ const prom = new Promise((resolve) => {
4778
+ cancel = element.requestVideoFrameCallback((now, metadata) => {
4779
+ const displayIn = metadata.expectedDisplayTime - now;
4780
+ if (displayIn <= 0) {
4781
+ resolve(metadata.mediaTime);
4782
+ return;
4783
+ }
4784
+ setTimeout(() => {
4785
+ resolve(metadata.mediaTime);
4786
+ }, displayIn + 50);
4787
+ });
4788
+ });
4789
+ const waitForSeekedEvent = new Promise((resolve) => {
4790
+ const onDone = () => {
4791
+ resolve();
4792
+ };
4793
+ element.addEventListener('seeked', onDone, {
4794
+ once: true,
4795
+ });
4796
+ cancelSeeked = () => {
4797
+ element.removeEventListener('seeked', onDone);
4798
+ };
4799
+ });
4800
+ return {
4801
+ wait: Promise.all([prom, waitForSeekedEvent]).then(([time]) => time),
4802
+ cancel: () => {
4803
+ cancelSeeked === null || cancelSeeked === void 0 ? void 0 : cancelSeeked();
4804
+ element.cancelVideoFrameCallback(cancel);
4805
+ },
4806
+ };
4807
+ };
4808
+ const seekToTimeMultipleUntilRight = (element, desiredTime, fps) => {
4809
+ const threshold = 1 / fps / 2;
4810
+ let currentCancel = () => undefined;
4811
+ const prom = new Promise((resolve, reject) => {
4812
+ const firstSeek = seekToTime(element, desiredTime + threshold);
4813
+ firstSeek.wait.then((seekedTo) => {
4814
+ const difference = Math.abs(desiredTime - seekedTo);
4815
+ if (difference < threshold) {
4816
+ return resolve();
4817
+ }
4818
+ const sign = desiredTime > seekedTo ? 1 : -1;
4819
+ const newSeek = seekToTime(element, seekedTo + threshold * sign);
4820
+ currentCancel = newSeek.cancel;
4821
+ newSeek.wait
4822
+ .then((newTime) => {
4823
+ const newDifference = Math.abs(desiredTime - newTime);
4824
+ if (newDifference < threshold) {
4825
+ return resolve();
4826
+ }
4827
+ const thirdSeek = seekToTime(element, desiredTime);
4828
+ currentCancel = thirdSeek.cancel;
4829
+ thirdSeek.wait.then(() => {
4830
+ resolve();
4831
+ });
4832
+ })
4833
+ .catch((err) => {
4834
+ reject(err);
4835
+ });
4836
+ });
4837
+ currentCancel = firstSeek.cancel;
4838
+ });
4839
+ return {
4840
+ prom,
4841
+ cancel: () => {
4842
+ currentCancel();
4843
+ },
4844
+ };
4845
+ };
4846
+
4734
4847
  const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAmplificationDuringRender, playbackRate, onDuration, toneFrequency, name, acceptableTimeShiftInSeconds, ...props }, ref) => {
4735
4848
  const absoluteFrame = useTimelinePosition();
4736
4849
  const frame = useCurrentFrame();
@@ -4810,16 +4923,12 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
4810
4923
  if (!current) {
4811
4924
  return;
4812
4925
  }
4813
- const currentTime = (() => {
4814
- return getMediaTime({
4815
- fps: videoConfig.fps,
4816
- frame,
4817
- src: props.src,
4818
- playbackRate: playbackRate || 1,
4819
- startFrom: -mediaStartsAt,
4820
- mediaType: 'video',
4821
- });
4822
- })();
4926
+ const currentTime = getMediaTime({
4927
+ frame,
4928
+ playbackRate: playbackRate || 1,
4929
+ startFrom: -mediaStartsAt,
4930
+ fps: videoConfig.fps,
4931
+ });
4823
4932
  const handle = delayRender(`Rendering <Video /> with src="${props.src}"`);
4824
4933
  if (process.env.NODE_ENV === 'test') {
4825
4934
  continueRender(handle);
@@ -4838,25 +4947,13 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
4838
4947
  current.removeEventListener('loadeddata', loadedDataHandler);
4839
4948
  };
4840
4949
  }
4841
- current.currentTime = currentTime;
4842
- const seekedHandler = () => {
4843
- warnAboutNonSeekableMedia(current, 'exception');
4844
- if (window.navigator.platform.startsWith('Mac')) {
4845
- // Improve me: This is ensures frame perfectness but slows down render.
4846
- // Please see this issue for context: https://github.com/remotion-dev/remotion/issues/200
4847
- // Only affects macOS since it uses VideoToolbox decoding.
4848
- setTimeout(() => {
4849
- continueRender(handle);
4850
- }, 100);
4851
- }
4852
- else {
4853
- continueRender(handle);
4854
- }
4855
- };
4856
- current.addEventListener('seeked', seekedHandler, { once: true });
4857
4950
  const endedHandler = () => {
4858
4951
  continueRender(handle);
4859
4952
  };
4953
+ const seek = seekToTimeMultipleUntilRight(current, currentTime, videoConfig.fps);
4954
+ seek.prom.then(() => {
4955
+ continueRender(handle);
4956
+ });
4860
4957
  current.addEventListener('ended', endedHandler, { once: true });
4861
4958
  const errorHandler = () => {
4862
4959
  var _a;
@@ -4876,9 +4973,9 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
4876
4973
  current.addEventListener('error', errorHandler, { once: true });
4877
4974
  // If video skips to another frame or unmounts, we clear the created handle
4878
4975
  return () => {
4976
+ seek.cancel();
4879
4977
  current.removeEventListener('ended', endedHandler);
4880
4978
  current.removeEventListener('error', errorHandler);
4881
- current.removeEventListener('seeked', seekedHandler);
4882
4979
  continueRender(handle);
4883
4980
  };
4884
4981
  }, [
@@ -1,4 +1,4 @@
1
1
  // Automatically generated on publish
2
- const VERSION = '4.0.84';
2
+ const VERSION = '4.0.86';
3
3
 
4
4
  export { VERSION };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remotion",
3
- "version": "4.0.84",
3
+ "version": "4.0.86",
4
4
  "description": "Render videos in React",
5
5
  "main": "dist/cjs/index.js",
6
6
  "types": "dist/cjs/index.d.ts",
@@ -1,15 +0,0 @@
1
- import React from 'react';
2
- export declare class CatchError extends React.Component {
3
- state: {
4
- hasError: boolean;
5
- };
6
- constructor(props: {
7
- children: React.ReactNode;
8
- });
9
- static getDerivedStateFromError(error: Error): {
10
- hasError: boolean;
11
- };
12
- componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;
13
- render(): React.ReactNode;
14
- }
15
- export declare const StackTraceFetcher: () => JSX.Element;
@@ -1,68 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.StackTraceFetcher = exports.CatchError = void 0;
27
- const jsx_runtime_1 = require("react/jsx-runtime");
28
- const react_1 = __importStar(require("react"));
29
- const ErrorThrower = () => {
30
- const [trigger, setTrigger] = react_1.default.useState(false);
31
- (0, react_1.useEffect)(() => {
32
- setTrigger(true);
33
- }, []);
34
- if (trigger) {
35
- // throw new Error('lol');
36
- }
37
- return null;
38
- };
39
- class CatchError extends react_1.default.Component {
40
- // eslint-disable-next-line no-useless-constructor
41
- constructor(props) {
42
- super(props);
43
- this.state = { hasError: false };
44
- }
45
- static getDerivedStateFromError(error) {
46
- console.log('DERIVED STATE');
47
- console.error(error);
48
- return { hasError: true };
49
- }
50
- componentDidCatch(error, errorInfo) {
51
- console.log('DID CATCH');
52
- console.error(error, errorInfo);
53
- }
54
- render() {
55
- console.log('state', this);
56
- if (this.state.hasError) {
57
- console.log('null');
58
- return null;
59
- }
60
- // @ts-expect-error
61
- return this.props.children;
62
- }
63
- }
64
- exports.CatchError = CatchError;
65
- const StackTraceFetcher = () => {
66
- return ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(CatchError, { children: (0, jsx_runtime_1.jsx)(ErrorThrower, {}) }) }));
67
- };
68
- exports.StackTraceFetcher = StackTraceFetcher;