Files
story-studio/remotion/node_modules/@remotion/player/dist/esm/index.mjs
2026-02-21 10:33:18 +01:00

3649 lines
110 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
// src/icons.tsx
import { jsx, jsxs } from "react/jsx-runtime";
var ICON_SIZE = 25;
var fullscreenIconSize = 16;
var PlayIcon = () => {
return /* @__PURE__ */ jsx("svg", {
width: ICON_SIZE,
height: ICON_SIZE,
viewBox: "0 0 25 25",
fill: "none",
children: /* @__PURE__ */ jsx("path", {
d: "M8 6.375C7.40904 8.17576 7.06921 10.2486 7.01438 12.3871C6.95955 14.5255 7.19163 16.6547 7.6875 18.5625C9.95364 18.2995 12.116 17.6164 14.009 16.5655C15.902 15.5147 17.4755 14.124 18.6088 12.5C17.5158 10.8949 15.9949 9.51103 14.1585 8.45082C12.3222 7.3906 10.2174 6.68116 8 6.375Z",
fill: "white",
stroke: "white",
strokeWidth: "6.25",
strokeLinejoin: "round"
})
});
};
var PauseIcon = () => {
return /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 100 100",
width: ICON_SIZE,
height: ICON_SIZE,
children: [
/* @__PURE__ */ jsx("rect", {
x: "25",
y: "20",
width: "20",
height: "60",
fill: "#fff",
ry: "5",
rx: "5"
}),
/* @__PURE__ */ jsx("rect", {
x: "55",
y: "20",
width: "20",
height: "60",
fill: "#fff",
ry: "5",
rx: "5"
})
]
});
};
var FullscreenIcon = ({
isFullscreen
}) => {
const strokeWidth = 6;
const viewSize = 32;
const out = isFullscreen ? 0 : strokeWidth / 2;
const middleInset = isFullscreen ? strokeWidth * 1.6 : strokeWidth / 2;
const inset = isFullscreen ? strokeWidth * 1.6 : strokeWidth * 2;
return /* @__PURE__ */ jsxs("svg", {
viewBox: `0 0 ${viewSize} ${viewSize}`,
height: fullscreenIconSize,
width: fullscreenIconSize,
children: [
/* @__PURE__ */ jsx("path", {
d: `
M ${out} ${inset}
L ${middleInset} ${middleInset}
L ${inset} ${out}
`,
stroke: "#fff",
strokeWidth,
fill: "none"
}),
/* @__PURE__ */ jsx("path", {
d: `
M ${viewSize - out} ${inset}
L ${viewSize - middleInset} ${middleInset}
L ${viewSize - inset} ${out}
`,
stroke: "#fff",
strokeWidth,
fill: "none"
}),
/* @__PURE__ */ jsx("path", {
d: `
M ${out} ${viewSize - inset}
L ${middleInset} ${viewSize - middleInset}
L ${inset} ${viewSize - out}
`,
stroke: "#fff",
strokeWidth,
fill: "none"
}),
/* @__PURE__ */ jsx("path", {
d: `
M ${viewSize - out} ${viewSize - inset}
L ${viewSize - middleInset} ${viewSize - middleInset}
L ${viewSize - inset} ${viewSize - out}
`,
stroke: "#fff",
strokeWidth,
fill: "none"
})
]
});
};
var VolumeOffIcon = () => {
return /* @__PURE__ */ jsx("svg", {
width: ICON_SIZE,
height: ICON_SIZE,
viewBox: "0 0 24 24",
children: /* @__PURE__ */ jsx("path", {
d: "M3.63 3.63a.996.996 0 000 1.41L7.29 8.7 7 9H4c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1h3l3.29 3.29c.63.63 1.71.18 1.71-.71v-4.17l4.18 4.18c-.49.37-1.02.68-1.6.91-.36.15-.58.53-.58.92 0 .72.73 1.18 1.39.91.8-.33 1.55-.77 2.22-1.31l1.34 1.34a.996.996 0 101.41-1.41L5.05 3.63c-.39-.39-1.02-.39-1.42 0zM19 12c0 .82-.15 1.61-.41 2.34l1.53 1.53c.56-1.17.88-2.48.88-3.87 0-3.83-2.4-7.11-5.78-8.4-.59-.23-1.22.23-1.22.86v.19c0 .38.25.71.61.85C17.18 6.54 19 9.06 19 12zm-8.71-6.29l-.17.17L12 7.76V6.41c0-.89-1.08-1.33-1.71-.7zM16.5 12A4.5 4.5 0 0014 7.97v1.79l2.48 2.48c.01-.08.02-.16.02-.24z",
fill: "#fff"
})
});
};
var VolumeOnIcon = () => {
return /* @__PURE__ */ jsx("svg", {
width: ICON_SIZE,
height: ICON_SIZE,
viewBox: "0 0 24 24",
children: /* @__PURE__ */ jsx("path", {
d: "M3 10v4c0 .55.45 1 1 1h3l3.29 3.29c.63.63 1.71.18 1.71-.71V6.41c0-.89-1.08-1.34-1.71-.71L7 9H4c-.55 0-1 .45-1 1zm13.5 2A4.5 4.5 0 0014 7.97v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 4.45v.2c0 .38.25.71.6.85C17.18 6.53 19 9.06 19 12s-1.82 5.47-4.4 6.5c-.36.14-.6.47-.6.85v.2c0 .63.63 1.07 1.21.85C18.6 19.11 21 15.84 21 12s-2.4-7.11-5.79-8.4c-.58-.23-1.21.22-1.21.85z",
fill: "#fff"
})
});
};
// src/BufferingIndicator.tsx
import { jsx as jsx2, jsxs as jsxs2, Fragment } from "react/jsx-runtime";
var className = "__remotion_buffering_indicator";
var remotionBufferingAnimation = "__remotion_buffering_animation";
var playerStyle = {
width: ICON_SIZE,
height: ICON_SIZE,
overflow: "hidden",
lineHeight: "normal",
fontSize: "inherit"
};
var studioStyle = {
width: 14,
height: 14,
overflow: "hidden",
lineHeight: "normal",
fontSize: "inherit"
};
var BufferingIndicator = ({ type }) => {
const style = type === "player" ? playerStyle : studioStyle;
return /* @__PURE__ */ jsxs2(Fragment, {
children: [
/* @__PURE__ */ jsx2("style", {
type: "text/css",
children: `
@keyframes ${remotionBufferingAnimation} {
0% {
rotate: 0deg;
}
100% {
rotate: 360deg;
}
}
.${className} {
animation: ${remotionBufferingAnimation} 1s linear infinite;
}
`
}),
/* @__PURE__ */ jsx2("div", {
style,
children: /* @__PURE__ */ jsx2("svg", {
viewBox: type === "player" ? "0 0 22 22" : "0 0 18 18",
style,
className,
children: /* @__PURE__ */ jsx2("path", {
d: type === "player" ? "M 11 4 A 7 7 0 0 1 15.1145 16.66312" : "M 9 2 A 7 7 0 0 1 13.1145 14.66312",
stroke: "white",
strokeLinecap: "round",
fill: "none",
strokeWidth: 3
})
})
})
]
});
};
// src/calculate-scale.ts
import { Internals } from "remotion";
// src/utils/calculate-player-size.ts
var calculatePlayerSize = ({
currentSize,
width,
height,
compositionWidth,
compositionHeight
}) => {
if (width !== undefined && height === undefined) {
return {
aspectRatio: [compositionWidth, compositionHeight].join("/")
};
}
if (height !== undefined && width === undefined) {
return {
aspectRatio: [compositionWidth, compositionHeight].join("/")
};
}
if (!currentSize) {
return {
width: compositionWidth,
height: compositionHeight
};
}
return {
width: compositionWidth,
height: compositionHeight
};
};
// src/calculate-scale.ts
var calculateCanvasTransformation = ({
previewSize,
compositionWidth,
compositionHeight,
canvasSize
}) => {
const scale = Internals.calculateScale({
canvasSize,
compositionHeight,
compositionWidth,
previewSize
});
const correction = 0 - (1 - scale) / 2;
const xCorrection = correction * compositionWidth;
const yCorrection = correction * compositionHeight;
const width = compositionWidth * scale;
const height = compositionHeight * scale;
const centerX = canvasSize.width / 2 - width / 2;
const centerY = canvasSize.height / 2 - height / 2;
return {
centerX,
centerY,
xCorrection,
yCorrection,
scale
};
};
var calculateOuterStyle = ({
config,
style,
canvasSize,
overflowVisible,
layout
}) => {
if (!config) {
return {};
}
return {
position: "relative",
overflow: overflowVisible ? "visible" : "hidden",
...calculatePlayerSize({
compositionHeight: config.height,
compositionWidth: config.width,
currentSize: canvasSize,
height: style?.height,
width: style?.width
}),
opacity: layout ? 1 : 0,
...style
};
};
var calculateContainerStyle = ({
config,
layout,
scale,
overflowVisible
}) => {
if (!config) {
return {};
}
if (!layout) {
return {
position: "absolute",
width: config.width,
height: config.height,
display: "flex",
transform: `scale(${scale})`,
overflow: overflowVisible ? "visible" : "hidden"
};
}
return {
position: "absolute",
width: config.width,
height: config.height,
display: "flex",
transform: `scale(${scale})`,
marginLeft: layout.xCorrection,
marginTop: layout.yCorrection,
overflow: overflowVisible ? "visible" : "hidden"
};
};
var calculateOuter = ({
layout,
scale,
config,
overflowVisible
}) => {
if (!config) {
return {};
}
if (!layout) {
return {
width: config.width * scale,
height: config.height * scale,
display: "flex",
flexDirection: "column",
position: "absolute",
overflow: overflowVisible ? "visible" : "hidden"
};
}
const { centerX, centerY } = layout;
return {
width: config.width * scale,
height: config.height * scale,
display: "flex",
flexDirection: "column",
position: "absolute",
left: centerX,
top: centerY,
overflow: overflowVisible ? "visible" : "hidden"
};
};
// src/emitter-context.ts
import React from "react";
var PlayerEventEmitterContext = React.createContext(undefined);
var ThumbnailEmitterContext = React.createContext(undefined);
// src/EmitterProvider.tsx
import { useContext as useContext2, useEffect, useState } from "react";
import { Internals as Internals3 } from "remotion";
// src/event-emitter.ts
class PlayerEmitter {
listeners = {
ended: [],
error: [],
pause: [],
play: [],
ratechange: [],
scalechange: [],
seeked: [],
timeupdate: [],
frameupdate: [],
fullscreenchange: [],
volumechange: [],
mutechange: [],
waiting: [],
resume: []
};
addEventListener(name, callback) {
this.listeners[name].push(callback);
}
removeEventListener(name, callback) {
this.listeners[name] = this.listeners[name].filter((l) => l !== callback);
}
dispatchEvent(dispatchName, context) {
this.listeners[dispatchName].forEach((callback) => {
callback({ detail: context });
});
}
dispatchSeek = (frame) => {
this.dispatchEvent("seeked", {
frame
});
};
dispatchVolumeChange = (volume) => {
this.dispatchEvent("volumechange", {
volume
});
};
dispatchPause = () => {
this.dispatchEvent("pause", undefined);
};
dispatchPlay = () => {
this.dispatchEvent("play", undefined);
};
dispatchEnded = () => {
this.dispatchEvent("ended", undefined);
};
dispatchRateChange = (playbackRate) => {
this.dispatchEvent("ratechange", {
playbackRate
});
};
dispatchScaleChange = (scale) => {
this.dispatchEvent("scalechange", {
scale
});
};
dispatchError = (error) => {
this.dispatchEvent("error", {
error
});
};
dispatchTimeUpdate = (event) => {
this.dispatchEvent("timeupdate", event);
};
dispatchFrameUpdate = (event) => {
this.dispatchEvent("frameupdate", event);
};
dispatchFullscreenChange = (event) => {
this.dispatchEvent("fullscreenchange", event);
};
dispatchMuteChange = (event) => {
this.dispatchEvent("mutechange", event);
};
dispatchWaiting = (event) => {
this.dispatchEvent("waiting", event);
};
dispatchResume = (event) => {
this.dispatchEvent("resume", event);
};
}
class ThumbnailEmitter {
listeners = {
error: [],
waiting: [],
resume: []
};
addEventListener(name, callback) {
this.listeners[name].push(callback);
}
removeEventListener(name, callback) {
this.listeners[name] = this.listeners[name].filter((l) => l !== callback);
}
dispatchEvent(dispatchName, context) {
this.listeners[dispatchName].forEach((callback) => {
callback({ detail: context });
});
}
dispatchError = (error) => {
this.dispatchEvent("error", {
error
});
};
dispatchWaiting = (event) => {
this.dispatchEvent("waiting", event);
};
dispatchResume = (event) => {
this.dispatchEvent("resume", event);
};
}
// src/use-buffer-state-emitter.ts
import { useContext, useLayoutEffect } from "react";
import { Internals as Internals2 } from "remotion";
var useBufferStateEmitter = (emitter) => {
const bufferManager = useContext(Internals2.BufferingContextReact);
if (!bufferManager) {
throw new Error("BufferingContextReact not found");
}
useLayoutEffect(() => {
const clear1 = bufferManager.listenForBuffering(() => {
bufferManager.buffering.current = true;
emitter.dispatchWaiting({});
});
const clear2 = bufferManager.listenForResume(() => {
bufferManager.buffering.current = false;
emitter.dispatchResume({});
});
return () => {
clear1.remove();
clear2.remove();
};
}, [bufferManager, emitter]);
};
// src/EmitterProvider.tsx
import { jsx as jsx3 } from "react/jsx-runtime";
var PlayerEmitterProvider = ({ children, currentPlaybackRate }) => {
const [emitter] = useState(() => new PlayerEmitter);
const bufferManager = useContext2(Internals3.BufferingContextReact);
if (!bufferManager) {
throw new Error("BufferingContextReact not found");
}
useEffect(() => {
if (currentPlaybackRate) {
emitter.dispatchRateChange(currentPlaybackRate);
}
}, [emitter, currentPlaybackRate]);
useBufferStateEmitter(emitter);
return /* @__PURE__ */ jsx3(PlayerEventEmitterContext.Provider, {
value: emitter,
children
});
};
// src/use-frame-imperative.ts
import { useCallback, useRef } from "react";
import { Internals as Internals4 } from "remotion";
var useFrameImperative = () => {
const frame = Internals4.Timeline.useTimelinePosition();
const frameRef = useRef(frame);
frameRef.current = frame;
const getCurrentFrame = useCallback(() => {
return frameRef.current;
}, []);
return getCurrentFrame;
};
// src/use-hover-state.ts
import { useEffect as useEffect2, useState as useState2 } from "react";
var useHoverState = (ref, hideControlsWhenPointerDoesntMove) => {
const [hovered, setHovered] = useState2(false);
useEffect2(() => {
const { current } = ref;
if (!current) {
return;
}
let hoverTimeout;
const addHoverTimeout = () => {
if (hideControlsWhenPointerDoesntMove) {
clearTimeout(hoverTimeout);
hoverTimeout = setTimeout(() => {
setHovered(false);
}, hideControlsWhenPointerDoesntMove === true ? 3000 : hideControlsWhenPointerDoesntMove);
}
};
const onHover = () => {
setHovered(true);
addHoverTimeout();
};
const onLeave = () => {
setHovered(false);
clearTimeout(hoverTimeout);
};
const onMove = () => {
setHovered(true);
addHoverTimeout();
};
current.addEventListener("mouseenter", onHover);
current.addEventListener("mouseleave", onLeave);
current.addEventListener("mousemove", onMove);
return () => {
current.removeEventListener("mouseenter", onHover);
current.removeEventListener("mouseleave", onLeave);
current.removeEventListener("mousemove", onMove);
clearTimeout(hoverTimeout);
};
}, [hideControlsWhenPointerDoesntMove, ref]);
return hovered;
};
// src/use-playback.ts
import { useContext as useContext4, useEffect as useEffect5, useRef as useRef4 } from "react";
import { Internals as Internals6 } from "remotion";
// src/browser-mediasession.ts
import { useEffect as useEffect3 } from "react";
// src/use-player.ts
import { useCallback as useCallback2, useContext as useContext3, useMemo, useRef as useRef2, useState as useState3 } from "react";
import { Internals as Internals5 } from "remotion";
var usePlayer = () => {
const [playing, setPlaying, imperativePlaying] = Internals5.Timeline.usePlayingState();
const [hasPlayed, setHasPlayed] = useState3(false);
const frame = Internals5.Timeline.useTimelinePosition();
const playStart = useRef2(frame);
const setFrame = Internals5.Timeline.useTimelineSetFrame();
const setTimelinePosition = Internals5.Timeline.useTimelineSetFrame();
const audioContext = useContext3(Internals5.SharedAudioContext);
const { audioAndVideoTags } = useContext3(Internals5.TimelineContext);
const frameRef = useRef2(frame);
frameRef.current = frame;
const video = Internals5.useVideo();
const config = Internals5.useUnsafeVideoConfig();
const emitter = useContext3(PlayerEventEmitterContext);
const lastFrame = (config?.durationInFrames ?? 1) - 1;
const isLastFrame = frame === lastFrame;
const isFirstFrame = frame === 0;
if (!emitter) {
throw new TypeError("Expected Player event emitter context");
}
const bufferingContext = useContext3(Internals5.BufferingContextReact);
if (!bufferingContext) {
throw new Error("Missing the buffering context. Most likely you have a Remotion version mismatch.");
}
const { buffering } = bufferingContext;
const seek = useCallback2((newFrame) => {
if (video?.id) {
setTimelinePosition((c) => ({ ...c, [video.id]: newFrame }));
}
frameRef.current = newFrame;
emitter.dispatchSeek(newFrame);
}, [emitter, setTimelinePosition, video?.id]);
const play = useCallback2((e) => {
if (imperativePlaying.current) {
return;
}
setHasPlayed(true);
if (isLastFrame) {
seek(0);
}
audioContext?.audioContext?.resume();
if (audioContext && audioContext.numberOfAudioTags > 0 && e) {
audioContext.playAllAudios();
}
audioAndVideoTags.current.forEach((a) => a.play("player play() was called and playing audio from a click"));
imperativePlaying.current = true;
setPlaying(true);
playStart.current = frameRef.current;
emitter.dispatchPlay();
}, [
imperativePlaying,
isLastFrame,
audioContext,
setPlaying,
emitter,
seek,
audioAndVideoTags
]);
const pause = useCallback2(() => {
if (imperativePlaying.current) {
imperativePlaying.current = false;
setPlaying(false);
emitter.dispatchPause();
audioContext?.audioContext?.suspend();
}
}, [emitter, imperativePlaying, setPlaying, audioContext]);
const pauseAndReturnToPlayStart = useCallback2(() => {
if (imperativePlaying.current) {
imperativePlaying.current = false;
frameRef.current = playStart.current;
if (config) {
setTimelinePosition((c) => ({
...c,
[config.id]: playStart.current
}));
setPlaying(false);
emitter.dispatchPause();
}
}
}, [config, emitter, imperativePlaying, setPlaying, setTimelinePosition]);
const videoId = video?.id;
const frameBack = useCallback2((frames) => {
if (!videoId) {
return null;
}
if (imperativePlaying.current) {
return;
}
setFrame((c) => {
const prevFrame = c[videoId] ?? window.remotion_initialFrame ?? 0;
const newFrame = Math.max(0, prevFrame - frames);
if (prevFrame === newFrame) {
return c;
}
return {
...c,
[videoId]: newFrame
};
});
}, [imperativePlaying, setFrame, videoId]);
const frameForward = useCallback2((frames) => {
if (!videoId) {
return null;
}
if (imperativePlaying.current) {
return;
}
setFrame((c) => {
const prevFrame = c[videoId] ?? window.remotion_initialFrame ?? 0;
const newFrame = Math.min(lastFrame, prevFrame + frames);
if (prevFrame === newFrame) {
return c;
}
return {
...c,
[videoId]: newFrame
};
});
}, [videoId, imperativePlaying, lastFrame, setFrame]);
const toggle = useCallback2((e) => {
if (imperativePlaying.current) {
pause();
} else {
play(e);
}
}, [imperativePlaying, pause, play]);
const isPlaying = useCallback2(() => {
return imperativePlaying.current;
}, [imperativePlaying]);
const getCurrentFrame = useCallback2(() => {
return frameRef.current;
}, [frameRef]);
const isBuffering = useCallback2(() => {
return buffering.current;
}, [buffering]);
const returnValue = useMemo(() => {
return {
frameBack,
frameForward,
isLastFrame,
emitter,
playing,
play,
pause,
seek,
isFirstFrame,
getCurrentFrame,
isPlaying,
isBuffering,
pauseAndReturnToPlayStart,
hasPlayed,
toggle
};
}, [
emitter,
frameBack,
frameForward,
hasPlayed,
isFirstFrame,
isLastFrame,
getCurrentFrame,
pause,
pauseAndReturnToPlayStart,
play,
playing,
seek,
toggle,
isPlaying,
isBuffering
]);
return returnValue;
};
// src/browser-mediasession.ts
var useBrowserMediaSession = ({
browserMediaControlsBehavior,
videoConfig,
playbackRate
}) => {
const { playing, pause, play, emitter, getCurrentFrame, seek } = usePlayer();
useEffect3(() => {
if (!navigator.mediaSession) {
return;
}
if (browserMediaControlsBehavior.mode === "do-nothing") {
return;
}
if (playing) {
navigator.mediaSession.playbackState = "playing";
} else {
navigator.mediaSession.playbackState = "paused";
}
}, [browserMediaControlsBehavior.mode, playing]);
useEffect3(() => {
if (!navigator.mediaSession) {
return;
}
if (browserMediaControlsBehavior.mode === "do-nothing") {
return;
}
const onTimeUpdate = () => {
if (!videoConfig) {
return;
}
if (navigator.mediaSession) {
navigator.mediaSession.setPositionState({
duration: videoConfig.durationInFrames / videoConfig.fps,
playbackRate,
position: getCurrentFrame() / videoConfig.fps
});
}
};
emitter.addEventListener("timeupdate", onTimeUpdate);
return () => {
emitter.removeEventListener("timeupdate", onTimeUpdate);
};
}, [
browserMediaControlsBehavior.mode,
emitter,
getCurrentFrame,
playbackRate,
videoConfig
]);
useEffect3(() => {
if (!navigator.mediaSession) {
return;
}
if (browserMediaControlsBehavior.mode === "do-nothing") {
return;
}
navigator.mediaSession.setActionHandler("play", () => {
if (browserMediaControlsBehavior.mode === "register-media-session") {
play();
}
});
navigator.mediaSession.setActionHandler("pause", () => {
if (browserMediaControlsBehavior.mode === "register-media-session") {
pause();
}
});
navigator.mediaSession.setActionHandler("seekto", (event) => {
if (browserMediaControlsBehavior.mode === "register-media-session" && event.seekTime !== undefined && videoConfig) {
seek(Math.round(event.seekTime * videoConfig.fps));
}
});
navigator.mediaSession.setActionHandler("seekbackward", () => {
if (browserMediaControlsBehavior.mode === "register-media-session" && videoConfig) {
seek(Math.max(0, Math.round((getCurrentFrame() - 10) * videoConfig.fps)));
}
});
navigator.mediaSession.setActionHandler("seekforward", () => {
if (browserMediaControlsBehavior.mode === "register-media-session" && videoConfig) {
seek(Math.max(videoConfig.durationInFrames - 1, Math.round((getCurrentFrame() + 10) * videoConfig.fps)));
}
});
navigator.mediaSession.setActionHandler("previoustrack", () => {
if (browserMediaControlsBehavior.mode === "register-media-session") {
seek(0);
}
});
return () => {
navigator.mediaSession.metadata = null;
navigator.mediaSession.setActionHandler("play", null);
navigator.mediaSession.setActionHandler("pause", null);
navigator.mediaSession.setActionHandler("seekto", null);
navigator.mediaSession.setActionHandler("seekbackward", null);
navigator.mediaSession.setActionHandler("seekforward", null);
navigator.mediaSession.setActionHandler("previoustrack", null);
};
}, [
browserMediaControlsBehavior.mode,
getCurrentFrame,
pause,
play,
seek,
videoConfig
]);
};
// src/calculate-next-frame.ts
var calculateNextFrame = ({
time,
currentFrame: startFrame,
playbackSpeed,
fps,
actualLastFrame,
actualFirstFrame,
framesAdvanced,
shouldLoop
}) => {
const op = playbackSpeed < 0 ? Math.ceil : Math.floor;
const framesToAdvance = op(time * playbackSpeed / (1000 / fps)) - framesAdvanced;
const nextFrame = framesToAdvance + startFrame;
const isCurrentFrameOutside = startFrame > actualLastFrame || startFrame < actualFirstFrame;
const isNextFrameOutside = nextFrame > actualLastFrame || nextFrame < actualFirstFrame;
const hasEnded = !shouldLoop && isNextFrameOutside && !isCurrentFrameOutside;
if (playbackSpeed > 0) {
if (isNextFrameOutside) {
return {
nextFrame: actualFirstFrame,
framesToAdvance,
hasEnded
};
}
return { nextFrame, framesToAdvance, hasEnded };
}
if (isNextFrameOutside) {
return { nextFrame: actualLastFrame, framesToAdvance, hasEnded };
}
return { nextFrame, framesToAdvance, hasEnded };
};
// src/is-backgrounded.ts
import { useEffect as useEffect4, useRef as useRef3 } from "react";
var getIsBackgrounded = () => {
if (typeof document === "undefined") {
return false;
}
return document.visibilityState === "hidden";
};
var useIsBackgrounded = () => {
const isBackgrounded = useRef3(getIsBackgrounded());
useEffect4(() => {
const onVisibilityChange = () => {
isBackgrounded.current = getIsBackgrounded();
};
document.addEventListener("visibilitychange", onVisibilityChange);
return () => {
document.removeEventListener("visibilitychange", onVisibilityChange);
};
}, []);
return isBackgrounded;
};
// src/use-playback.ts
var usePlayback = ({
loop,
playbackRate,
moveToBeginningWhenEnded,
inFrame,
outFrame,
browserMediaControlsBehavior,
getCurrentFrame
}) => {
const config = Internals6.useUnsafeVideoConfig();
const frame = Internals6.Timeline.useTimelinePosition();
const { playing, pause, emitter, isPlaying } = usePlayer();
const setFrame = Internals6.Timeline.useTimelineSetFrame();
const isBackgroundedRef = useIsBackgrounded();
const lastTimeUpdateEvent = useRef4(null);
const context = useContext4(Internals6.BufferingContextReact);
if (!context) {
throw new Error("Missing the buffering context. Most likely you have a Remotion version mismatch.");
}
useBrowserMediaSession({
browserMediaControlsBehavior,
playbackRate,
videoConfig: config
});
useEffect5(() => {
if (!config) {
return;
}
if (!playing) {
return;
}
let hasBeenStopped = false;
let reqAnimFrameCall = null;
let startedTime = performance.now();
let framesAdvanced = 0;
const cancelQueuedFrame = () => {
if (reqAnimFrameCall !== null) {
if (reqAnimFrameCall.type === "raf") {
cancelAnimationFrame(reqAnimFrameCall.id);
} else {
clearTimeout(reqAnimFrameCall.id);
}
}
};
const stop = () => {
hasBeenStopped = true;
cancelQueuedFrame();
};
const callback = () => {
if (hasBeenStopped) {
return;
}
if (!isPlaying()) {
return;
}
const time = performance.now() - startedTime;
const actualLastFrame = outFrame ?? config.durationInFrames - 1;
const actualFirstFrame = inFrame ?? 0;
const currentFrame = getCurrentFrame();
const { nextFrame, framesToAdvance, hasEnded } = calculateNextFrame({
time,
currentFrame,
playbackSpeed: playbackRate,
fps: config.fps,
actualFirstFrame,
actualLastFrame,
framesAdvanced,
shouldLoop: loop
});
framesAdvanced += framesToAdvance;
if (nextFrame !== getCurrentFrame() && (!hasEnded || moveToBeginningWhenEnded)) {
setFrame((c) => ({ ...c, [config.id]: nextFrame }));
}
if (hasEnded) {
stop();
pause();
emitter.dispatchEnded();
return;
}
queueNextFrame();
};
const queueNextFrame = () => {
if (context.buffering.current) {
const stopListening = context.listenForResume(() => {
stopListening.remove();
startedTime = performance.now();
framesAdvanced = 0;
queueNextFrame();
});
return;
}
if (isBackgroundedRef.current) {
reqAnimFrameCall = {
type: "timeout",
id: setTimeout(callback, 1000 / config.fps)
};
return;
}
reqAnimFrameCall = { type: "raf", id: requestAnimationFrame(callback) };
};
queueNextFrame();
const onVisibilityChange = () => {
if (document.visibilityState === "visible") {
return;
}
cancelQueuedFrame();
callback();
};
window.addEventListener("visibilitychange", onVisibilityChange);
return () => {
window.removeEventListener("visibilitychange", onVisibilityChange);
stop();
};
}, [
config,
loop,
pause,
playing,
setFrame,
emitter,
playbackRate,
inFrame,
outFrame,
moveToBeginningWhenEnded,
isBackgroundedRef,
getCurrentFrame,
context,
isPlaying
]);
useEffect5(() => {
const interval = setInterval(() => {
if (lastTimeUpdateEvent.current === getCurrentFrame()) {
return;
}
emitter.dispatchTimeUpdate({ frame: getCurrentFrame() });
lastTimeUpdateEvent.current = getCurrentFrame();
}, 250);
return () => clearInterval(interval);
}, [emitter, getCurrentFrame]);
useEffect5(() => {
emitter.dispatchFrameUpdate({ frame });
}, [emitter, frame]);
};
// src/utils/use-element-size.ts
import { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo2, useState as useState4 } from "react";
var elementSizeHooks = [];
var updateAllElementsSizes = () => {
for (const listener of elementSizeHooks) {
listener();
}
};
var useElementSize = (ref, options) => {
const [size, setSize] = useState4(() => {
if (!ref.current) {
return null;
}
const rect = ref.current.getClientRects();
if (!rect[0]) {
return null;
}
return {
width: rect[0].width,
height: rect[0].height,
left: rect[0].x,
top: rect[0].y,
windowSize: {
height: window.innerHeight,
width: window.innerWidth
}
};
});
const observer = useMemo2(() => {
if (typeof ResizeObserver === "undefined") {
return null;
}
return new ResizeObserver((entries) => {
const { contentRect, target } = entries[0];
const newSize = target.getClientRects();
if (!newSize?.[0]) {
setSize(null);
return;
}
const probableCssParentScale = contentRect.width === 0 ? 1 : newSize[0].width / contentRect.width;
const width = options.shouldApplyCssTransforms || probableCssParentScale === 0 ? newSize[0].width : newSize[0].width * (1 / probableCssParentScale);
const height = options.shouldApplyCssTransforms || probableCssParentScale === 0 ? newSize[0].height : newSize[0].height * (1 / probableCssParentScale);
setSize((prevState) => {
const isSame = prevState && prevState.width === width && prevState.height === height && prevState.left === newSize[0].x && prevState.top === newSize[0].y && prevState.windowSize.height === window.innerHeight && prevState.windowSize.width === window.innerWidth;
if (isSame) {
return prevState;
}
return {
width,
height,
left: newSize[0].x,
top: newSize[0].y,
windowSize: {
height: window.innerHeight,
width: window.innerWidth
}
};
});
});
}, [options.shouldApplyCssTransforms]);
const updateSize = useCallback3(() => {
if (!ref.current) {
return;
}
const rect = ref.current.getClientRects();
if (!rect[0]) {
setSize(null);
return;
}
setSize((prevState) => {
const isSame = prevState && prevState.width === rect[0].width && prevState.height === rect[0].height && prevState.left === rect[0].x && prevState.top === rect[0].y && prevState.windowSize.height === window.innerHeight && prevState.windowSize.width === window.innerWidth;
if (isSame) {
return prevState;
}
return {
width: rect[0].width,
height: rect[0].height,
left: rect[0].x,
top: rect[0].y,
windowSize: {
height: window.innerHeight,
width: window.innerWidth
}
};
});
}, [ref]);
useEffect6(() => {
if (!observer) {
return;
}
const { current } = ref;
if (current) {
observer.observe(current);
}
return () => {
if (current) {
observer.unobserve(current);
}
};
}, [observer, ref, updateSize]);
useEffect6(() => {
if (!options.triggerOnWindowResize) {
return;
}
window.addEventListener("resize", updateSize);
return () => {
window.removeEventListener("resize", updateSize);
};
}, [options.triggerOnWindowResize, updateSize]);
useEffect6(() => {
elementSizeHooks.push(updateSize);
return () => {
elementSizeHooks = elementSizeHooks.filter((e) => e !== updateSize);
};
}, [updateSize]);
return useMemo2(() => {
if (!size) {
return null;
}
return { ...size, refresh: updateSize };
}, [size, updateSize]);
};
// src/Player.tsx
import {
forwardRef as forwardRef2,
useEffect as useEffect13,
useImperativeHandle as useImperativeHandle2,
useLayoutEffect as useLayoutEffect2,
useMemo as useMemo14,
useRef as useRef11,
useState as useState13
} from "react";
import { Composition, Internals as Internals15 } from "remotion";
// src/PlayerUI.tsx
import React10, {
Suspense,
forwardRef,
useCallback as useCallback11,
useContext as useContext6,
useEffect as useEffect12,
useImperativeHandle,
useMemo as useMemo12,
useRef as useRef10,
useState as useState11
} from "react";
import { Internals as Internals11 } from "remotion";
// src/PlayerControls.tsx
import { useCallback as useCallback8, useEffect as useEffect10, useMemo as useMemo9, useRef as useRef8, useState as useState10 } from "react";
// src/DefaultPlayPauseButton.tsx
import { jsx as jsx4 } from "react/jsx-runtime";
var DefaultPlayPauseButton = ({ playing, buffering }) => {
if (playing && buffering) {
return /* @__PURE__ */ jsx4(BufferingIndicator, {
type: "player"
});
}
if (playing) {
return /* @__PURE__ */ jsx4(PauseIcon, {});
}
return /* @__PURE__ */ jsx4(PlayIcon, {});
};
// src/MediaVolumeSlider.tsx
import { useCallback as useCallback5, useMemo as useMemo4, useRef as useRef5, useState as useState6 } from "react";
import { Internals as Internals7 } from "remotion";
// src/render-volume-slider.tsx
import React3, { useCallback as useCallback4, useMemo as useMemo3, useState as useState5 } from "react";
import { random } from "remotion";
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
var KNOB_SIZE = 12;
var BAR_HEIGHT = 5;
var DefaultVolumeSlider = ({
volume,
isVertical,
onBlur,
inputRef,
setVolume
}) => {
const sliderContainer = useMemo3(() => {
const paddingLeft = 5;
const common = {
paddingLeft,
height: ICON_SIZE,
width: VOLUME_SLIDER_WIDTH,
display: "inline-flex",
alignItems: "center"
};
if (isVertical) {
return {
...common,
position: "absolute",
transform: `rotate(-90deg) translateX(${VOLUME_SLIDER_WIDTH / 2 + ICON_SIZE / 2}px)`
};
}
return {
...common
};
}, [isVertical]);
const randomId = typeof React3.useId === "undefined" ? "volume-slider" : React3.useId();
const [randomClass] = useState5(() => `__remotion-volume-slider-${random(randomId)}`.replace(".", ""));
const onVolumeChange = useCallback4((e) => {
setVolume(parseFloat(e.target.value));
}, [setVolume]);
const inputStyle = useMemo3(() => {
const commonStyle = {
WebkitAppearance: "none",
backgroundColor: "rgba(255, 255, 255, 0.5)",
borderRadius: BAR_HEIGHT / 2,
cursor: "pointer",
height: BAR_HEIGHT,
width: VOLUME_SLIDER_WIDTH,
backgroundImage: `linear-gradient(
to right,
white ${volume * 100}%, rgba(255, 255, 255, 0) ${volume * 100}%
)`
};
if (isVertical) {
return {
...commonStyle,
bottom: ICON_SIZE + VOLUME_SLIDER_WIDTH / 2
};
}
return commonStyle;
}, [isVertical, volume]);
const sliderStyle = `
.${randomClass}::-webkit-slider-thumb {
-webkit-appearance: none;
background-color: white;
border-radius: ${KNOB_SIZE / 2}px;
box-shadow: 0 0 2px black;
height: ${KNOB_SIZE}px;
width: ${KNOB_SIZE}px;
}
.${randomClass}::-moz-range-thumb {
-webkit-appearance: none;
background-color: white;
border-radius: ${KNOB_SIZE / 2}px;
box-shadow: 0 0 2px black;
height: ${KNOB_SIZE}px;
width: ${KNOB_SIZE}px;
}
`;
return /* @__PURE__ */ jsxs3("div", {
style: sliderContainer,
children: [
/* @__PURE__ */ jsx5("style", {
dangerouslySetInnerHTML: {
__html: sliderStyle
}
}),
/* @__PURE__ */ jsx5("input", {
ref: inputRef,
"aria-label": "Change volume",
className: randomClass,
max: 1,
min: 0,
onBlur,
onChange: onVolumeChange,
step: 0.01,
type: "range",
value: volume,
style: inputStyle
})
]
});
};
var renderDefaultVolumeSlider = (props) => {
return /* @__PURE__ */ jsx5(DefaultVolumeSlider, {
...props
});
};
// src/MediaVolumeSlider.tsx
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
var VOLUME_SLIDER_WIDTH = 100;
var MediaVolumeSlider = ({ displayVerticalVolumeSlider, renderMuteButton, renderVolumeSlider }) => {
const [mediaMuted, setMediaMuted] = Internals7.useMediaMutedState();
const [mediaVolume, setMediaVolume] = Internals7.useMediaVolumeState();
const [focused, setFocused] = useState6(false);
const parentDivRef = useRef5(null);
const inputRef = useRef5(null);
const hover = useHoverState(parentDivRef, false);
const onBlur = useCallback5(() => {
setTimeout(() => {
if (inputRef.current && document.activeElement !== inputRef.current) {
setFocused(false);
}
}, 10);
}, []);
const isVolume0 = mediaVolume === 0;
const onClick = useCallback5(() => {
if (isVolume0) {
setMediaVolume(1);
setMediaMuted(false);
return;
}
setMediaMuted((mute) => !mute);
}, [isVolume0, setMediaMuted, setMediaVolume]);
const parentDivStyle = useMemo4(() => {
return {
display: "inline-flex",
background: "none",
border: "none",
justifyContent: "center",
alignItems: "center",
touchAction: "none",
...displayVerticalVolumeSlider && { position: "relative" }
};
}, [displayVerticalVolumeSlider]);
const volumeContainer = useMemo4(() => {
return {
display: "inline",
width: ICON_SIZE,
height: ICON_SIZE,
cursor: "pointer",
appearance: "none",
background: "none",
border: "none",
padding: 0
};
}, []);
const renderDefaultMuteButton = useCallback5(({ muted, volume }) => {
const isMutedOrZero = muted || volume === 0;
return /* @__PURE__ */ jsx6("button", {
"aria-label": isMutedOrZero ? "Unmute sound" : "Mute sound",
title: isMutedOrZero ? "Unmute sound" : "Mute sound",
onClick,
onBlur,
onFocus: () => setFocused(true),
style: volumeContainer,
type: "button",
children: isMutedOrZero ? /* @__PURE__ */ jsx6(VolumeOffIcon, {}) : /* @__PURE__ */ jsx6(VolumeOnIcon, {})
});
}, [onBlur, onClick, volumeContainer]);
const muteButton = useMemo4(() => {
return renderMuteButton ? renderMuteButton({ muted: mediaMuted, volume: mediaVolume }) : renderDefaultMuteButton({ muted: mediaMuted, volume: mediaVolume });
}, [mediaMuted, mediaVolume, renderDefaultMuteButton, renderMuteButton]);
const volumeSlider = useMemo4(() => {
return (focused || hover) && !mediaMuted && !Internals7.isIosSafari() ? (renderVolumeSlider ?? renderDefaultVolumeSlider)({
isVertical: displayVerticalVolumeSlider,
volume: mediaVolume,
onBlur: () => setFocused(false),
inputRef,
setVolume: setMediaVolume
}) : null;
}, [
displayVerticalVolumeSlider,
focused,
hover,
mediaMuted,
mediaVolume,
renderVolumeSlider,
setMediaVolume
]);
return /* @__PURE__ */ jsxs4("div", {
ref: parentDivRef,
style: parentDivStyle,
children: [
muteButton,
volumeSlider
]
});
};
// src/PlaybackrateControl.tsx
import {
useCallback as useCallback6,
useContext as useContext5,
useEffect as useEffect8,
useMemo as useMemo5,
useState as useState8
} from "react";
import { Internals as Internals8 } from "remotion";
// src/utils/use-component-visible.ts
import { useEffect as useEffect7, useRef as useRef6, useState as useState7 } from "react";
function useComponentVisible(initialIsVisible) {
const [isComponentVisible, setIsComponentVisible] = useState7(initialIsVisible);
const ref = useRef6(null);
useEffect7(() => {
const handleClickOutside = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setIsComponentVisible(false);
}
};
document.addEventListener("pointerup", handleClickOutside, true);
return () => {
document.removeEventListener("pointerup", handleClickOutside, true);
};
}, []);
return { ref, isComponentVisible, setIsComponentVisible };
}
// src/PlaybackrateControl.tsx
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
var BOTTOM = 35;
var THRESHOLD = 70;
var rateDiv = {
height: 30,
paddingRight: 15,
paddingLeft: 12,
display: "flex",
flexDirection: "row",
alignItems: "center"
};
var checkmarkContainer = {
width: 22,
display: "flex",
alignItems: "center"
};
var checkmarkStyle = {
width: 14,
height: 14,
color: "black"
};
var Checkmark = () => /* @__PURE__ */ jsx7("svg", {
viewBox: "0 0 512 512",
style: checkmarkStyle,
children: /* @__PURE__ */ jsx7("path", {
fill: "currentColor",
d: "M435.848 83.466L172.804 346.51l-96.652-96.652c-4.686-4.686-12.284-4.686-16.971 0l-28.284 28.284c-4.686 4.686-4.686 12.284 0 16.971l133.421 133.421c4.686 4.686 12.284 4.686 16.971 0l299.813-299.813c4.686-4.686 4.686-12.284 0-16.971l-28.284-28.284c-4.686-4.686-12.284-4.686-16.97 0z"
})
});
var formatPlaybackRate = (rate) => {
const str = rate.toString();
return str.includes(".") ? str : str + ".0";
};
var PlaybackrateOption = ({ rate, onSelect, selectedRate, keyboardSelectedRate }) => {
const onClick = useCallback6((e) => {
e.stopPropagation();
e.preventDefault();
onSelect(rate);
}, [onSelect, rate]);
const [hovered, setHovered] = useState8(false);
const onMouseEnter = useCallback6(() => {
setHovered(true);
}, []);
const onMouseLeave = useCallback6(() => {
setHovered(false);
}, []);
const isFocused = keyboardSelectedRate === rate;
const actualStyle = useMemo5(() => {
return {
...rateDiv,
backgroundColor: hovered || isFocused ? "#eee" : "transparent"
};
}, [hovered, isFocused]);
return /* @__PURE__ */ jsxs5("div", {
onPointerEnter: onMouseEnter,
onPointerLeave: onMouseLeave,
tabIndex: 0,
style: actualStyle,
onClick,
children: [
/* @__PURE__ */ jsx7("div", {
style: checkmarkContainer,
children: rate === selectedRate ? /* @__PURE__ */ jsx7(Checkmark, {}) : null
}),
formatPlaybackRate(rate),
"x"
]
}, rate);
};
var PlaybackPopup = ({ setIsComponentVisible, playbackRates, canvasSize }) => {
const { setPlaybackRate, playbackRate } = useContext5(Internals8.TimelineContext);
const [keyboardSelectedRate, setKeyboardSelectedRate] = useState8(playbackRate);
useEffect8(() => {
const listener = (e) => {
e.preventDefault();
if (e.key === "ArrowUp") {
const currentIndex = playbackRates.findIndex((rate) => rate === keyboardSelectedRate);
if (currentIndex === 0) {
return;
}
if (currentIndex === -1) {
setKeyboardSelectedRate(playbackRates[0]);
} else {
setKeyboardSelectedRate(playbackRates[currentIndex - 1]);
}
} else if (e.key === "ArrowDown") {
const currentIndex = playbackRates.findIndex((rate) => rate === keyboardSelectedRate);
if (currentIndex === playbackRates.length - 1) {
return;
}
if (currentIndex === -1) {
setKeyboardSelectedRate(playbackRates[playbackRates.length - 1]);
} else {
setKeyboardSelectedRate(playbackRates[currentIndex + 1]);
}
} else if (e.key === "Enter") {
setPlaybackRate(keyboardSelectedRate);
setIsComponentVisible(false);
}
};
window.addEventListener("keydown", listener);
return () => {
window.removeEventListener("keydown", listener);
};
}, [
playbackRates,
keyboardSelectedRate,
setPlaybackRate,
setIsComponentVisible
]);
const onSelect = useCallback6((rate) => {
setPlaybackRate(rate);
setIsComponentVisible(false);
}, [setIsComponentVisible, setPlaybackRate]);
const playbackPopup = useMemo5(() => {
return {
position: "absolute",
right: 0,
width: 125,
maxHeight: canvasSize.height - THRESHOLD - BOTTOM,
bottom: 35,
background: "#fff",
borderRadius: 4,
overflow: "auto",
color: "black",
textAlign: "left"
};
}, [canvasSize.height]);
return /* @__PURE__ */ jsx7("div", {
style: playbackPopup,
children: playbackRates.map((rate) => {
return /* @__PURE__ */ jsx7(PlaybackrateOption, {
selectedRate: playbackRate,
onSelect,
rate,
keyboardSelectedRate
}, rate);
})
});
};
var label = {
fontSize: 13,
fontWeight: "bold",
color: "white",
border: "2px solid white",
borderRadius: 20,
paddingLeft: 8,
paddingRight: 8,
paddingTop: 2,
paddingBottom: 2
};
var playerButtonStyle = {
appearance: "none",
backgroundColor: "transparent",
border: "none",
cursor: "pointer",
paddingLeft: 0,
paddingRight: 0,
paddingTop: 6,
paddingBottom: 6,
height: 37,
display: "inline-flex",
marginBottom: 0,
marginTop: 0,
alignItems: "center"
};
var button = {
...playerButtonStyle,
position: "relative"
};
var PlaybackrateControl = ({ playbackRates, canvasSize }) => {
const { ref, isComponentVisible, setIsComponentVisible } = useComponentVisible(false);
const { playbackRate } = useContext5(Internals8.TimelineContext);
const onClick = useCallback6((e) => {
e.stopPropagation();
e.preventDefault();
setIsComponentVisible((prevIsComponentVisible) => !prevIsComponentVisible);
}, [setIsComponentVisible]);
return /* @__PURE__ */ jsx7("div", {
ref,
children: /* @__PURE__ */ jsxs5("button", {
type: "button",
"aria-label": "Change playback rate",
style: button,
onClick,
children: [
/* @__PURE__ */ jsxs5("div", {
style: label,
children: [
playbackRate,
"x"
]
}),
isComponentVisible && /* @__PURE__ */ jsx7(PlaybackPopup, {
canvasSize,
playbackRates,
setIsComponentVisible
})
]
})
});
};
// src/PlayerSeekBar.tsx
import { useCallback as useCallback7, useEffect as useEffect9, useMemo as useMemo6, useRef as useRef7, useState as useState9 } from "react";
import { Internals as Internals9, interpolate } from "remotion";
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
var getFrameFromX = (clientX, durationInFrames, width) => {
const pos = clientX;
const frame = Math.round(interpolate(pos, [0, width], [0, durationInFrames - 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp"
}));
return frame;
};
var BAR_HEIGHT2 = 5;
var KNOB_SIZE2 = 12;
var VERTICAL_PADDING = 4;
var containerStyle = {
userSelect: "none",
WebkitUserSelect: "none",
paddingTop: VERTICAL_PADDING,
paddingBottom: VERTICAL_PADDING,
boxSizing: "border-box",
cursor: "pointer",
position: "relative",
touchAction: "none"
};
var barBackground = {
height: BAR_HEIGHT2,
backgroundColor: "rgba(255, 255, 255, 0.25)",
width: "100%",
borderRadius: BAR_HEIGHT2 / 2
};
var findBodyInWhichDivIsLocated = (div) => {
let current = div;
while (current.parentElement) {
current = current.parentElement;
}
return current;
};
var PlayerSeekBar = ({ durationInFrames, onSeekEnd, onSeekStart, inFrame, outFrame }) => {
const containerRef = useRef7(null);
const barHovered = useHoverState(containerRef, false);
const size = useElementSize(containerRef, {
triggerOnWindowResize: true,
shouldApplyCssTransforms: true
});
const { seek, play, pause, playing } = usePlayer();
const frame = Internals9.Timeline.useTimelinePosition();
const [dragging, setDragging] = useState9({
dragging: false
});
const width = size?.width ?? 0;
const onPointerDown = useCallback7((e) => {
if (e.button !== 0) {
return;
}
const posLeft = containerRef.current?.getBoundingClientRect().left;
const _frame = getFrameFromX(e.clientX - posLeft, durationInFrames, width);
pause();
seek(_frame);
setDragging({
dragging: true,
wasPlaying: playing
});
onSeekStart();
}, [durationInFrames, width, pause, seek, playing, onSeekStart]);
const onPointerMove = useCallback7((e) => {
if (!size) {
throw new Error("Player has no size");
}
if (!dragging.dragging) {
return;
}
const posLeft = containerRef.current?.getBoundingClientRect().left;
const _frame = getFrameFromX(e.clientX - posLeft, durationInFrames, size.width);
seek(_frame);
}, [dragging.dragging, durationInFrames, seek, size]);
const onPointerUp = useCallback7(() => {
setDragging({
dragging: false
});
if (!dragging.dragging) {
return;
}
if (dragging.wasPlaying) {
play();
} else {
pause();
}
onSeekEnd();
}, [dragging, onSeekEnd, pause, play]);
useEffect9(() => {
if (!dragging.dragging) {
return;
}
const body = findBodyInWhichDivIsLocated(containerRef.current);
body.addEventListener("pointermove", onPointerMove);
body.addEventListener("pointerup", onPointerUp);
return () => {
body.removeEventListener("pointermove", onPointerMove);
body.removeEventListener("pointerup", onPointerUp);
};
}, [dragging.dragging, onPointerMove, onPointerUp]);
const knobStyle = useMemo6(() => {
return {
height: KNOB_SIZE2,
width: KNOB_SIZE2,
borderRadius: KNOB_SIZE2 / 2,
position: "absolute",
top: VERTICAL_PADDING - KNOB_SIZE2 / 2 + 5 / 2,
backgroundColor: "white",
left: Math.max(0, frame / Math.max(1, durationInFrames - 1) * width - KNOB_SIZE2 / 2),
boxShadow: "0 0 2px black",
opacity: Number(barHovered || dragging.dragging)
};
}, [barHovered, dragging.dragging, durationInFrames, frame, width]);
const fillStyle = useMemo6(() => {
return {
height: BAR_HEIGHT2,
backgroundColor: "rgba(255, 255, 255, 1)",
width: (frame - (inFrame ?? 0)) / (durationInFrames - 1) * width,
marginLeft: (inFrame ?? 0) / (durationInFrames - 1) * width,
borderRadius: BAR_HEIGHT2 / 2
};
}, [durationInFrames, frame, inFrame, width]);
const active = useMemo6(() => {
return {
height: BAR_HEIGHT2,
backgroundColor: "rgba(255, 255, 255, 0.25)",
width: ((outFrame ?? durationInFrames - 1) - (inFrame ?? 0)) / (durationInFrames - 1) * 100 + "%",
marginLeft: (inFrame ?? 0) / (durationInFrames - 1) * 100 + "%",
borderRadius: BAR_HEIGHT2 / 2,
position: "absolute"
};
}, [durationInFrames, inFrame, outFrame]);
return /* @__PURE__ */ jsxs6("div", {
ref: containerRef,
onPointerDown,
style: containerStyle,
children: [
/* @__PURE__ */ jsxs6("div", {
style: barBackground,
children: [
/* @__PURE__ */ jsx8("div", {
style: active
}),
/* @__PURE__ */ jsx8("div", {
style: fillStyle
})
]
}),
/* @__PURE__ */ jsx8("div", {
style: knobStyle
})
]
});
};
// src/PlayerTimeLabel.tsx
import { useMemo as useMemo7 } from "react";
import { Internals as Internals10 } from "remotion";
// src/format-time.ts
var formatTime = (timeInSeconds) => {
const minutes = Math.floor(timeInSeconds / 60);
const seconds = Math.floor(timeInSeconds - minutes * 60);
return `${String(minutes)}:${String(seconds).padStart(2, "0")}`;
};
// src/PlayerTimeLabel.tsx
import { jsxs as jsxs7 } from "react/jsx-runtime";
var PlayerTimeLabel = ({ durationInFrames, maxTimeLabelWidth, fps }) => {
const frame = Internals10.Timeline.useTimelinePosition();
const timeLabel = useMemo7(() => {
return {
color: "white",
fontFamily: "sans-serif",
fontSize: 14,
maxWidth: maxTimeLabelWidth === null ? undefined : maxTimeLabelWidth,
overflow: "hidden",
textOverflow: "ellipsis"
};
}, [maxTimeLabelWidth]);
const isLastFrame = frame === durationInFrames - 1;
const frameToDisplay = isLastFrame ? frame + 1 : frame;
return /* @__PURE__ */ jsxs7("div", {
style: timeLabel,
children: [
formatTime(frameToDisplay / fps),
" / ",
formatTime(durationInFrames / fps)
]
});
};
// src/use-video-controls-resize.ts
import { useMemo as useMemo8 } from "react";
var X_SPACER = 10;
var X_PADDING = 12;
var useVideoControlsResize = ({
allowFullscreen: allowFullScreen,
playerWidth
}) => {
const resizeInfo = useMemo8(() => {
const playPauseIconSize = ICON_SIZE;
const volumeIconSize = ICON_SIZE;
const _fullscreenIconSize = allowFullScreen ? fullscreenIconSize : 0;
const elementsSize = volumeIconSize + playPauseIconSize + _fullscreenIconSize + X_PADDING * 2 + X_SPACER * 2;
const maxTimeLabelWidth = playerWidth - elementsSize;
const maxTimeLabelWidthWithoutNegativeValue = Math.max(maxTimeLabelWidth, 0);
const availableTimeLabelWidthIfVolumeOpen = maxTimeLabelWidthWithoutNegativeValue - VOLUME_SLIDER_WIDTH;
const computedLabelWidth = availableTimeLabelWidthIfVolumeOpen < VOLUME_SLIDER_WIDTH ? maxTimeLabelWidthWithoutNegativeValue : availableTimeLabelWidthIfVolumeOpen;
const minWidthForHorizontalDisplay = computedLabelWidth + elementsSize + VOLUME_SLIDER_WIDTH;
const displayVerticalVolumeSlider = playerWidth < minWidthForHorizontalDisplay;
return {
maxTimeLabelWidth: maxTimeLabelWidthWithoutNegativeValue === 0 ? null : maxTimeLabelWidthWithoutNegativeValue,
displayVerticalVolumeSlider
};
}, [allowFullScreen, playerWidth]);
return resizeInfo;
};
// src/PlayerControls.tsx
import { jsx as jsx9, jsxs as jsxs8, Fragment as Fragment2 } from "react/jsx-runtime";
var gradientSteps = [
0,
0.013,
0.049,
0.104,
0.175,
0.259,
0.352,
0.45,
0.55,
0.648,
0.741,
0.825,
0.896,
0.951,
0.987
];
var gradientOpacities = [
0,
8.1,
15.5,
22.5,
29,
35.3,
41.2,
47.1,
52.9,
58.8,
64.7,
71,
77.5,
84.5,
91.9
];
var globalGradientOpacity = 1 / 0.7;
var containerStyle2 = {
boxSizing: "border-box",
position: "absolute",
bottom: 0,
width: "100%",
paddingTop: 40,
paddingBottom: 10,
backgroundImage: `linear-gradient(to bottom,${gradientSteps.map((g, i) => {
return `hsla(0, 0%, 0%, ${g}) ${gradientOpacities[i] * globalGradientOpacity}%`;
}).join(", ")}, hsl(0, 0%, 0%) 100%)`,
backgroundSize: "auto 145px",
display: "flex",
paddingRight: X_PADDING,
paddingLeft: X_PADDING,
flexDirection: "column",
transition: "opacity 0.3s"
};
var controlsRow = {
display: "flex",
flexDirection: "row",
width: "100%",
alignItems: "center",
justifyContent: "center",
userSelect: "none",
WebkitUserSelect: "none"
};
var leftPartStyle = {
display: "flex",
flexDirection: "row",
userSelect: "none",
WebkitUserSelect: "none",
alignItems: "center"
};
var xSpacer = {
width: 12
};
var ySpacer = {
height: 8
};
var flex1 = {
flex: 1
};
var fullscreen = {};
var Controls = ({
durationInFrames,
isFullscreen,
fps,
showVolumeControls,
onFullscreenButtonClick,
allowFullscreen,
onExitFullscreenButtonClick,
spaceKeyToPlayOrPause,
onSeekEnd,
onSeekStart,
inFrame,
outFrame,
initiallyShowControls,
canvasSize,
renderPlayPauseButton,
renderFullscreenButton,
alwaysShowControls,
showPlaybackRateControl,
containerRef,
buffering,
hideControlsWhenPointerDoesntMove,
onPointerDown,
onDoubleClick,
renderMuteButton,
renderVolumeSlider,
playing,
toggle,
renderCustomControls
}) => {
const playButtonRef = useRef8(null);
const [supportsFullscreen, setSupportsFullscreen] = useState10(false);
const hovered = useHoverState(containerRef, hideControlsWhenPointerDoesntMove);
const { maxTimeLabelWidth, displayVerticalVolumeSlider } = useVideoControlsResize({
allowFullscreen,
playerWidth: canvasSize?.width ?? 0
});
const [shouldShowInitially, setInitiallyShowControls] = useState10(() => {
if (typeof initiallyShowControls === "boolean") {
return initiallyShowControls;
}
if (typeof initiallyShowControls === "number") {
if (initiallyShowControls % 1 !== 0) {
throw new Error("initiallyShowControls must be an integer or a boolean");
}
if (Number.isNaN(initiallyShowControls)) {
throw new Error("initiallyShowControls must not be NaN");
}
if (!Number.isFinite(initiallyShowControls)) {
throw new Error("initiallyShowControls must be finite");
}
if (initiallyShowControls <= 0) {
throw new Error("initiallyShowControls must be a positive integer");
}
return initiallyShowControls;
}
throw new TypeError("initiallyShowControls must be a number or a boolean");
});
const containerCss = useMemo9(() => {
const shouldShow = hovered || !playing || shouldShowInitially || alwaysShowControls;
return {
...containerStyle2,
opacity: Number(shouldShow)
};
}, [hovered, shouldShowInitially, playing, alwaysShowControls]);
useEffect10(() => {
if (playButtonRef.current && spaceKeyToPlayOrPause) {
playButtonRef.current.focus({
preventScroll: true
});
}
}, [playing, spaceKeyToPlayOrPause]);
useEffect10(() => {
setSupportsFullscreen((typeof document !== "undefined" && (document.fullscreenEnabled || document.webkitFullscreenEnabled)) ?? false);
}, []);
useEffect10(() => {
if (shouldShowInitially === false) {
return;
}
const time = shouldShowInitially === true ? 2000 : shouldShowInitially;
const timeout = setTimeout(() => {
setInitiallyShowControls(false);
}, time);
return () => {
clearInterval(timeout);
};
}, [shouldShowInitially]);
const playbackRates = useMemo9(() => {
if (showPlaybackRateControl === true) {
return [0.5, 0.8, 1, 1.2, 1.5, 1.8, 2, 2.5, 3];
}
if (Array.isArray(showPlaybackRateControl)) {
for (const rate of showPlaybackRateControl) {
if (typeof rate !== "number") {
throw new Error("Every item in showPlaybackRateControl must be a number");
}
if (rate <= 0) {
throw new Error("Every item in showPlaybackRateControl must be positive");
}
}
return showPlaybackRateControl;
}
return null;
}, [showPlaybackRateControl]);
const customControlsElement = renderCustomControls ? renderCustomControls() : null;
const ref = useRef8(null);
const flexRef = useRef8(null);
const onPointerDownIfContainer = useCallback8((e) => {
if (e.target === ref.current || e.target === flexRef.current) {
onPointerDown?.(e);
}
}, [onPointerDown]);
const onDoubleClickIfContainer = useCallback8((e) => {
if (e.target === ref.current || e.target === flexRef.current) {
onDoubleClick?.(e);
}
}, [onDoubleClick]);
return /* @__PURE__ */ jsxs8("div", {
ref,
style: containerCss,
onPointerDown: onPointerDownIfContainer,
onDoubleClick: onDoubleClickIfContainer,
children: [
/* @__PURE__ */ jsxs8("div", {
ref: flexRef,
style: controlsRow,
children: [
/* @__PURE__ */ jsxs8("div", {
style: leftPartStyle,
children: [
/* @__PURE__ */ jsx9("button", {
ref: playButtonRef,
type: "button",
style: playerButtonStyle,
onClick: toggle,
"aria-label": playing ? "Pause video" : "Play video",
title: playing ? "Pause video" : "Play video",
children: renderPlayPauseButton === null ? /* @__PURE__ */ jsx9(DefaultPlayPauseButton, {
buffering,
playing
}) : renderPlayPauseButton({
playing,
isBuffering: buffering
}) ?? /* @__PURE__ */ jsx9(DefaultPlayPauseButton, {
buffering,
playing
})
}),
showVolumeControls ? /* @__PURE__ */ jsxs8(Fragment2, {
children: [
/* @__PURE__ */ jsx9("div", {
style: xSpacer
}),
/* @__PURE__ */ jsx9(MediaVolumeSlider, {
renderMuteButton,
renderVolumeSlider,
displayVerticalVolumeSlider
})
]
}) : null,
/* @__PURE__ */ jsx9("div", {
style: xSpacer
}),
/* @__PURE__ */ jsx9(PlayerTimeLabel, {
durationInFrames,
fps,
maxTimeLabelWidth
}),
/* @__PURE__ */ jsx9("div", {
style: xSpacer
})
]
}),
/* @__PURE__ */ jsx9("div", {
style: flex1
}),
customControlsElement,
customControlsElement && playbackRates && canvasSize ? /* @__PURE__ */ jsx9("div", {
style: xSpacer
}) : null,
playbackRates && canvasSize && /* @__PURE__ */ jsx9(PlaybackrateControl, {
canvasSize,
playbackRates
}),
playbackRates && supportsFullscreen && allowFullscreen ? /* @__PURE__ */ jsx9("div", {
style: xSpacer
}) : null,
/* @__PURE__ */ jsx9("div", {
style: fullscreen,
children: supportsFullscreen && allowFullscreen ? /* @__PURE__ */ jsx9("button", {
type: "button",
"aria-label": isFullscreen ? "Exit fullscreen" : "Enter Fullscreen",
title: isFullscreen ? "Exit fullscreen" : "Enter Fullscreen",
style: playerButtonStyle,
onClick: isFullscreen ? onExitFullscreenButtonClick : onFullscreenButtonClick,
children: renderFullscreenButton === null ? /* @__PURE__ */ jsx9(FullscreenIcon, {
isFullscreen
}) : renderFullscreenButton({ isFullscreen })
}) : null
})
]
}),
/* @__PURE__ */ jsx9("div", {
style: ySpacer
}),
/* @__PURE__ */ jsx9(PlayerSeekBar, {
onSeekEnd,
onSeekStart,
durationInFrames,
inFrame,
outFrame
})
]
});
};
// src/error-boundary.tsx
import React8 from "react";
import { jsx as jsx10 } from "react/jsx-runtime";
var errorStyle = {
display: "flex",
justifyContent: "center",
alignItems: "center",
flex: 1,
height: "100%",
width: "100%"
};
class ErrorBoundary extends React8.Component {
state = { hasError: null };
static getDerivedStateFromError(error) {
return { hasError: error };
}
componentDidCatch(error) {
this.props.onError(error);
}
render() {
if (this.state.hasError) {
return /* @__PURE__ */ jsx10("div", {
style: errorStyle,
children: this.props.errorFallback({
error: this.state.hasError
})
});
}
return this.props.children;
}
}
// src/license-blacklist.tsx
import React9, { useEffect as useEffect11 } from "react";
import { jsx as jsx11 } from "react/jsx-runtime";
var getHashOfDomain = async () => {
if (typeof window === "undefined") {
return null;
}
if (typeof window.crypto === "undefined") {
return null;
}
if (typeof window.crypto.subtle === "undefined") {
return null;
}
try {
const hashBuffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(window.location.hostname));
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
} catch {
return null;
}
};
var style = {
backgroundColor: "red",
position: "absolute",
padding: 12,
fontFamily: "Arial"
};
var DOMAIN_BLACKLIST = [
"28d262b44cc61fa750f1686b16ad0604dabfe193fbc263eec05c89b7ad4c2cd6",
"4db1b0a94be33165dfefcb3ba03d04c7a2666dd27c496d3dc9fa41858e94925e",
"fbc48530bbf245da790f63675e84e06bab38c3b114fab07eb350025119922bdc",
"7baf10a8932757b1b3a22b3fce10a048747ac2f8eaf638603487e3705b07eb83",
"8a6c21a598d8c667272b5207c051b85997bf5b45d5fb712378be3f27cd72c6a6",
"a2f7aaac9c50a9255e7fc376110c4e0bfe153722dc66ed3c5d3bf2a135f65518"
];
var ran = false;
var RenderWarningIfBlacklist = () => {
const [unlicensed, setUnlicensed] = React9.useState(false);
useEffect11(() => {
if (ran) {
return;
}
ran = true;
getHashOfDomain().then((hash) => {
if (hash && DOMAIN_BLACKLIST.includes(hash)) {
setUnlicensed(true);
}
}).catch(() => {});
}, []);
useEffect11(() => {
if (!unlicensed) {
return;
}
const ensureBanner = () => {
const banner = document.querySelector(".warning-banner");
if (!banner) {
const div = document.createElement("div");
div.className = "warning-banner";
Object.assign(div.style, style, {
zIndex: "9999",
cssText: `${style.cssText} !important;`
});
div.innerHTML = `
<a href="https://github.com/remotion-dev/remotion/pull/4589" style="color: white;">
Remotion Unlicensed Contact hi@remotion.dev
</a>
`;
document.body.appendChild(div);
}
};
const observer = new MutationObserver(() => ensureBanner());
observer.observe(document.body, { childList: true, subtree: true });
return () => {
observer.disconnect();
};
}, [unlicensed]);
if (!unlicensed) {
return null;
}
return /* @__PURE__ */ jsx11("div", {
style,
className: "warning-banner",
children: /* @__PURE__ */ jsx11("a", {
style: { color: "white" },
href: "https://github.com/remotion-dev/remotion/pull/4589",
children: "Remotion Unlicensed Contact hi@remotion.dev"
})
});
};
// src/player-css-classname.ts
var playerCssClassname = (override) => {
return override ?? "__remotion-player";
};
// src/utils/is-node.ts
var IS_NODE = typeof document === "undefined";
// src/utils/use-click-prevention-on-double-click.ts
import { useCallback as useCallback10, useMemo as useMemo11 } from "react";
// src/utils/cancellable-promise.ts
var cancellablePromise = (promise) => {
let isCanceled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then((value) => {
if (isCanceled) {
reject({ isCanceled, value });
return;
}
resolve(value);
}).catch((error) => {
reject({ isCanceled, error });
});
});
return {
promise: wrappedPromise,
cancel: () => {
isCanceled = true;
}
};
};
// src/utils/delay.ts
var delay = (n) => new Promise((resolve) => setTimeout(resolve, n));
// src/utils/use-cancellable-promises.ts
import { useCallback as useCallback9, useMemo as useMemo10, useRef as useRef9 } from "react";
var useCancellablePromises = () => {
const pendingPromises = useRef9([]);
const appendPendingPromise = useCallback9((promise) => {
pendingPromises.current = [...pendingPromises.current, promise];
}, []);
const removePendingPromise = useCallback9((promise) => {
pendingPromises.current = pendingPromises.current.filter((p) => p !== promise);
}, []);
const clearPendingPromises = useCallback9(() => pendingPromises.current.map((p) => p.cancel()), []);
const api = useMemo10(() => ({
appendPendingPromise,
removePendingPromise,
clearPendingPromises
}), [appendPendingPromise, clearPendingPromises, removePendingPromise]);
return api;
};
// src/utils/use-click-prevention-on-double-click.ts
var useClickPreventionOnDoubleClick = (onClick, onDoubleClick, doubleClickToFullscreen) => {
const api = useCancellablePromises();
const handleClick = useCallback10(async (e) => {
if (e instanceof PointerEvent ? e.pointerType === "touch" : e.nativeEvent.pointerType === "touch") {
onClick(e);
return;
}
api.clearPendingPromises();
const waitForClick = cancellablePromise(delay(200));
api.appendPendingPromise(waitForClick);
try {
await waitForClick.promise;
api.removePendingPromise(waitForClick);
onClick(e);
} catch (errorInfo) {
const info = errorInfo;
api.removePendingPromise(waitForClick);
if (!info.isCanceled) {
throw info.error;
}
}
}, [api, onClick]);
const handlePointerDown = useCallback10(() => {
document.addEventListener("pointerup", (newEvt) => {
handleClick(newEvt);
}, {
once: true
});
}, [handleClick]);
const handleDoubleClick = useCallback10(() => {
api.clearPendingPromises();
onDoubleClick();
}, [api, onDoubleClick]);
const returnValue = useMemo11(() => {
if (!doubleClickToFullscreen) {
return { handlePointerDown: onClick, handleDoubleClick: () => {
return;
} };
}
return { handlePointerDown, handleDoubleClick };
}, [doubleClickToFullscreen, handleDoubleClick, handlePointerDown, onClick]);
return returnValue;
};
// src/PlayerUI.tsx
import { jsx as jsx12, jsxs as jsxs9, Fragment as Fragment3 } from "react/jsx-runtime";
var reactVersion = React10.version.split(".")[0];
if (reactVersion === "0") {
throw new Error(`Version ${reactVersion} of "react" is not supported by Remotion`);
}
var doesReactVersionSupportSuspense = parseInt(reactVersion, 10) >= 18;
var PlayerUI = ({
controls,
style: style2,
loop,
autoPlay,
allowFullscreen,
inputProps,
clickToPlay,
showVolumeControls,
doubleClickToFullscreen,
spaceKeyToPlayOrPause,
errorFallback,
playbackRate,
renderLoading,
renderPoster,
className: className2,
moveToBeginningWhenEnded,
showPosterWhenUnplayed,
showPosterWhenEnded,
showPosterWhenPaused,
showPosterWhenBuffering,
showPosterWhenBufferingAndPaused,
inFrame,
outFrame,
initiallyShowControls,
renderFullscreen: renderFullscreenButton,
renderPlayPauseButton,
renderMuteButton,
renderVolumeSlider,
renderCustomControls,
alwaysShowControls,
showPlaybackRateControl,
posterFillMode,
bufferStateDelayInMilliseconds,
hideControlsWhenPointerDoesntMove,
overflowVisible,
browserMediaControlsBehavior,
overrideInternalClassName,
noSuspense
}, ref) => {
const config = Internals11.useUnsafeVideoConfig();
const video = Internals11.useVideo();
const container = useRef10(null);
const canvasSize = useElementSize(container, {
triggerOnWindowResize: false,
shouldApplyCssTransforms: false
});
const [hasPausedToResume, setHasPausedToResume] = useState11(false);
const [shouldAutoplay, setShouldAutoPlay] = useState11(autoPlay);
const [isFullscreen, setIsFullscreen] = useState11(() => false);
const [seeking, setSeeking] = useState11(false);
const supportsFullScreen = useMemo12(() => {
if (typeof document === "undefined") {
return false;
}
return Boolean(document.fullscreenEnabled || document.webkitFullscreenEnabled);
}, []);
const player = usePlayer();
const playerToggle = player.toggle;
usePlayback({
loop,
playbackRate,
moveToBeginningWhenEnded,
inFrame,
outFrame,
getCurrentFrame: player.getCurrentFrame,
browserMediaControlsBehavior
});
useEffect12(() => {
if (hasPausedToResume && !player.playing) {
setHasPausedToResume(false);
player.play();
}
}, [hasPausedToResume, player]);
useEffect12(() => {
const { current } = container;
if (!current) {
return;
}
const onFullscreenChange = () => {
const newValue = document.fullscreenElement === current || document.webkitFullscreenElement === current;
setIsFullscreen(newValue);
};
document.addEventListener("fullscreenchange", onFullscreenChange);
document.addEventListener("webkitfullscreenchange", onFullscreenChange);
return () => {
document.removeEventListener("fullscreenchange", onFullscreenChange);
document.removeEventListener("webkitfullscreenchange", onFullscreenChange);
};
}, []);
const toggle = useCallback11((e) => {
playerToggle(e);
}, [playerToggle]);
const requestFullscreen = useCallback11(() => {
if (!allowFullscreen) {
throw new Error("allowFullscreen is false");
}
if (!supportsFullScreen) {
throw new Error("Browser doesnt support fullscreen");
}
if (!container.current) {
throw new Error("No player ref found");
}
if (container.current.webkitRequestFullScreen) {
container.current.webkitRequestFullScreen();
} else {
container.current.requestFullscreen();
}
}, [allowFullscreen, supportsFullScreen]);
const exitFullscreen = useCallback11(() => {
if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else {
document.exitFullscreen();
}
}, []);
useEffect12(() => {
const { current } = container;
if (!current) {
return;
}
const fullscreenChange = () => {
const element = document.webkitFullscreenElement ?? document.fullscreenElement;
if (element && element === container.current) {
player.emitter.dispatchFullscreenChange({
isFullscreen: true
});
} else {
player.emitter.dispatchFullscreenChange({
isFullscreen: false
});
}
};
current.addEventListener("webkitfullscreenchange", fullscreenChange);
current.addEventListener("fullscreenchange", fullscreenChange);
return () => {
current.removeEventListener("webkitfullscreenchange", fullscreenChange);
current.removeEventListener("fullscreenchange", fullscreenChange);
};
}, [player.emitter]);
const durationInFrames = config?.durationInFrames ?? 1;
const layout = useMemo12(() => {
if (!config || !canvasSize) {
return null;
}
return calculateCanvasTransformation({
canvasSize,
compositionHeight: config.height,
compositionWidth: config.width,
previewSize: "auto"
});
}, [canvasSize, config]);
const scale = layout?.scale ?? 1;
const initialScaleIgnored = useRef10(false);
useEffect12(() => {
if (!initialScaleIgnored.current) {
initialScaleIgnored.current = true;
return;
}
player.emitter.dispatchScaleChange(scale);
}, [player.emitter, scale]);
const { setMediaVolume, setMediaMuted } = useContext6(Internals11.SetMediaVolumeContext);
const { mediaMuted, mediaVolume } = useContext6(Internals11.MediaVolumeContext);
useEffect12(() => {
player.emitter.dispatchVolumeChange(mediaVolume);
}, [player.emitter, mediaVolume]);
const isMuted = mediaMuted || mediaVolume === 0;
useEffect12(() => {
player.emitter.dispatchMuteChange({
isMuted
});
}, [player.emitter, isMuted]);
const [showBufferIndicator, setShowBufferState] = useState11(false);
useEffect12(() => {
let timeout = null;
let stopped = false;
const onBuffer = () => {
stopped = false;
requestAnimationFrame(() => {
if (bufferStateDelayInMilliseconds === 0) {
setShowBufferState(true);
} else {
timeout = setTimeout(() => {
if (!stopped) {
setShowBufferState(true);
}
}, bufferStateDelayInMilliseconds);
}
});
};
const onResume = () => {
requestAnimationFrame(() => {
stopped = true;
setShowBufferState(false);
if (timeout) {
clearTimeout(timeout);
}
});
};
player.emitter.addEventListener("waiting", onBuffer);
player.emitter.addEventListener("resume", onResume);
return () => {
player.emitter.removeEventListener("waiting", onBuffer);
player.emitter.removeEventListener("resume", onResume);
setShowBufferState(false);
if (timeout) {
clearTimeout(timeout);
}
stopped = true;
};
}, [bufferStateDelayInMilliseconds, player.emitter]);
useImperativeHandle(ref, () => {
const methods = {
play: player.play,
pause: () => {
setHasPausedToResume(false);
player.pause();
},
toggle,
getContainerNode: () => container.current,
getCurrentFrame: player.getCurrentFrame,
isPlaying: player.isPlaying,
seekTo: (f) => {
const lastFrame = durationInFrames - 1;
const frameToSeekTo = Math.max(0, Math.min(lastFrame, f));
if (player.isPlaying()) {
const pauseToResume = frameToSeekTo !== lastFrame || loop;
setHasPausedToResume(pauseToResume);
player.pause();
}
if (frameToSeekTo === lastFrame && !loop) {
player.emitter.dispatchEnded();
}
player.seek(frameToSeekTo);
},
isFullscreen: () => {
const { current } = container;
if (!current) {
return false;
}
return document.fullscreenElement === current || document.webkitFullscreenElement === current;
},
requestFullscreen,
exitFullscreen,
getVolume: () => {
if (mediaMuted) {
return 0;
}
return mediaVolume;
},
setVolume: (vol) => {
if (typeof vol !== "number") {
throw new TypeError(`setVolume() takes a number, got value of type ${typeof vol}`);
}
if (isNaN(vol)) {
throw new TypeError(`setVolume() got a number that is NaN. Volume must be between 0 and 1.`);
}
if (vol < 0 || vol > 1) {
throw new TypeError(`setVolume() got a number that is out of range. Must be between 0 and 1, got ${typeof vol}`);
}
setMediaVolume(vol);
},
isMuted: () => isMuted,
mute: () => {
setMediaMuted(true);
},
unmute: () => {
setMediaMuted(false);
},
getScale: () => scale,
pauseAndReturnToPlayStart: () => {
player.pauseAndReturnToPlayStart();
}
};
return Object.assign(player.emitter, methods);
}, [
durationInFrames,
exitFullscreen,
loop,
mediaMuted,
isMuted,
mediaVolume,
player,
requestFullscreen,
setMediaMuted,
setMediaVolume,
toggle,
scale
]);
const VideoComponent = video ? video.component : null;
const outerStyle = useMemo12(() => {
return calculateOuterStyle({
canvasSize,
config,
style: style2,
overflowVisible,
layout
});
}, [canvasSize, config, layout, overflowVisible, style2]);
const outer = useMemo12(() => {
return calculateOuter({ config, layout, scale, overflowVisible });
}, [config, layout, overflowVisible, scale]);
const containerStyle3 = useMemo12(() => {
return calculateContainerStyle({
config,
layout,
scale,
overflowVisible
});
}, [config, layout, overflowVisible, scale]);
const playerPause = player.pause;
const playerDispatchError = player.emitter.dispatchError;
const onError = useCallback11((error) => {
playerPause();
playerDispatchError(error);
}, [playerDispatchError, playerPause]);
const onFullscreenButtonClick = useCallback11((e) => {
e.stopPropagation();
requestFullscreen();
}, [requestFullscreen]);
const onExitFullscreenButtonClick = useCallback11((e) => {
e.stopPropagation();
exitFullscreen();
}, [exitFullscreen]);
const onSingleClick = useCallback11((e) => {
const rightClick = e instanceof MouseEvent ? e.button === 2 : e.nativeEvent.button;
if (rightClick) {
return;
}
toggle(e);
}, [toggle]);
const onSeekStart = useCallback11(() => {
setSeeking(true);
}, []);
const onSeekEnd = useCallback11(() => {
setSeeking(false);
}, []);
const onDoubleClick = useCallback11(() => {
if (isFullscreen) {
exitFullscreen();
} else {
requestFullscreen();
}
}, [exitFullscreen, isFullscreen, requestFullscreen]);
const { handlePointerDown, handleDoubleClick } = useClickPreventionOnDoubleClick(onSingleClick, onDoubleClick, doubleClickToFullscreen && allowFullscreen && supportsFullScreen);
useEffect12(() => {
if (shouldAutoplay) {
player.play();
setShouldAutoPlay(false);
}
}, [shouldAutoplay, player]);
const loadingMarkup = useMemo12(() => {
return renderLoading ? renderLoading({
height: outerStyle.height,
width: outerStyle.width,
isBuffering: showBufferIndicator
}) : null;
}, [outerStyle.height, outerStyle.width, renderLoading, showBufferIndicator]);
const currentScale = useMemo12(() => {
return {
type: "scale",
scale
};
}, [scale]);
if (!config) {
return null;
}
const poster = renderPoster ? renderPoster({
height: posterFillMode === "player-size" ? outerStyle.height : config.height,
width: posterFillMode === "player-size" ? outerStyle.width : config.width,
isBuffering: showBufferIndicator
}) : null;
if (poster === undefined) {
throw new TypeError("renderPoster() must return a React element, but undefined was returned");
}
const shouldShowPoster = poster && [
showPosterWhenPaused && !player.isPlaying() && !seeking,
showPosterWhenEnded && player.isLastFrame && !player.isPlaying(),
showPosterWhenUnplayed && !player.hasPlayed && !player.isPlaying(),
showPosterWhenBuffering && showBufferIndicator && player.isPlaying(),
showPosterWhenBufferingAndPaused && showBufferIndicator && !player.isPlaying()
].some(Boolean);
const { left, top, width, height, ...outerWithoutScale } = outer;
const content = /* @__PURE__ */ jsxs9(Fragment3, {
children: [
/* @__PURE__ */ jsxs9("div", {
style: outer,
onPointerDown: clickToPlay ? handlePointerDown : undefined,
onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined,
children: [
/* @__PURE__ */ jsxs9("div", {
style: containerStyle3,
className: playerCssClassname(overrideInternalClassName),
children: [
VideoComponent ? /* @__PURE__ */ jsx12(ErrorBoundary, {
onError,
errorFallback,
children: /* @__PURE__ */ jsx12(Internals11.CurrentScaleContext.Provider, {
value: currentScale,
children: /* @__PURE__ */ jsx12(VideoComponent, {
...video?.props ?? {},
...inputProps ?? {}
})
})
}) : null,
shouldShowPoster && posterFillMode === "composition-size" ? /* @__PURE__ */ jsx12("div", {
style: {
...outerWithoutScale,
width: config.width,
height: config.height
},
onPointerDown: clickToPlay ? handlePointerDown : undefined,
onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined,
children: poster
}) : null
]
}),
/* @__PURE__ */ jsx12(RenderWarningIfBlacklist, {})
]
}),
shouldShowPoster && posterFillMode === "player-size" ? /* @__PURE__ */ jsx12("div", {
style: outer,
onPointerDown: clickToPlay ? handlePointerDown : undefined,
onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined,
children: poster
}) : null,
controls ? /* @__PURE__ */ jsx12(Controls, {
fps: config.fps,
playing: player.playing,
toggle: player.toggle,
durationInFrames: config.durationInFrames,
containerRef: container,
onFullscreenButtonClick,
isFullscreen,
allowFullscreen,
showVolumeControls,
onExitFullscreenButtonClick,
spaceKeyToPlayOrPause,
onSeekEnd,
onSeekStart,
inFrame,
outFrame,
initiallyShowControls,
canvasSize,
renderFullscreenButton,
renderPlayPauseButton,
alwaysShowControls,
showPlaybackRateControl,
buffering: showBufferIndicator,
hideControlsWhenPointerDoesntMove,
onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined,
onPointerDown: clickToPlay ? handlePointerDown : undefined,
renderMuteButton,
renderVolumeSlider,
renderCustomControls
}) : null
]
});
if (noSuspense || IS_NODE && !doesReactVersionSupportSuspense) {
return /* @__PURE__ */ jsx12("div", {
ref: container,
style: outerStyle,
className: className2,
children: content
});
}
return /* @__PURE__ */ jsx12("div", {
ref: container,
style: outerStyle,
className: className2,
children: /* @__PURE__ */ jsx12(Suspense, {
fallback: loadingMarkup,
children: content
})
});
};
var PlayerUI_default = forwardRef(PlayerUI);
// src/SharedPlayerContext.tsx
import { useCallback as useCallback12, useMemo as useMemo13, useState as useState12 } from "react";
import { Internals as Internals13 } from "remotion";
// src/volume-persistance.ts
import { Internals as Internals12 } from "remotion";
var DEFAULT_VOLUME_PERSISTANCE_KEY = "remotion.volumePreference";
var persistVolume = (volume, logLevel, volumePersistenceKey) => {
if (typeof window === "undefined") {
return;
}
try {
window.localStorage.setItem(volumePersistenceKey ?? DEFAULT_VOLUME_PERSISTANCE_KEY, String(volume));
} catch (e) {
Internals12.Log.error({ logLevel, tag: null }, "Could not persist volume", e);
}
};
var getPreferredVolume = (volumePersistenceKey) => {
if (typeof window === "undefined") {
return 1;
}
try {
const val = window.localStorage.getItem(volumePersistenceKey ?? DEFAULT_VOLUME_PERSISTANCE_KEY);
return val ? Number(val) : 1;
} catch {
return 1;
}
};
// src/SharedPlayerContext.tsx
import { jsx as jsx13 } from "react/jsx-runtime";
var PLAYER_COMP_ID = "player-comp";
var SharedPlayerContexts = ({
children,
timelineContext,
fps,
compositionHeight,
compositionWidth,
durationInFrames,
component,
numberOfSharedAudioTags,
initiallyMuted,
logLevel,
audioLatencyHint,
volumePersistenceKey,
inputProps,
audioEnabled
}) => {
const compositionManagerContext = useMemo13(() => {
const context = {
compositions: [
{
component,
durationInFrames,
height: compositionHeight,
width: compositionWidth,
fps,
id: PLAYER_COMP_ID,
nonce: 777,
folderName: null,
parentFolderName: null,
schema: null,
calculateMetadata: null
}
],
folders: [],
currentCompositionMetadata: {
defaultCodec: null,
defaultOutName: null,
defaultPixelFormat: null,
defaultProResProfile: null,
defaultVideoImageFormat: null,
durationInFrames,
fps,
height: compositionHeight,
width: compositionWidth,
props: inputProps
},
canvasContent: { type: "composition", compositionId: "player-comp" }
};
return context;
}, [
component,
durationInFrames,
compositionHeight,
compositionWidth,
fps,
inputProps
]);
const [mediaMuted, setMediaMuted] = useState12(() => initiallyMuted);
const [mediaVolume, setMediaVolume] = useState12(() => getPreferredVolume(volumePersistenceKey ?? null));
const mediaVolumeContextValue = useMemo13(() => {
return {
mediaMuted,
mediaVolume
};
}, [mediaMuted, mediaVolume]);
const setMediaVolumeAndPersist = useCallback12((vol) => {
setMediaVolume(vol);
persistVolume(vol, logLevel, volumePersistenceKey ?? null);
}, [logLevel, volumePersistenceKey]);
const setMediaVolumeContextValue = useMemo13(() => {
return {
setMediaMuted,
setMediaVolume: setMediaVolumeAndPersist
};
}, [setMediaVolumeAndPersist]);
const logLevelContext = useMemo13(() => {
return {
logLevel,
mountTime: Date.now()
};
}, [logLevel]);
const env = useMemo13(() => {
return {
isPlayer: true,
isRendering: false,
isStudio: false,
isClientSideRendering: false,
isReadOnlyStudio: false
};
}, []);
return /* @__PURE__ */ jsx13(Internals13.RemotionEnvironmentContext.Provider, {
value: env,
children: /* @__PURE__ */ jsx13(Internals13.LogLevelContext.Provider, {
value: logLevelContext,
children: /* @__PURE__ */ jsx13(Internals13.CanUseRemotionHooksProvider, {
children: /* @__PURE__ */ jsx13(Internals13.TimelineContext.Provider, {
value: timelineContext,
children: /* @__PURE__ */ jsx13(Internals13.CompositionManager.Provider, {
value: compositionManagerContext,
children: /* @__PURE__ */ jsx13(Internals13.PrefetchProvider, {
children: /* @__PURE__ */ jsx13(Internals13.DurationsContextProvider, {
children: /* @__PURE__ */ jsx13(Internals13.MediaVolumeContext.Provider, {
value: mediaVolumeContextValue,
children: /* @__PURE__ */ jsx13(Internals13.SetMediaVolumeContext.Provider, {
value: setMediaVolumeContextValue,
children: /* @__PURE__ */ jsx13(Internals13.SharedAudioContextProvider, {
numberOfAudioTags: numberOfSharedAudioTags,
audioLatencyHint,
audioEnabled,
children: /* @__PURE__ */ jsx13(Internals13.BufferingProvider, {
children
})
})
})
})
})
})
})
})
})
})
});
};
// src/use-remotion-license-acknowledge.ts
import { Internals as Internals14 } from "remotion";
var warningShown = false;
var acknowledgeRemotionLicenseMessage = (acknowledge, logLevel) => {
if (acknowledge) {
return;
}
if (warningShown) {
return;
}
warningShown = true;
Internals14.Log.warn({ logLevel, tag: null }, "Note: Some companies are required to obtain a license to use Remotion. See: https://remotion.dev/license\nPass the `acknowledgeRemotionLicense` prop to `<Player />` function to make this message disappear.");
};
// src/utils/validate-in-out-frame.ts
var validateSingleFrame = (frame, variableName) => {
if (typeof frame === "undefined" || frame === null) {
return frame ?? null;
}
if (typeof frame !== "number") {
throw new TypeError(`"${variableName}" must be a number, but is ${JSON.stringify(frame)}`);
}
if (Number.isNaN(frame)) {
throw new TypeError(`"${variableName}" must not be NaN, but is ${JSON.stringify(frame)}`);
}
if (!Number.isFinite(frame)) {
throw new TypeError(`"${variableName}" must be finite, but is ${JSON.stringify(frame)}`);
}
if (frame % 1 !== 0) {
throw new TypeError(`"${variableName}" must be an integer, but is ${JSON.stringify(frame)}`);
}
return frame;
};
var validateInOutFrames = ({
inFrame,
durationInFrames,
outFrame
}) => {
const validatedInFrame = validateSingleFrame(inFrame, "inFrame");
const validatedOutFrame = validateSingleFrame(outFrame, "outFrame");
if (validatedInFrame === null && validatedOutFrame === null) {
return;
}
if (validatedInFrame !== null && validatedInFrame > durationInFrames - 1) {
throw new Error("inFrame must be less than (durationInFrames - 1), but is " + validatedInFrame);
}
if (validatedOutFrame !== null && validatedOutFrame > durationInFrames - 1) {
throw new Error("outFrame must be less than (durationInFrames - 1), but is " + validatedOutFrame);
}
if (validatedInFrame !== null && validatedInFrame < 0) {
throw new Error("inFrame must be greater than 0, but is " + validatedInFrame);
}
if (validatedOutFrame !== null && validatedOutFrame <= 0) {
throw new Error(`outFrame must be greater than 0, but is ${validatedOutFrame}. If you want to render a single frame, use <Thumbnail /> instead.`);
}
if (validatedOutFrame !== null && validatedInFrame !== null && validatedOutFrame <= validatedInFrame) {
throw new Error("outFrame must be greater than inFrame, but is " + validatedOutFrame + " <= " + validatedInFrame);
}
};
// src/utils/validate-initial-frame.ts
var validateInitialFrame = ({
initialFrame,
durationInFrames
}) => {
if (typeof durationInFrames !== "number") {
throw new Error(`\`durationInFrames\` must be a number, but is ${JSON.stringify(durationInFrames)}`);
}
if (typeof initialFrame === "undefined") {
return;
}
if (typeof initialFrame !== "number") {
throw new Error(`\`initialFrame\` must be a number, but is ${JSON.stringify(initialFrame)}`);
}
if (Number.isNaN(initialFrame)) {
throw new Error(`\`initialFrame\` must be a number, but is NaN`);
}
if (!Number.isFinite(initialFrame)) {
throw new Error(`\`initialFrame\` must be a number, but is Infinity`);
}
if (initialFrame % 1 !== 0) {
throw new Error(`\`initialFrame\` must be an integer, but is ${JSON.stringify(initialFrame)}`);
}
if (initialFrame > durationInFrames - 1) {
throw new Error(`\`initialFrame\` must be less or equal than \`durationInFrames - 1\`, but is ${JSON.stringify(initialFrame)}`);
}
};
// src/utils/validate-playbackrate.ts
var validatePlaybackRate = (playbackRate) => {
if (playbackRate === undefined) {
return;
}
if (playbackRate > 4) {
throw new Error(`The highest possible playback rate is 4. You passed: ${playbackRate}`);
}
if (playbackRate < -4) {
throw new Error(`The lowest possible playback rate is -4. You passed: ${playbackRate}`);
}
if (playbackRate === 0) {
throw new Error(`A playback rate of 0 is not supported.`);
}
};
// src/validate.ts
import { NoReactInternals } from "remotion/no-react";
var validateFps = NoReactInternals.validateFps;
var validateDimension = NoReactInternals.validateDimension;
var validateDurationInFrames = NoReactInternals.validateDurationInFrames;
var validateDefaultAndInputProps = NoReactInternals.validateDefaultAndInputProps;
// src/Player.tsx
import { jsx as jsx14 } from "react/jsx-runtime";
var componentOrNullIfLazy = (props) => {
if ("component" in props) {
return props.component;
}
return null;
};
var PlayerFn = ({
durationInFrames,
compositionHeight,
compositionWidth,
fps,
inputProps,
style: style2,
controls = false,
loop = false,
autoPlay = false,
showVolumeControls = true,
allowFullscreen = true,
clickToPlay,
doubleClickToFullscreen = false,
spaceKeyToPlayOrPause = true,
moveToBeginningWhenEnded = true,
numberOfSharedAudioTags = 5,
errorFallback = () => "⚠️",
playbackRate = 1,
renderLoading,
className: className2,
showPosterWhenUnplayed,
showPosterWhenEnded,
showPosterWhenPaused,
showPosterWhenBuffering,
showPosterWhenBufferingAndPaused,
initialFrame,
renderPoster,
inFrame,
outFrame,
initiallyShowControls,
renderFullscreenButton,
renderPlayPauseButton,
renderVolumeSlider,
renderCustomControls,
alwaysShowControls = false,
initiallyMuted = false,
showPlaybackRateControl = false,
posterFillMode = "player-size",
bufferStateDelayInMilliseconds,
hideControlsWhenPointerDoesntMove = true,
overflowVisible = false,
renderMuteButton,
browserMediaControlsBehavior: passedBrowserMediaControlsBehavior,
overrideInternalClassName,
logLevel = "info",
noSuspense,
acknowledgeRemotionLicense,
audioLatencyHint = "interactive",
volumePersistenceKey,
...componentProps
}, ref) => {
if (typeof window !== "undefined") {
window.remotion_isPlayer = true;
}
if (componentProps.defaultProps !== undefined) {
throw new Error("The <Player /> component does not accept `defaultProps`, but some were passed. Use `inputProps` instead.");
}
const componentForValidation = componentOrNullIfLazy(componentProps);
if (componentForValidation?.type === Composition) {
throw new TypeError(`'component' should not be an instance of <Composition/>. Pass the React component directly, and set the duration, fps and dimensions as separate props. See https://www.remotion.dev/docs/player/examples for an example.`);
}
if (componentForValidation === Composition) {
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.`);
}
useState13(() => acknowledgeRemotionLicenseMessage(Boolean(acknowledgeRemotionLicense), logLevel));
const component = Internals15.useLazyComponent({
compProps: componentProps,
componentName: "Player",
noSuspense: Boolean(noSuspense)
});
validateInitialFrame({ initialFrame, durationInFrames });
const [frame, setFrame] = useState13(() => ({
[PLAYER_COMP_ID]: initialFrame ?? 0
}));
const [playing, setPlaying] = useState13(false);
const [rootId] = useState13("player-comp");
const rootRef = useRef11(null);
const audioAndVideoTags = useRef11([]);
const imperativePlaying = useRef11(false);
const [currentPlaybackRate, setCurrentPlaybackRate] = useState13(playbackRate);
if (typeof compositionHeight !== "number") {
throw new TypeError(`'compositionHeight' must be a number but got '${typeof compositionHeight}' instead`);
}
if (typeof compositionWidth !== "number") {
throw new TypeError(`'compositionWidth' must be a number but got '${typeof compositionWidth}' instead`);
}
validateDimension(compositionHeight, "compositionHeight", "of the <Player /> component");
validateDimension(compositionWidth, "compositionWidth", "of the <Player /> component");
validateDurationInFrames(durationInFrames, {
component: "of the <Player/> component",
allowFloats: false
});
validateFps(fps, "as a prop of the <Player/> component", false);
validateDefaultAndInputProps(inputProps, "inputProps", null);
validateInOutFrames({
durationInFrames,
inFrame,
outFrame
});
if (typeof controls !== "boolean" && typeof controls !== "undefined") {
throw new TypeError(`'controls' must be a boolean or undefined but got '${typeof controls}' instead`);
}
if (typeof autoPlay !== "boolean" && typeof autoPlay !== "undefined") {
throw new TypeError(`'autoPlay' must be a boolean or undefined but got '${typeof autoPlay}' instead`);
}
if (typeof loop !== "boolean" && typeof loop !== "undefined") {
throw new TypeError(`'loop' must be a boolean or undefined but got '${typeof loop}' instead`);
}
if (typeof doubleClickToFullscreen !== "boolean" && typeof doubleClickToFullscreen !== "undefined") {
throw new TypeError(`'doubleClickToFullscreen' must be a boolean or undefined but got '${typeof doubleClickToFullscreen}' instead`);
}
if (typeof showVolumeControls !== "boolean" && typeof showVolumeControls !== "undefined") {
throw new TypeError(`'showVolumeControls' must be a boolean or undefined but got '${typeof showVolumeControls}' instead`);
}
if (typeof allowFullscreen !== "boolean" && typeof allowFullscreen !== "undefined") {
throw new TypeError(`'allowFullscreen' must be a boolean or undefined but got '${typeof allowFullscreen}' instead`);
}
if (typeof clickToPlay !== "boolean" && typeof clickToPlay !== "undefined") {
throw new TypeError(`'clickToPlay' must be a boolean or undefined but got '${typeof clickToPlay}' instead`);
}
if (typeof spaceKeyToPlayOrPause !== "boolean" && typeof spaceKeyToPlayOrPause !== "undefined") {
throw new TypeError(`'spaceKeyToPlayOrPause' must be a boolean or undefined but got '${typeof spaceKeyToPlayOrPause}' instead`);
}
if (typeof numberOfSharedAudioTags !== "number" || numberOfSharedAudioTags % 1 !== 0 || !Number.isFinite(numberOfSharedAudioTags) || Number.isNaN(numberOfSharedAudioTags) || numberOfSharedAudioTags < 0) {
throw new TypeError(`'numberOfSharedAudioTags' must be an integer but got '${numberOfSharedAudioTags}' instead`);
}
validatePlaybackRate(currentPlaybackRate);
useEffect13(() => {
setCurrentPlaybackRate(playbackRate);
}, [playbackRate]);
useImperativeHandle2(ref, () => rootRef.current, []);
useState13(() => {
Internals15.playbackLogging({
logLevel,
message: `[player] Mounting <Player>. User agent = ${typeof navigator === "undefined" ? "server" : navigator.userAgent}`,
tag: "player",
mountTime: Date.now()
});
});
const timelineContextValue = useMemo14(() => {
return {
frame,
playing,
rootId,
playbackRate: currentPlaybackRate,
imperativePlaying,
setPlaybackRate: (rate) => {
setCurrentPlaybackRate(rate);
},
audioAndVideoTags
};
}, [frame, currentPlaybackRate, playing, rootId]);
const setTimelineContextValue = useMemo14(() => {
return {
setFrame,
setPlaying
};
}, [setFrame]);
if (typeof window !== "undefined") {
useLayoutEffect2(() => {
Internals15.CSSUtils.injectCSS(Internals15.CSSUtils.makeDefaultPreviewCSS(`.${playerCssClassname(overrideInternalClassName)}`, "#fff"));
}, [overrideInternalClassName]);
}
const actualInputProps = useMemo14(() => inputProps ?? {}, [inputProps]);
const browserMediaControlsBehavior = useMemo14(() => {
return passedBrowserMediaControlsBehavior ?? {
mode: "prevent-media-session"
};
}, [passedBrowserMediaControlsBehavior]);
return /* @__PURE__ */ jsx14(Internals15.IsPlayerContextProvider, {
children: /* @__PURE__ */ jsx14(SharedPlayerContexts, {
timelineContext: timelineContextValue,
component,
compositionHeight,
compositionWidth,
durationInFrames,
fps,
numberOfSharedAudioTags,
initiallyMuted,
logLevel,
audioLatencyHint,
volumePersistenceKey,
inputProps: actualInputProps,
audioEnabled: true,
children: /* @__PURE__ */ jsx14(Internals15.SetTimelineContext.Provider, {
value: setTimelineContextValue,
children: /* @__PURE__ */ jsx14(PlayerEmitterProvider, {
currentPlaybackRate,
children: /* @__PURE__ */ jsx14(PlayerUI_default, {
ref: rootRef,
posterFillMode,
renderLoading,
autoPlay: Boolean(autoPlay),
loop: Boolean(loop),
controls: Boolean(controls),
errorFallback,
style: style2,
inputProps: actualInputProps,
allowFullscreen: Boolean(allowFullscreen),
moveToBeginningWhenEnded: Boolean(moveToBeginningWhenEnded),
clickToPlay: typeof clickToPlay === "boolean" ? clickToPlay : Boolean(controls),
showVolumeControls: Boolean(showVolumeControls),
doubleClickToFullscreen: Boolean(doubleClickToFullscreen),
spaceKeyToPlayOrPause: Boolean(spaceKeyToPlayOrPause),
playbackRate: currentPlaybackRate,
className: className2 ?? undefined,
showPosterWhenUnplayed: Boolean(showPosterWhenUnplayed),
showPosterWhenEnded: Boolean(showPosterWhenEnded),
showPosterWhenPaused: Boolean(showPosterWhenPaused),
showPosterWhenBuffering: Boolean(showPosterWhenBuffering),
showPosterWhenBufferingAndPaused: Boolean(showPosterWhenBufferingAndPaused),
renderPoster,
inFrame: inFrame ?? null,
outFrame: outFrame ?? null,
initiallyShowControls: initiallyShowControls ?? true,
renderFullscreen: renderFullscreenButton ?? null,
renderPlayPauseButton: renderPlayPauseButton ?? null,
renderMuteButton: renderMuteButton ?? null,
renderVolumeSlider: renderVolumeSlider ?? null,
renderCustomControls: renderCustomControls ?? null,
alwaysShowControls,
showPlaybackRateControl,
bufferStateDelayInMilliseconds: bufferStateDelayInMilliseconds ?? 300,
hideControlsWhenPointerDoesntMove,
overflowVisible,
browserMediaControlsBehavior,
overrideInternalClassName: overrideInternalClassName ?? undefined,
noSuspense: Boolean(noSuspense)
})
})
})
})
});
};
var forward = forwardRef2;
var Player = forward(PlayerFn);
// src/Thumbnail.tsx
import {
forwardRef as forwardRef4,
useImperativeHandle as useImperativeHandle4,
useLayoutEffect as useLayoutEffect3,
useMemo as useMemo17,
useRef as useRef13,
useState as useState14
} from "react";
import { Internals as Internals17, random as random2 } from "remotion";
// src/ThumbnailUI.tsx
import React13, {
forwardRef as forwardRef3,
Suspense as Suspense2,
useCallback as useCallback13,
useImperativeHandle as useImperativeHandle3,
useMemo as useMemo16,
useRef as useRef12
} from "react";
import { Internals as Internals16 } from "remotion";
// src/use-thumbnail.ts
import { useContext as useContext7, useMemo as useMemo15 } from "react";
var useThumbnail = () => {
const emitter = useContext7(ThumbnailEmitterContext);
if (!emitter) {
throw new TypeError("Expected Player event emitter context");
}
const returnValue = useMemo15(() => {
return {
emitter
};
}, [emitter]);
return returnValue;
};
// src/ThumbnailUI.tsx
import { jsx as jsx15 } from "react/jsx-runtime";
var reactVersion2 = React13.version.split(".")[0];
if (reactVersion2 === "0") {
throw new Error(`Version ${reactVersion2} of "react" is not supported by Remotion`);
}
var doesReactVersionSupportSuspense2 = parseInt(reactVersion2, 10) >= 18;
var ThumbnailUI = ({
style: style2,
inputProps,
errorFallback,
renderLoading,
className: className2,
overflowVisible,
noSuspense,
overrideInternalClassName
}, ref) => {
const config = Internals16.useUnsafeVideoConfig();
const video = Internals16.useVideo();
const container = useRef12(null);
const canvasSize = useElementSize(container, {
triggerOnWindowResize: false,
shouldApplyCssTransforms: false
});
const layout = useMemo16(() => {
if (!config || !canvasSize) {
return null;
}
return calculateCanvasTransformation({
canvasSize,
compositionHeight: config.height,
compositionWidth: config.width,
previewSize: "auto"
});
}, [canvasSize, config]);
const scale = layout?.scale ?? 1;
const thumbnail = useThumbnail();
useBufferStateEmitter(thumbnail.emitter);
useImperativeHandle3(ref, () => {
const methods = {
getContainerNode: () => container.current,
getScale: () => scale
};
return Object.assign(thumbnail.emitter, methods);
}, [scale, thumbnail.emitter]);
const VideoComponent = video ? video.component : null;
const outerStyle = useMemo16(() => {
return calculateOuterStyle({
config,
style: style2,
canvasSize,
overflowVisible,
layout
});
}, [canvasSize, config, layout, overflowVisible, style2]);
const outer = useMemo16(() => {
return calculateOuter({ config, layout, scale, overflowVisible });
}, [config, layout, overflowVisible, scale]);
const containerStyle3 = useMemo16(() => {
return calculateContainerStyle({
config,
layout,
scale,
overflowVisible
});
}, [config, layout, overflowVisible, scale]);
const onError = useCallback13((error) => {
thumbnail.emitter.dispatchError(error);
}, [thumbnail.emitter]);
const loadingMarkup = useMemo16(() => {
return renderLoading ? renderLoading({
height: outerStyle.height,
width: outerStyle.width,
isBuffering: false
}) : null;
}, [outerStyle.height, outerStyle.width, renderLoading]);
const currentScaleContext = useMemo16(() => {
return {
type: "scale",
scale
};
}, [scale]);
if (!config) {
return null;
}
const content = /* @__PURE__ */ jsx15("div", {
style: outer,
children: /* @__PURE__ */ jsx15("div", {
style: containerStyle3,
className: playerCssClassname(overrideInternalClassName),
children: VideoComponent ? /* @__PURE__ */ jsx15(ErrorBoundary, {
onError,
errorFallback,
children: /* @__PURE__ */ jsx15(Internals16.CurrentScaleContext.Provider, {
value: currentScaleContext,
children: /* @__PURE__ */ jsx15(VideoComponent, {
...video?.props ?? {},
...inputProps ?? {}
})
})
}) : null
})
});
if (noSuspense || IS_NODE && !doesReactVersionSupportSuspense2) {
return /* @__PURE__ */ jsx15("div", {
ref: container,
style: outerStyle,
className: className2,
children: content
});
}
return /* @__PURE__ */ jsx15("div", {
ref: container,
style: outerStyle,
className: className2,
children: /* @__PURE__ */ jsx15(Suspense2, {
fallback: loadingMarkup,
children: content
})
});
};
var ThumbnailUI_default = forwardRef3(ThumbnailUI);
// src/Thumbnail.tsx
import { jsx as jsx16 } from "react/jsx-runtime";
var ThumbnailFn = ({
frameToDisplay,
style: style2,
inputProps,
compositionHeight,
compositionWidth,
durationInFrames,
fps,
className: className2,
errorFallback = () => "⚠️",
renderLoading,
overflowVisible = false,
overrideInternalClassName,
logLevel = "info",
noSuspense,
...componentProps
}, ref) => {
if (typeof window !== "undefined") {
useLayoutEffect3(() => {
window.remotion_isPlayer = true;
}, []);
}
const [thumbnailId] = useState14(() => String(random2(null)));
const rootRef = useRef13(null);
const timelineState = useMemo17(() => {
const value = {
playing: false,
frame: {
[PLAYER_COMP_ID]: frameToDisplay
},
rootId: thumbnailId,
imperativePlaying: {
current: false
},
playbackRate: 1,
setPlaybackRate: () => {
throw new Error("thumbnail");
},
audioAndVideoTags: { current: [] }
};
return value;
}, [frameToDisplay, thumbnailId]);
useImperativeHandle4(ref, () => rootRef.current, []);
const Component = Internals17.useLazyComponent({
compProps: componentProps,
componentName: "Thumbnail",
noSuspense: Boolean(noSuspense)
});
const [emitter] = useState14(() => new ThumbnailEmitter);
const passedInputProps = useMemo17(() => {
return inputProps ?? {};
}, [inputProps]);
return /* @__PURE__ */ jsx16(Internals17.IsPlayerContextProvider, {
children: /* @__PURE__ */ jsx16(SharedPlayerContexts, {
timelineContext: timelineState,
component: Component,
compositionHeight,
compositionWidth,
durationInFrames,
fps,
numberOfSharedAudioTags: 0,
initiallyMuted: true,
logLevel,
audioLatencyHint: "playback",
inputProps: passedInputProps,
audioEnabled: false,
children: /* @__PURE__ */ jsx16(ThumbnailEmitterContext.Provider, {
value: emitter,
children: /* @__PURE__ */ jsx16(ThumbnailUI_default, {
ref: rootRef,
className: className2,
errorFallback,
inputProps: passedInputProps,
renderLoading,
style: style2,
overflowVisible,
overrideInternalClassName,
noSuspense: Boolean(noSuspense)
})
})
})
});
};
var forward2 = forwardRef4;
var Thumbnail = forward2(ThumbnailFn);
// src/index.ts
var PlayerInternals = {
PlayerEventEmitterContext,
PlayerEmitter,
usePlayer,
usePlayback,
useElementSize,
calculateCanvasTransformation,
useHoverState,
updateAllElementsSizes,
PlayerEmitterProvider,
BufferingIndicator,
useFrameImperative
};
export {
Thumbnail,
PlayerInternals,
Player
};