@remotion/player
Version:
React component for embedding a Remotion preview into your app
1,724 lines (1,691 loc) • 111 kB
JavaScript
"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,
canvasSize,
layout,
scale,
overflowVisible
}) => {
if (!config || !canvasSize || !layout) {
return {};
}
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 (!layout || !config) {
return {};
}
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 as useEffect2, 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, useEffect } from "react";
import { Internals as Internals2 } from "remotion";
var useBufferStateEmitter = (emitter) => {
const bufferManager = useContext(Internals2.BufferingContextReact);
if (!bufferManager) {
throw new Error("BufferingContextReact not found");
}
useEffect(() => {
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");
}
useEffect2(() => {
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 useEffect3, useState as useState2 } from "react";
var useHoverState = (ref, hideControlsWhenPointerDoesntMove) => {
const [hovered, setHovered] = useState2(false);
useEffect3(() => {
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 useEffect6, useRef as useRef4 } from "react";
import { Internals as Internals6 } from "remotion";
// src/browser-mediasession.ts
import { useEffect as useEffect4 } 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.Timeline.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);
}
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();
}
}, [emitter, imperativePlaying, setPlaying]);
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 getCurrentFrame = useFrameImperative();
const toggle = useCallback2((e) => {
if (imperativePlaying.current) {
pause();
} else {
play(e);
}
}, [imperativePlaying, pause, play]);
const returnValue = useMemo(() => {
return {
frameBack,
frameForward,
isLastFrame,
emitter,
playing,
play,
pause,
seek,
isFirstFrame,
getCurrentFrame,
isPlaying: () => imperativePlaying.current,
isBuffering: () => buffering.current,
pauseAndReturnToPlayStart,
hasPlayed,
remotionInternal_currentFrameRef: frameRef,
toggle
};
}, [
buffering,
emitter,
frameBack,
frameForward,
getCurrentFrame,
hasPlayed,
imperativePlaying,
isFirstFrame,
isLastFrame,
pause,
pauseAndReturnToPlayStart,
play,
playing,
seek,
toggle
]);
return returnValue;
};
// src/browser-mediasession.ts
var useBrowserMediaSession = ({
browserMediaControlsBehavior,
videoConfig,
playbackRate
}) => {
const { playing, pause, play, emitter, getCurrentFrame, seek } = usePlayer();
useEffect4(() => {
if (!navigator.mediaSession) {
return;
}
if (browserMediaControlsBehavior.mode === "do-nothing") {
return;
}
if (playing) {
navigator.mediaSession.playbackState = "playing";
} else {
navigator.mediaSession.playbackState = "paused";
}
}, [browserMediaControlsBehavior.mode, playing]);
useEffect4(() => {
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
]);
useEffect4(() => {
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 useEffect5, useRef as useRef3 } from "react";
var getIsBackgrounded = () => {
if (typeof document === "undefined") {
return false;
}
return document.visibilityState === "hidden";
};
var useIsBackgrounded = () => {
const isBackgrounded = useRef3(getIsBackgrounded());
useEffect5(() => {
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 } = usePlayer();
const setFrame = Internals6.Timeline.useTimelineSetFrame();
const buffering = useRef4(null);
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
});
useEffect6(() => {
const onBufferClear = context.listenForBuffering(() => {
buffering.current = performance.now();
});
const onResumeClear = context.listenForResume(() => {
buffering.current = null;
});
return () => {
onBufferClear.remove();
onResumeClear.remove();
};
}, [context]);
useEffect6(() => {
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 = () => {
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;
}
if (!hasBeenStopped) {
queueNextFrame();
}
};
const queueNextFrame = () => {
if (buffering.current) {
const stopListening = context.listenForResume(() => {
stopListening.remove();
if (hasBeenStopped) {
return;
}
startedTime = performance.now();
framesAdvanced = 0;
callback();
});
return;
}
if (isBackgroundedRef.current) {
reqAnimFrameCall = {
type: "timeout",
id: setTimeout(callback, 1000 / config.fps)
};
} else {
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,
buffering,
context
]);
useEffect6(() => {
const interval = setInterval(() => {
if (lastTimeUpdateEvent.current === getCurrentFrame()) {
return;
}
emitter.dispatchTimeUpdate({ frame: getCurrentFrame() });
lastTimeUpdateEvent.current = getCurrentFrame();
}, 250);
return () => clearInterval(interval);
}, [emitter, getCurrentFrame]);
useEffect6(() => {
emitter.dispatchFrameUpdate({ frame });
}, [emitter, frame]);
};
// src/utils/use-element-size.ts
import { useCallback as useCallback3, useEffect as useEffect7, 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 ? newSize[0].width : newSize[0].width * (1 / probableCssParentScale);
const height = options.shouldApplyCssTransforms ? 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]);
useEffect7(() => {
if (!observer) {
return;
}
const { current } = ref;
if (current) {
observer.observe(current);
}
return () => {
if (current) {
observer.unobserve(current);
}
};
}, [observer, ref, updateSize]);
useEffect7(() => {
if (!options.triggerOnWindowResize) {
return;
}
window.addEventListener("resize", updateSize);
return () => {
window.removeEventListener("resize", updateSize);
};
}, [options.triggerOnWindowResize, updateSize]);
useEffect7(() => {
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 useEffect14,
useImperativeHandle as useImperativeHandle2,
useLayoutEffect,
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 useEffect13,
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 useEffect11, 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 useEffect9,
useMemo as useMemo5,
useState as useState8
} from "react";
import { Internals as Internals8 } from "remotion";
// src/utils/use-component-visible.ts
import { useEffect as useEffect8, useRef as useRef6, useState as useState7 } from "react";
function useComponentVisible(initialIsVisible) {
const [isComponentVisible, setIsComponentVisible] = useState7(initialIsVisible);
const ref = useRef6(null);
useEffect8(() => {
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 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", {
onMouseEnter,
onMouseLeave,
tabIndex: 0,
style: actualStyle,
onClick,
children: [
/* @__PURE__ */ jsx7("div", {
style: checkmarkContainer,
children: rate === selectedRate ? /* @__PURE__ */ jsx7(Checkmark, {}) : null
}),
rate.toFixed(1),
"x"
]
}, rate);
};
var PlaybackPopup = ({ setIsComponentVisible, playbackRates, canvasSize }) => {
const { setPlaybackRate, playbackRate } = useContext5(Internals8.Timeline.TimelineContext);
const [keyboardSelectedRate, setKeyboardSelectedRate] = useState8(playbackRate);
useEffect9(() => {
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.Timeline.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 useEffect10, 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]);
useEffect10(() => {
if (!dragging.dragging) {
return;
}
const body = findBodyInWhichDivIsLocated(containerRef.current);
body.addEventListener("pointermove", onPointerMove);
body.addEventListener("pointerup", onPointerUp);
return () => {
bod