@remotion/player 3.2.13 → 3.2.15

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.
package/dist/Player.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { ComponentType, MutableRefObject } from 'react';
2
2
  import React from 'react';
3
3
  import type { CompProps } from 'remotion';
4
4
  import type { PlayerRef } from './player-methods';
5
- import type { RenderLoading } from './PlayerUI';
5
+ import type { RenderLoading, RenderPoster } from './PlayerUI';
6
6
  declare type PropsIfHasProps<Props> = {} extends Props ? {
7
7
  inputProps?: Props;
8
8
  } : {
@@ -31,9 +31,16 @@ export declare type PlayerProps<T> = {
31
31
  renderLoading?: RenderLoading;
32
32
  moveToBeginningWhenEnded?: boolean;
33
33
  className?: string;
34
+ initialFrame?: number;
35
+ renderPoster?: RenderPoster;
36
+ showPosterWhenPaused?: boolean;
37
+ showPosterWhenEnded?: boolean;
38
+ showPosterWhenUnplayed?: boolean;
39
+ inFrame?: number | null;
40
+ outFrame?: number | null;
34
41
  } & PropsIfHasProps<T> & CompProps<T>;
35
42
  export declare const componentOrNullIfLazy: <T>(props: CompProps<T>) => ComponentType<T> | null;
36
- export declare const PlayerFn: <T>({ durationInFrames, compositionHeight, compositionWidth, fps, inputProps, style, controls, loop, autoPlay, showVolumeControls, allowFullscreen, clickToPlay, doubleClickToFullscreen, spaceKeyToPlayOrPause, moveToBeginningWhenEnded, numberOfSharedAudioTags, errorFallback, playbackRate, renderLoading, className, ...componentProps }: PlayerProps<T>, ref: MutableRefObject<PlayerRef>) => JSX.Element;
43
+ export declare const PlayerFn: <T>({ durationInFrames, compositionHeight, compositionWidth, fps, inputProps, style, controls, loop, autoPlay, showVolumeControls, allowFullscreen, clickToPlay, doubleClickToFullscreen, spaceKeyToPlayOrPause, moveToBeginningWhenEnded, numberOfSharedAudioTags, errorFallback, playbackRate, renderLoading, className, showPosterWhenUnplayed, showPosterWhenEnded, showPosterWhenPaused, initialFrame, renderPoster, inFrame, outFrame, ...componentProps }: PlayerProps<T>, ref: MutableRefObject<PlayerRef>) => JSX.Element;
37
44
  declare module 'react' {
38
45
  function forwardRef<T, P = {}>(render: (props: P, ref: React.MutableRefObject<T>) => React.ReactElement | null): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
39
46
  }
package/dist/Player.js CHANGED
@@ -11,6 +11,8 @@ const emitter_context_1 = require("./emitter-context");
11
11
  const event_emitter_1 = require("./event-emitter");
12
12
  const player_css_classname_1 = require("./player-css-classname");
13
13
  const PlayerUI_1 = __importDefault(require("./PlayerUI"));
14
+ const validate_in_out_frame_1 = require("./utils/validate-in-out-frame");
15
+ const validate_initial_frame_1 = require("./utils/validate-initial-frame");
14
16
  const validate_playbackrate_1 = require("./utils/validate-playbackrate");
15
17
  const volume_persistance_1 = require("./volume-persistance");
16
18
  const componentOrNullIfLazy = (props) => {
@@ -20,7 +22,7 @@ const componentOrNullIfLazy = (props) => {
20
22
  return null;
21
23
  };
22
24
  exports.componentOrNullIfLazy = componentOrNullIfLazy;
23
- const PlayerFn = ({ durationInFrames, compositionHeight, compositionWidth, fps, inputProps, style, controls = false, loop = false, autoPlay = false, showVolumeControls = true, allowFullscreen = true, clickToPlay, doubleClickToFullscreen = false, spaceKeyToPlayOrPause = true, moveToBeginningWhenEnded = true, numberOfSharedAudioTags = 5, errorFallback = () => '⚠️', playbackRate = 1, renderLoading, className, ...componentProps }, ref) => {
25
+ const PlayerFn = ({ durationInFrames, compositionHeight, compositionWidth, fps, inputProps, style, controls = false, loop = false, autoPlay = false, showVolumeControls = true, allowFullscreen = true, clickToPlay, doubleClickToFullscreen = false, spaceKeyToPlayOrPause = true, moveToBeginningWhenEnded = true, numberOfSharedAudioTags = 5, errorFallback = () => '⚠️', playbackRate = 1, renderLoading, className, showPosterWhenUnplayed, showPosterWhenEnded, showPosterWhenPaused, initialFrame, renderPoster, inFrame, outFrame, ...componentProps }, ref) => {
24
26
  if (typeof window !== 'undefined') {
25
27
  // eslint-disable-next-line react-hooks/rules-of-hooks
26
28
  (0, react_1.useLayoutEffect)(() => {
@@ -40,7 +42,8 @@ const PlayerFn = ({ durationInFrames, compositionHeight, compositionWidth, fps,
40
42
  throw new TypeError(`'component' must not be the 'Composition' component. Pass your own React component directly, and set the duration, fps and dimensions as separate props. See https://www.remotion.dev/docs/player/examples for an example.`);
41
43
  }
42
44
  const component = remotion_1.Internals.useLazyComponent(componentProps);
43
- const [frame, setFrame] = (0, react_1.useState)(0);
45
+ (0, validate_initial_frame_1.validateInitialFrame)({ initialFrame, durationInFrames });
46
+ const [frame, setFrame] = (0, react_1.useState)(() => initialFrame !== null && initialFrame !== void 0 ? initialFrame : 0);
44
47
  const [playing, setPlaying] = (0, react_1.useState)(false);
45
48
  const [rootId] = (0, react_1.useState)('player-comp');
46
49
  const [emitter] = (0, react_1.useState)(() => new event_emitter_1.PlayerEmitter());
@@ -59,6 +62,11 @@ const PlayerFn = ({ durationInFrames, compositionHeight, compositionWidth, fps,
59
62
  remotion_1.Internals.validateDimension(compositionWidth, 'compositionWidth', 'of the <Player /> component');
60
63
  remotion_1.Internals.validateDurationInFrames(durationInFrames, 'of the <Player/> component');
61
64
  remotion_1.Internals.validateFps(fps, 'as a prop of the <Player/> component', false);
65
+ (0, validate_in_out_frame_1.validateInOutFrames)({
66
+ durationInFrames,
67
+ inFrame,
68
+ outFrame,
69
+ });
62
70
  if (typeof controls !== 'boolean' && typeof controls !== 'undefined') {
63
71
  throw new TypeError(`'controls' must be a boolean or undefined but got '${typeof controls}' instead`);
64
72
  }
@@ -189,7 +197,7 @@ const PlayerFn = ({ durationInFrames, compositionHeight, compositionWidth, fps,
189
197
  }
190
198
  return ((0, jsx_runtime_1.jsx)(remotion_1.Internals.CanUseRemotionHooksProvider, { children: (0, jsx_runtime_1.jsx)(remotion_1.Internals.Timeline.TimelineContext.Provider, { value: timelineContextValue, children: (0, jsx_runtime_1.jsx)(remotion_1.Internals.Timeline.SetTimelineContext.Provider, { value: setTimelineContextValue, children: (0, jsx_runtime_1.jsx)(remotion_1.Internals.CompositionManager.Provider, { value: compositionManagerContext, children: (0, jsx_runtime_1.jsx)(remotion_1.Internals.MediaVolumeContext.Provider, { value: mediaVolumeContextValue, children: (0, jsx_runtime_1.jsx)(remotion_1.Internals.SetMediaVolumeContext.Provider, { value: setMediaVolumeContextValue, children: (0, jsx_runtime_1.jsx)(remotion_1.Internals.SharedAudioContextProvider, { numberOfAudioTags: numberOfSharedAudioTags, children: (0, jsx_runtime_1.jsx)(emitter_context_1.PlayerEventEmitterContext.Provider, { value: emitter, children: (0, jsx_runtime_1.jsx)(PlayerUI_1.default, { ref: rootRef, renderLoading: renderLoading, autoPlay: Boolean(autoPlay), loop: Boolean(loop), controls: Boolean(controls), errorFallback: errorFallback, style: style, inputProps: passedInputProps, allowFullscreen: Boolean(allowFullscreen), moveToBeginningWhenEnded: Boolean(moveToBeginningWhenEnded), clickToPlay: typeof clickToPlay === 'boolean'
191
199
  ? clickToPlay
192
- : Boolean(controls), showVolumeControls: Boolean(showVolumeControls), setMediaVolume: setMediaVolumeAndPersist, mediaVolume: mediaVolume, mediaMuted: mediaMuted, doubleClickToFullscreen: Boolean(doubleClickToFullscreen), setMediaMuted: setMediaMuted, spaceKeyToPlayOrPause: Boolean(spaceKeyToPlayOrPause), playbackRate: playbackRate, className: className !== null && className !== void 0 ? className : undefined }) }) }) }) }) }) }) }) }));
200
+ : Boolean(controls), showVolumeControls: Boolean(showVolumeControls), setMediaVolume: setMediaVolumeAndPersist, mediaVolume: mediaVolume, mediaMuted: mediaMuted, doubleClickToFullscreen: Boolean(doubleClickToFullscreen), setMediaMuted: setMediaMuted, spaceKeyToPlayOrPause: Boolean(spaceKeyToPlayOrPause), playbackRate: playbackRate, className: className !== null && className !== void 0 ? className : undefined, showPosterWhenUnplayed: Boolean(showPosterWhenUnplayed), showPosterWhenEnded: Boolean(showPosterWhenEnded), showPosterWhenPaused: Boolean(showPosterWhenPaused), renderPoster: renderPoster, inFrame: inFrame !== null && inFrame !== void 0 ? inFrame : null, outFrame: outFrame !== null && outFrame !== void 0 ? outFrame : null }) }) }) }) }) }) }) }) }));
193
201
  };
194
202
  exports.PlayerFn = PlayerFn;
195
203
  exports.Player = (0, react_1.forwardRef)(exports.PlayerFn);
@@ -22,4 +22,8 @@ export declare const Controls: React.FC<{
22
22
  allowFullscreen: boolean;
23
23
  onExitFullscreenButtonClick: MouseEventHandler<HTMLButtonElement>;
24
24
  spaceKeyToPlayOrPause: boolean;
25
+ onSeekEnd: () => void;
26
+ onSeekStart: () => void;
27
+ inFrame: number | null;
28
+ outFrame: number | null;
25
29
  }>;
@@ -62,7 +62,7 @@ const timeLabel = {
62
62
  fontFamily: 'sans-serif',
63
63
  fontSize: 14,
64
64
  };
65
- const Controls = ({ durationInFrames, hovered, isFullscreen, fps, player, showVolumeControls, onFullscreenButtonClick, allowFullscreen, onExitFullscreenButtonClick, spaceKeyToPlayOrPause, }) => {
65
+ const Controls = ({ durationInFrames, hovered, isFullscreen, fps, player, showVolumeControls, onFullscreenButtonClick, allowFullscreen, onExitFullscreenButtonClick, spaceKeyToPlayOrPause, onSeekEnd, onSeekStart, inFrame, outFrame, }) => {
66
66
  const playButtonRef = (0, react_1.useRef)(null);
67
67
  const frame = remotion_1.Internals.Timeline.useTimelinePosition();
68
68
  const [supportsFullscreen, setSupportsFullscreen] = (0, react_1.useState)(false);
@@ -90,6 +90,6 @@ const Controls = ({ durationInFrames, hovered, isFullscreen, fps, player, showVo
90
90
  }, []);
91
91
  return ((0, jsx_runtime_1.jsxs)("div", { style: containerCss, children: [(0, jsx_runtime_1.jsxs)("div", { style: controlsRow, children: [(0, jsx_runtime_1.jsxs)("div", { style: leftPartStyle, children: [(0, jsx_runtime_1.jsx)("button", { ref: playButtonRef, type: "button", style: buttonStyle, onClick: player.playing ? player.pause : player.play, "aria-label": player.playing ? 'Pause video' : 'Play video', title: player.playing ? 'Pause video' : 'Play video', children: player.playing ? (0, jsx_runtime_1.jsx)(icons_1.PauseIcon, {}) : (0, jsx_runtime_1.jsx)(icons_1.PlayIcon, {}) }), showVolumeControls ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { style: xSpacer }), (0, jsx_runtime_1.jsx)(MediaVolumeSlider_1.MediaVolumeSlider, {})] })) : null, (0, jsx_runtime_1.jsx)("div", { style: xSpacer }), (0, jsx_runtime_1.jsxs)("div", { style: timeLabel, children: [(0, format_time_1.formatTime)(frame / fps), " / ", (0, format_time_1.formatTime)(durationInFrames / fps)] }), (0, jsx_runtime_1.jsx)("div", { style: xSpacer })] }), (0, jsx_runtime_1.jsx)("div", { style: flex1 }), (0, jsx_runtime_1.jsx)("div", { style: fullscreen, children: supportsFullscreen && allowFullscreen ? ((0, jsx_runtime_1.jsx)("button", { type: "button", "aria-label": isFullscreen ? 'Exit fullscreen' : 'Enter Fullscreen', title: isFullscreen ? 'Exit fullscreen' : 'Enter Fullscreen', style: buttonStyle, onClick: isFullscreen
92
92
  ? onExitFullscreenButtonClick
93
- : onFullscreenButtonClick, children: (0, jsx_runtime_1.jsx)(icons_1.FullscreenIcon, { minimized: !isFullscreen }) })) : null })] }), (0, jsx_runtime_1.jsx)("div", { style: ySpacer }), (0, jsx_runtime_1.jsx)(PlayerSeekBar_1.PlayerSeekBar, { durationInFrames: durationInFrames })] }));
93
+ : onFullscreenButtonClick, children: (0, jsx_runtime_1.jsx)(icons_1.FullscreenIcon, { minimized: !isFullscreen }) })) : null })] }), (0, jsx_runtime_1.jsx)("div", { style: ySpacer }), (0, jsx_runtime_1.jsx)(PlayerSeekBar_1.PlayerSeekBar, { onSeekEnd: onSeekEnd, onSeekStart: onSeekStart, durationInFrames: durationInFrames, inFrame: inFrame, outFrame: outFrame })] }));
94
94
  };
95
95
  exports.Controls = Controls;
@@ -1,4 +1,8 @@
1
1
  /// <reference types="react" />
2
2
  export declare const PlayerSeekBar: React.FC<{
3
3
  durationInFrames: number;
4
+ onSeekStart: () => void;
5
+ onSeekEnd: () => void;
6
+ inFrame: number | null;
7
+ outFrame: number | null;
4
8
  }>;
@@ -30,7 +30,7 @@ const containerStyle = {
30
30
  };
31
31
  const barBackground = {
32
32
  height: BAR_HEIGHT,
33
- backgroundColor: 'rgba(255, 255, 255, 0.5)',
33
+ backgroundColor: 'rgba(255, 255, 255, 0.25)',
34
34
  width: '100%',
35
35
  borderRadius: BAR_HEIGHT / 2,
36
36
  };
@@ -41,7 +41,7 @@ const findBodyInWhichDivIsLocated = (div) => {
41
41
  }
42
42
  return current;
43
43
  };
44
- const PlayerSeekBar = ({ durationInFrames }) => {
44
+ const PlayerSeekBar = ({ durationInFrames, onSeekEnd, onSeekStart, inFrame, outFrame }) => {
45
45
  const containerRef = (0, react_1.useRef)(null);
46
46
  const barHovered = (0, use_hover_state_1.useHoverState)(containerRef);
47
47
  const size = (0, use_element_size_1.useElementSize)(containerRef, {
@@ -64,7 +64,8 @@ const PlayerSeekBar = ({ durationInFrames }) => {
64
64
  dragging: true,
65
65
  wasPlaying: playing,
66
66
  });
67
- }, [size, durationInFrames, pause, seek, playing]);
67
+ onSeekStart();
68
+ }, [size, durationInFrames, pause, seek, playing, onSeekStart]);
68
69
  const onPointerMove = (0, react_1.useCallback)((e) => {
69
70
  var _a;
70
71
  if (!size) {
@@ -89,7 +90,8 @@ const PlayerSeekBar = ({ durationInFrames }) => {
89
90
  else {
90
91
  pause();
91
92
  }
92
- }, [dragging, pause, play]);
93
+ onSeekEnd();
94
+ }, [dragging, onSeekEnd, pause, play]);
93
95
  (0, react_1.useEffect)(() => {
94
96
  if (!dragging.dragging) {
95
97
  return;
@@ -121,10 +123,24 @@ const PlayerSeekBar = ({ durationInFrames }) => {
121
123
  return {
122
124
  height: BAR_HEIGHT,
123
125
  backgroundColor: 'rgba(255, 255, 255, 1)',
124
- width: (frame / (durationInFrames - 1)) * 100 + '%',
126
+ width: ((frame - (inFrame !== null && inFrame !== void 0 ? inFrame : 0)) / (durationInFrames - 1)) * 100 + '%',
127
+ marginLeft: ((inFrame !== null && inFrame !== void 0 ? inFrame : 0) / (durationInFrames - 1)) * 100 + '%',
125
128
  borderRadius: BAR_HEIGHT / 2,
126
129
  };
127
- }, [durationInFrames, frame]);
128
- return ((0, jsx_runtime_1.jsxs)("div", { ref: containerRef, onPointerDown: onPointerDown, style: containerStyle, children: [(0, jsx_runtime_1.jsx)("div", { style: barBackground, children: (0, jsx_runtime_1.jsx)("div", { style: fillStyle }) }), (0, jsx_runtime_1.jsx)("div", { style: knobStyle })] }));
130
+ }, [durationInFrames, frame, inFrame]);
131
+ const active = (0, react_1.useMemo)(() => {
132
+ return {
133
+ height: BAR_HEIGHT,
134
+ backgroundColor: 'rgba(255, 255, 255, 0.25)',
135
+ width: (((outFrame !== null && outFrame !== void 0 ? outFrame : durationInFrames - 1) - (inFrame !== null && inFrame !== void 0 ? inFrame : 0)) /
136
+ (durationInFrames - 1)) *
137
+ 100 +
138
+ '%',
139
+ marginLeft: ((inFrame !== null && inFrame !== void 0 ? inFrame : 0) / (durationInFrames - 1)) * 100 + '%',
140
+ borderRadius: BAR_HEIGHT / 2,
141
+ position: 'absolute',
142
+ };
143
+ }, [durationInFrames, inFrame, outFrame]);
144
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: containerRef, onPointerDown: onPointerDown, style: containerStyle, children: [(0, jsx_runtime_1.jsxs)("div", { style: barBackground, children: [(0, jsx_runtime_1.jsx)("div", { style: active }), (0, jsx_runtime_1.jsx)("div", { style: fillStyle })] }), (0, jsx_runtime_1.jsx)("div", { style: knobStyle })] }));
129
145
  };
130
146
  exports.PlayerSeekBar = PlayerSeekBar;
@@ -7,6 +7,7 @@ export declare type RenderLoading = (canvas: {
7
7
  height: number;
8
8
  width: number;
9
9
  }) => React.ReactChild;
10
+ export declare type RenderPoster = RenderLoading;
10
11
  declare const _default: (props: {
11
12
  controls: boolean;
12
13
  loop: boolean;
@@ -24,8 +25,14 @@ declare const _default: (props: {
24
25
  mediaVolume: number;
25
26
  errorFallback: ErrorFallback;
26
27
  playbackRate: number;
27
- renderLoading?: RenderLoading | undefined;
28
+ renderLoading: RenderLoading | undefined;
29
+ renderPoster: RenderLoading | undefined;
28
30
  className: string | undefined;
29
31
  moveToBeginningWhenEnded: boolean;
32
+ showPosterWhenPaused: boolean;
33
+ showPosterWhenEnded: boolean;
34
+ showPosterWhenUnplayed: boolean;
35
+ inFrame: number | null;
36
+ outFrame: number | null;
30
37
  } & React.RefAttributes<PlayerRef | null>) => React.ReactElement<any, string | React.JSXElementConstructor<any>> | null;
31
38
  export default _default;
package/dist/PlayerUI.js CHANGED
@@ -42,7 +42,7 @@ if (reactVersion === '0') {
42
42
  throw new Error(`Version ${reactVersion} of "react" is not supported by Remotion`);
43
43
  }
44
44
  const doesReactVersionSupportSuspense = parseInt(reactVersion, 10) >= 18;
45
- const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps, clickToPlay, showVolumeControls, mediaVolume, mediaMuted, doubleClickToFullscreen, setMediaMuted, setMediaVolume, spaceKeyToPlayOrPause, errorFallback, playbackRate, renderLoading, className, moveToBeginningWhenEnded, }, ref) => {
45
+ const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps, clickToPlay, showVolumeControls, mediaVolume, mediaMuted, doubleClickToFullscreen, setMediaMuted, setMediaVolume, spaceKeyToPlayOrPause, errorFallback, playbackRate, renderLoading, renderPoster, className, moveToBeginningWhenEnded, showPosterWhenUnplayed, showPosterWhenEnded, showPosterWhenPaused, inFrame, outFrame, }, ref) => {
46
46
  var _a, _b, _c;
47
47
  const config = remotion_1.Internals.useUnsafeVideoConfig();
48
48
  const video = remotion_1.Internals.useVideo();
@@ -55,12 +55,13 @@ const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps
55
55
  const [hasPausedToResume, setHasPausedToResume] = (0, react_1.useState)(false);
56
56
  const [shouldAutoplay, setShouldAutoPlay] = (0, react_1.useState)(autoPlay);
57
57
  const [isFullscreen, setIsFullscreen] = (0, react_1.useState)(() => false);
58
+ const [seeking, setSeeking] = (0, react_1.useState)(false);
58
59
  (0, use_playback_1.usePlayback)({
59
60
  loop,
60
61
  playbackRate,
61
62
  moveToBeginningWhenEnded,
62
- inFrame: null,
63
- outFrame: null,
63
+ inFrame,
64
+ outFrame,
64
65
  });
65
66
  const player = (0, use_player_1.usePlayer)();
66
67
  (0, react_1.useEffect)(() => {
@@ -232,11 +233,17 @@ const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps
232
233
  if (!config || !canvasSize) {
233
234
  return null;
234
235
  }
235
- return (0, calculate_scale_1.calculateScale)({
236
+ return (0, calculate_scale_1.calculateCanvasTransformation)({
236
237
  canvasSize,
237
238
  compositionHeight: config.height,
238
239
  compositionWidth: config.width,
239
- previewSize: 'auto',
240
+ previewSize: {
241
+ size: 'auto',
242
+ translation: {
243
+ x: 0,
244
+ y: 0,
245
+ },
246
+ },
240
247
  });
241
248
  }, [canvasSize, config]);
242
249
  const outer = (0, react_1.useMemo)(() => {
@@ -259,11 +266,17 @@ const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps
259
266
  if (!config || !canvasSize) {
260
267
  return {};
261
268
  }
262
- const { scale, xCorrection, yCorrection } = (0, calculate_scale_1.calculateScale)({
269
+ const { scale, xCorrection, yCorrection } = (0, calculate_scale_1.calculateCanvasTransformation)({
263
270
  canvasSize,
264
271
  compositionHeight: config.height,
265
272
  compositionWidth: config.width,
266
- previewSize: 'auto',
273
+ previewSize: {
274
+ size: 'auto',
275
+ translation: {
276
+ x: 0,
277
+ y: 0,
278
+ },
279
+ },
267
280
  });
268
281
  return {
269
282
  position: 'absolute',
@@ -292,6 +305,12 @@ const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps
292
305
  const onSingleClick = (0, react_1.useCallback)((e) => {
293
306
  toggle(e);
294
307
  }, [toggle]);
308
+ const onSeekStart = (0, react_1.useCallback)(() => {
309
+ setSeeking(true);
310
+ }, []);
311
+ const onSeekEnd = (0, react_1.useCallback)(() => {
312
+ setSeeking(false);
313
+ }, []);
295
314
  const onDoubleClick = (0, react_1.useCallback)(() => {
296
315
  if (isFullscreen) {
297
316
  exitFullscreen();
@@ -310,7 +329,22 @@ const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps
310
329
  if (!config) {
311
330
  return null;
312
331
  }
313
- const content = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { style: outer, onClick: clickToPlay ? handleClick : undefined, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, children: (0, jsx_runtime_1.jsx)("div", { style: containerStyle, className: player_css_classname_1.PLAYER_CSS_CLASSNAME, children: VideoComponent ? ((0, jsx_runtime_1.jsx)(error_boundary_1.ErrorBoundary, { onError: onError, errorFallback: errorFallback, children: (0, jsx_runtime_1.jsx)(VideoComponent, { ...((_b = video === null || video === void 0 ? void 0 : video.defaultProps) !== null && _b !== void 0 ? _b : {}), ...((_c = inputProps) !== null && _c !== void 0 ? _c : {}) }) })) : null }) }), controls ? ((0, jsx_runtime_1.jsx)(PlayerControls_1.Controls, { fps: config.fps, durationInFrames: config.durationInFrames, hovered: hovered, player: player, onFullscreenButtonClick: onFullscreenButtonClick, isFullscreen: isFullscreen, allowFullscreen: allowFullscreen, showVolumeControls: showVolumeControls, onExitFullscreenButtonClick: onExitFullscreenButtonClick, spaceKeyToPlayOrPause: spaceKeyToPlayOrPause })) : null] }));
332
+ const poster = renderPoster
333
+ ? renderPoster({
334
+ height: outerStyle.height,
335
+ width: outerStyle.width,
336
+ })
337
+ : null;
338
+ if (poster === undefined) {
339
+ throw new TypeError('renderPoster() must return a React element, but undefined was returned');
340
+ }
341
+ const shouldShowPoster = poster &&
342
+ [
343
+ showPosterWhenPaused && !player.isPlaying() && !seeking,
344
+ showPosterWhenEnded && player.isLastFrame && !player.isPlaying(),
345
+ showPosterWhenUnplayed && !player.hasPlayed && !player.isPlaying(),
346
+ ].some(Boolean);
347
+ const content = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { style: outer, onClick: clickToPlay ? handleClick : undefined, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, children: (0, jsx_runtime_1.jsx)("div", { style: containerStyle, className: player_css_classname_1.PLAYER_CSS_CLASSNAME, children: VideoComponent ? ((0, jsx_runtime_1.jsx)(error_boundary_1.ErrorBoundary, { onError: onError, errorFallback: errorFallback, children: (0, jsx_runtime_1.jsx)(VideoComponent, { ...((_b = video === null || video === void 0 ? void 0 : video.defaultProps) !== null && _b !== void 0 ? _b : {}), ...((_c = inputProps) !== null && _c !== void 0 ? _c : {}) }) })) : null }) }), shouldShowPoster ? ((0, jsx_runtime_1.jsx)("div", { style: outer, onClick: clickToPlay ? handleClick : undefined, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, children: poster })) : null, controls ? ((0, jsx_runtime_1.jsx)(PlayerControls_1.Controls, { fps: config.fps, durationInFrames: config.durationInFrames, hovered: hovered, player: player, onFullscreenButtonClick: onFullscreenButtonClick, isFullscreen: isFullscreen, allowFullscreen: allowFullscreen, showVolumeControls: showVolumeControls, onExitFullscreenButtonClick: onExitFullscreenButtonClick, spaceKeyToPlayOrPause: spaceKeyToPlayOrPause, onSeekEnd: onSeekEnd, onSeekStart: onSeekStart, inFrame: inFrame, outFrame: outFrame })) : null] }));
314
348
  if (is_node_1.IS_NODE && !doesReactVersionSupportSuspense) {
315
349
  return ((0, jsx_runtime_1.jsx)("div", { ref: container, style: outerStyle, className: className, children: content }));
316
350
  }
@@ -1,6 +1,12 @@
1
1
  import type { PreviewSize } from './utils/preview-size';
2
2
  import type { Size } from './utils/use-element-size';
3
- export declare const calculateScale: ({ previewSize, compositionWidth, compositionHeight, canvasSize, }: {
3
+ export declare const calculateScale: ({ canvasSize, compositionHeight, compositionWidth, previewSize, }: {
4
+ previewSize: PreviewSize['size'];
5
+ compositionWidth: number;
6
+ compositionHeight: number;
7
+ canvasSize: Size;
8
+ }) => number;
9
+ export declare const calculateCanvasTransformation: ({ previewSize, compositionWidth, compositionHeight, canvasSize, }: {
4
10
  previewSize: PreviewSize;
5
11
  compositionWidth: number;
6
12
  compositionHeight: number;
@@ -1,11 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.calculateScale = void 0;
4
- const calculateScale = ({ previewSize, compositionWidth, compositionHeight, canvasSize, }) => {
3
+ exports.calculateCanvasTransformation = exports.calculateScale = void 0;
4
+ const calculateScale = ({ canvasSize, compositionHeight, compositionWidth, previewSize, }) => {
5
5
  const heightRatio = canvasSize.height / compositionHeight;
6
6
  const widthRatio = canvasSize.width / compositionWidth;
7
7
  const ratio = Math.min(heightRatio, widthRatio);
8
- const scale = previewSize === 'auto' ? ratio : Number(previewSize);
8
+ return previewSize === 'auto' ? ratio : Number(previewSize);
9
+ };
10
+ exports.calculateScale = calculateScale;
11
+ const calculateCanvasTransformation = ({ previewSize, compositionWidth, compositionHeight, canvasSize, }) => {
12
+ const scale = (0, exports.calculateScale)({
13
+ canvasSize,
14
+ compositionHeight,
15
+ compositionWidth,
16
+ previewSize: previewSize.size,
17
+ });
9
18
  const correction = 0 - (1 - scale) / 2;
10
19
  const xCorrection = correction * compositionWidth;
11
20
  const yCorrection = correction * compositionHeight;
@@ -21,4 +30,4 @@ const calculateScale = ({ previewSize, compositionWidth, compositionHeight, canv
21
30
  scale,
22
31
  };
23
32
  };
24
- exports.calculateScale = calculateScale;
33
+ exports.calculateCanvasTransformation = calculateCanvasTransformation;
package/dist/index.d.ts CHANGED
@@ -1,10 +1,9 @@
1
- /// <reference types="react" />
2
1
  import type { CallbackListener, EventTypes } from './event-emitter';
3
2
  import { PlayerEmitter } from './event-emitter';
4
3
  export { ErrorFallback, Player, PlayerProps } from './Player';
5
4
  export { PlayerMethods, PlayerRef } from './player-methods';
6
- export type { RenderLoading } from './PlayerUI';
7
- export { PreviewSize } from './utils/preview-size';
5
+ export type { RenderLoading, RenderPoster } from './PlayerUI';
6
+ export { PreviewSize, Translation } from './utils/preview-size';
8
7
  export { Size } from './utils/use-element-size';
9
8
  export type { CallbackListener, EventTypes };
10
9
  export declare const PlayerInternals: {
@@ -23,6 +22,7 @@ export declare const PlayerInternals: {
23
22
  seek: (newFrame: number) => void;
24
23
  getCurrentFrame: () => number;
25
24
  isPlaying: () => boolean;
25
+ hasPlayed: boolean;
26
26
  };
27
27
  usePlayback: ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, outFrame, }: {
28
28
  loop: boolean;
@@ -35,7 +35,7 @@ export declare const PlayerInternals: {
35
35
  triggerOnWindowResize: boolean;
36
36
  shouldApplyCssTransforms: boolean;
37
37
  }) => import("./utils/use-element-size").Size | null;
38
- calculateScale: ({ previewSize, compositionWidth, compositionHeight, canvasSize, }: {
38
+ calculateCanvasTransformation: ({ previewSize, compositionWidth, compositionHeight, canvasSize, }: {
39
39
  previewSize: import("./utils/preview-size").PreviewSize;
40
40
  compositionWidth: number;
41
41
  compositionHeight: number;
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ exports.PlayerInternals = {
16
16
  usePlayer: use_player_1.usePlayer,
17
17
  usePlayback: use_playback_1.usePlayback,
18
18
  useElementSize: use_element_size_1.useElementSize,
19
- calculateScale: calculate_scale_1.calculateScale,
19
+ calculateCanvasTransformation: calculate_scale_1.calculateCanvasTransformation,
20
20
  useHoverState: use_hover_state_1.useHoverState,
21
21
  updateAllElementsSizes: use_element_size_1.updateAllElementsSizes,
22
22
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const validate_in_out_frame_1 = require("../utils/validate-in-out-frame");
4
+ test('Validate in out frames', () => {
5
+ expect(() => (0, validate_in_out_frame_1.validateInOutFrames)({
6
+ durationInFrames: 200,
7
+ inFrame: 201,
8
+ outFrame: undefined,
9
+ })).toThrow(/inFrame must be less than \(durationInFrames - 1\)/);
10
+ expect(() => (0, validate_in_out_frame_1.validateInOutFrames)({
11
+ durationInFrames: 200,
12
+ inFrame: 199,
13
+ outFrame: 201,
14
+ })).toThrow(/outFrame must be less than \(durationInFrames - 1\)/);
15
+ expect(() => (0, validate_in_out_frame_1.validateInOutFrames)({
16
+ durationInFrames: 200,
17
+ inFrame: -10,
18
+ outFrame: null,
19
+ })).toThrow(/inFrame must be greater than 0, but is -10/);
20
+ expect(() => (0, validate_in_out_frame_1.validateInOutFrames)({
21
+ durationInFrames: 200,
22
+ inFrame: null,
23
+ outFrame: -10,
24
+ })).toThrow(/outFrame must be greater than 0, but is -10/);
25
+ expect(() => (0, validate_in_out_frame_1.validateInOutFrames)({
26
+ durationInFrames: 200,
27
+ inFrame: 1.5,
28
+ outFrame: null,
29
+ })).toThrow(/"inFrame" must be an integer, but is 1.5/);
30
+ expect(() => (0, validate_in_out_frame_1.validateInOutFrames)({
31
+ durationInFrames: 200,
32
+ inFrame: 20,
33
+ outFrame: 20,
34
+ })).toThrow(/outFrame must be greater than inFrame, but is 20/);
35
+ expect(() => (0, validate_in_out_frame_1.validateInOutFrames)({
36
+ durationInFrames: 200,
37
+ inFrame: 21,
38
+ outFrame: 20,
39
+ })).toThrow(/outFrame must be greater than inFrame, but is 20 <= 21/);
40
+ expect(() => (0, validate_in_out_frame_1.validateInOutFrames)({
41
+ durationInFrames: 200,
42
+ inFrame: null,
43
+ outFrame: 20,
44
+ })).not.toThrow();
45
+ expect(() => (0, validate_in_out_frame_1.validateInOutFrames)({
46
+ durationInFrames: 200,
47
+ inFrame: null,
48
+ outFrame: null,
49
+ })).not.toThrow();
50
+ expect(() => (0, validate_in_out_frame_1.validateInOutFrames)({
51
+ durationInFrames: 200,
52
+ inFrame: 10,
53
+ outFrame: 20,
54
+ })).not.toThrow();
55
+ });
@@ -51,7 +51,7 @@ test('No durationInFrames should give errors', () => {
51
51
  durationInFrames: undefined, component: test_utils_1.HelloWorld, controls: true, showVolumeControls: true }));
52
52
  }
53
53
  catch (e) {
54
- expect(e.message).toMatch(/The "durationInFrames" prop of the <Player\/> component must be a number, but you passed a value of type undefined/);
54
+ expect(e.message).toMatch(/durationInFrames` must be a number, but is undefined/);
55
55
  }
56
56
  });
57
57
  test('Invalid playbackRate should give error', () => {
@@ -13,6 +13,7 @@ declare type UsePlayerMethods = {
13
13
  seek: (newFrame: number) => void;
14
14
  getCurrentFrame: () => number;
15
15
  isPlaying: () => boolean;
16
+ hasPlayed: boolean;
16
17
  };
17
18
  export declare const usePlayer: () => UsePlayerMethods;
18
19
  export {};
@@ -7,8 +7,9 @@ const emitter_context_1 = require("./emitter-context");
7
7
  const usePlayer = () => {
8
8
  var _a;
9
9
  const [playing, setPlaying, imperativePlaying] = remotion_1.Internals.Timeline.usePlayingState();
10
+ const [hasPlayed, setHasPlayed] = (0, react_1.useState)(false);
10
11
  const frame = remotion_1.Internals.Timeline.useTimelinePosition();
11
- const playStart = (0, react_1.useRef)(0);
12
+ const playStart = (0, react_1.useRef)(frame);
12
13
  const setFrame = remotion_1.Internals.Timeline.useTimelineSetFrame();
13
14
  const setTimelinePosition = remotion_1.Internals.Timeline.useTimelineSetFrame();
14
15
  const audioContext = (0, react_1.useContext)(remotion_1.Internals.SharedAudioContext);
@@ -32,6 +33,7 @@ const usePlayer = () => {
32
33
  if (imperativePlaying.current) {
33
34
  return;
34
35
  }
36
+ setHasPlayed(true);
35
37
  if (isLastFrame) {
36
38
  seek(0);
37
39
  }
@@ -109,6 +111,7 @@ const usePlayer = () => {
109
111
  getCurrentFrame: () => frameRef.current,
110
112
  isPlaying: () => imperativePlaying.current,
111
113
  pauseAndReturnToPlayStart,
114
+ hasPlayed,
112
115
  };
113
116
  }, [
114
117
  frameBack,
@@ -122,6 +125,7 @@ const usePlayer = () => {
122
125
  isFirstFrame,
123
126
  pauseAndReturnToPlayStart,
124
127
  imperativePlaying,
128
+ hasPlayed,
125
129
  ]);
126
130
  return returnValue;
127
131
  };
@@ -1 +1,8 @@
1
- export declare type PreviewSize = 'auto' | number;
1
+ export declare type Translation = {
2
+ x: number;
3
+ y: number;
4
+ };
5
+ export declare type PreviewSize = {
6
+ size: number | 'auto';
7
+ translation: Translation;
8
+ };
@@ -0,0 +1,6 @@
1
+ export declare const validateSingleFrameFrame: (frame: unknown, variableName: string) => number | null;
2
+ export declare const validateInOutFrames: ({ inFrame, durationInFrames, outFrame, }: {
3
+ inFrame: unknown;
4
+ outFrame: unknown;
5
+ durationInFrames: number;
6
+ }) => void;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateInOutFrames = exports.validateSingleFrameFrame = void 0;
4
+ const validateSingleFrameFrame = (frame, variableName) => {
5
+ if (typeof frame === 'undefined' || frame === null) {
6
+ return frame !== null && frame !== void 0 ? frame : null;
7
+ }
8
+ if (typeof frame !== 'number') {
9
+ throw new TypeError(`"${variableName}" must be a number, but is ${JSON.stringify(frame)}`);
10
+ }
11
+ if (Number.isNaN(frame)) {
12
+ throw new TypeError(`"${variableName}" must not be NaN, but is ${JSON.stringify(frame)}`);
13
+ }
14
+ if (!Number.isFinite(frame)) {
15
+ throw new TypeError(`"${variableName}" must be finite, but is ${JSON.stringify(frame)}`);
16
+ }
17
+ if (frame % 1 !== 0) {
18
+ throw new TypeError(`"${variableName}" must be an integer, but is ${JSON.stringify(frame)}`);
19
+ }
20
+ return frame;
21
+ };
22
+ exports.validateSingleFrameFrame = validateSingleFrameFrame;
23
+ const validateInOutFrames = ({ inFrame, durationInFrames, outFrame, }) => {
24
+ const validatedInFrame = (0, exports.validateSingleFrameFrame)(inFrame, 'inFrame');
25
+ const validateOutFrame = (0, exports.validateSingleFrameFrame)(outFrame, 'outFrame');
26
+ if (validatedInFrame === null && validateOutFrame === null) {
27
+ return;
28
+ }
29
+ // Must not be over the duration
30
+ if (validatedInFrame !== null && validatedInFrame > durationInFrames - 1) {
31
+ throw new Error('inFrame must be less than (durationInFrames - 1), but is ' +
32
+ validatedInFrame);
33
+ }
34
+ if (validateOutFrame !== null && validateOutFrame > durationInFrames - 1) {
35
+ throw new Error('outFrame must be less than (durationInFrames - 1), but is ' +
36
+ validatedInFrame);
37
+ }
38
+ // Must not be under 0
39
+ if (validatedInFrame !== null && validatedInFrame < 0) {
40
+ throw new Error('inFrame must be greater than 0, but is ' + validatedInFrame);
41
+ }
42
+ if (validateOutFrame !== null && validateOutFrame < 0) {
43
+ throw new Error('outFrame must be greater than 0, but is ' + validateOutFrame);
44
+ }
45
+ if (validateOutFrame !== null &&
46
+ validatedInFrame !== null &&
47
+ validateOutFrame <= validatedInFrame) {
48
+ throw new Error('outFrame must be greater than inFrame, but is ' +
49
+ validateOutFrame +
50
+ ' <= ' +
51
+ validatedInFrame);
52
+ }
53
+ };
54
+ exports.validateInOutFrames = validateInOutFrames;
@@ -0,0 +1,4 @@
1
+ export declare const validateInitialFrame: ({ initialFrame, durationInFrames, }: {
2
+ initialFrame: unknown;
3
+ durationInFrames: unknown;
4
+ }) => void;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateInitialFrame = void 0;
4
+ const validateInitialFrame = ({ initialFrame, durationInFrames, }) => {
5
+ if (typeof durationInFrames !== 'number') {
6
+ throw new Error(`\`durationInFrames\` must be a number, but is ${JSON.stringify(durationInFrames)}`);
7
+ }
8
+ if (typeof initialFrame === 'undefined') {
9
+ return;
10
+ }
11
+ if (typeof initialFrame !== 'number') {
12
+ throw new Error(`\`initialFrame\` must be a number, but is ${JSON.stringify(initialFrame)}`);
13
+ }
14
+ if (Number.isNaN(initialFrame)) {
15
+ throw new Error(`\`initialFrame\` must be a number, but is NaN`);
16
+ }
17
+ if (!Number.isFinite(initialFrame)) {
18
+ throw new Error(`\`initialFrame\` must be a number, but is Infinity`);
19
+ }
20
+ if (initialFrame % 1 !== 0) {
21
+ throw new Error(`\`initialFrame\` must be an integer, but is ${JSON.stringify(initialFrame)}`);
22
+ }
23
+ if (initialFrame > durationInFrames - 1) {
24
+ throw new Error(`\`initialFrame\` must be less or equal than \`durationInFrames - 1\`, but is ${JSON.stringify(initialFrame)}`);
25
+ }
26
+ };
27
+ exports.validateInitialFrame = validateInitialFrame;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/player",
3
- "version": "3.2.13",
3
+ "version": "3.2.15",
4
4
  "description": "Remotion Player",
5
5
  "main": "dist/index.js",
6
6
  "sideEffects": false,
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "license": "SEE LICENSE IN LICENSE.md",
30
30
  "dependencies": {
31
- "remotion": "3.2.13"
31
+ "remotion": "3.2.15"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "react": ">=16.8.0",
@@ -63,5 +63,5 @@
63
63
  "publishConfig": {
64
64
  "access": "public"
65
65
  },
66
- "gitHead": "989d20fc0ace36685c71396847e1b7b7a06d00d7"
66
+ "gitHead": "7346704683815d274733cb3d280157b9ff570773"
67
67
  }