"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 = `
Remotion Unlicensed – Contact hi@remotion.dev
`;
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 `` 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 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 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 . 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 component");
validateDimension(compositionWidth, "compositionWidth", "of the component");
validateDurationInFrames(durationInFrames, {
component: "of the component",
allowFloats: false
});
validateFps(fps, "as a prop of the 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 . 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
};