@aidenlx/vidstack-react
Version:
UI component library for building high-quality, accessible video and audio experiences on the web.
349 lines (336 loc) • 12.4 kB
JavaScript
"use client"
import * as React from 'react';
import { createDisposalBin, animationFrameThrottle, noop } from 'maverick.js/std';
import { Internals, random } from 'remotion';
import { IS_SERVER, useMediaState, isRemotionSrc, Primitive, useSliderState } from '../chunks/vidstack-B1ySk2FQ.js';
export { isRemotionProvider } from '../chunks/vidstack-B1ySk2FQ.js';
import { RemotionThumbnail as RemotionThumbnail$1, RemotionSliderThumbnail as RemotionSliderThumbnail$1, RemotionPoster as RemotionPoster$1 } from '../chunks/vidstack-DcGAdum5.js';
import 'maverick.js/react';
import 'maverick.js';
import '../chunks/vidstack-CPShcCv0.js';
import '@floating-ui/dom';
class RemotionLayoutEngine {
#src = null;
#viewport = null;
#canvas = null;
#container = null;
#disposal = createDisposalBin();
constructor() {
}
setSrc(src) {
this.#src = src;
this.setContainer(this.#container);
}
setContainer(container) {
if (IS_SERVER) return;
this.#disposal.empty();
this.#container = container;
this.#canvas = container?.parentElement ?? null;
this.#viewport = this.#canvas?.parentElement ?? null;
if (this.#src && this.#viewport) {
const onResize = animationFrameThrottle(this.#onResize.bind(this));
onResize();
const observer = new ResizeObserver(onResize);
observer.observe(this.#viewport);
this.#disposal.add(() => observer.disconnect());
}
}
destroy() {
this.#disposal.empty();
}
#onResize(entries) {
if (!this.#viewport || !this.#src) return;
const rect = this.#getRect(this.#viewport, entries?.[0]), scale = this.#calcScale(rect), transform = this.#calcTransform(rect, scale);
Object.assign(this.#canvas.style, {
width: this.#src.compositionWidth * scale + "px",
height: this.#src.compositionHeight * scale + "px",
display: "flex",
flexDirection: "column",
position: "absolute",
left: transform.centerX,
top: transform.centerY,
overflow: "hidden"
});
Object.assign(this.#container.style, {
position: "absolute",
width: this.#src.compositionWidth + "px",
height: this.#src.compositionHeight + "px",
display: "flex",
transform: `scale(${scale})`,
marginLeft: transform.x,
marginTop: transform.y,
overflow: "hidden"
});
}
#getRect(el, entry) {
const rect = el.getBoundingClientRect();
if (!entry) return rect;
const { contentRect, target } = entry, newSize = target.getClientRects();
if (!newSize?.[0]) return rect;
const scale = contentRect.width === 0 ? 1 : newSize[0].width / contentRect.width, width = newSize[0].width * (1 / scale), height = newSize[0].height * (1 / scale);
return {
width,
height,
top: newSize[0].y,
left: newSize[0].x
};
}
#calcScale(rect) {
if (!this.#src) return 0;
const heightRatio = rect.height / this.#src.compositionHeight, widthRatio = rect.width / this.#src.compositionWidth;
return Math.min(heightRatio || 0, widthRatio || 0);
}
#calcTransform(rect, scale) {
if (!this.#src) return {};
const correction = 0 - (1 - scale) / 2, x = correction * this.#src.compositionWidth, y = correction * this.#src.compositionHeight, width = this.#src.compositionWidth * scale, height = this.#src.compositionHeight * scale, centerX = rect.width / 2 - width / 2, centerY = rect.height / 2 - height / 2;
return { x, y, centerX, centerY };
}
}
const REMOTION_PROVIDER_ID = "vds-remotion-provider";
function RemotionContextProvider({
src: {
compositionWidth: width,
compositionHeight: height,
fps,
durationInFrames,
numberOfSharedAudioTags
},
component,
timeline,
mediaVolume,
setMediaVolume,
children,
numberOfSharedAudioTags: providedNumberOfAudioTags
}) {
const compositionManager = React.useMemo(() => {
return {
compositions: [
{
id: REMOTION_PROVIDER_ID,
component,
durationInFrames,
width,
height,
fps,
nonce: 777,
folderName: null,
parentFolderName: null,
schema: null,
calculateMetadata: null
}
],
folders: [],
registerFolder: () => void 0,
unregisterFolder: () => void 0,
registerComposition: () => void 0,
unregisterComposition: () => void 0,
updateCompositionDefaultProps: noop,
currentCompositionMetadata: null,
setCurrentCompositionMetadata: () => void 0,
canvasContent: { type: "composition", compositionId: REMOTION_PROVIDER_ID },
setCanvasContent: () => void 0
};
}, [component, width, height, fps, durationInFrames]);
const sequenceManager = React.useMemo(() => {
let sequences = [];
return {
get sequences() {
return sequences;
},
registerSequence(sequence) {
sequences = [...sequences, sequence];
},
unregisterSequence(sequence) {
sequences = sequences.filter((s) => s.id !== sequence);
}
};
}, []);
return /* @__PURE__ */ React.createElement(Internals.IsPlayerContextProvider, null, /* @__PURE__ */ React.createElement(Internals.CanUseRemotionHooksProvider, null, /* @__PURE__ */ React.createElement(Internals.Timeline.TimelineContext.Provider, { value: timeline }, /* @__PURE__ */ React.createElement(Internals.CompositionManager.Provider, { value: compositionManager }, /* @__PURE__ */ React.createElement(Internals.SequenceManager.Provider, { value: sequenceManager }, /* @__PURE__ */ React.createElement(Internals.ResolveCompositionConfig, null, /* @__PURE__ */ React.createElement(Internals.PrefetchProvider, null, /* @__PURE__ */ React.createElement(Internals.DurationsContextProvider, null, /* @__PURE__ */ React.createElement(Internals.MediaVolumeContext.Provider, { value: mediaVolume }, /* @__PURE__ */ React.createElement(Internals.NativeLayersProvider, null, /* @__PURE__ */ React.createElement(Internals.SetMediaVolumeContext.Provider, { value: setMediaVolume }, /* @__PURE__ */ React.createElement(
Internals.SharedAudioContextProvider,
{
numberOfAudioTags: providedNumberOfAudioTags ?? numberOfSharedAudioTags,
component
},
children
))))))))))));
}
RemotionContextProvider.displayName = "RemotionContextProvider";
const errorStyle = {
display: "flex",
alignItems: "center",
justifyContent: "center",
color: " #ff3333",
padding: "24px",
position: "absolute",
inset: "0",
width: "100%",
height: "100%"
} ;
class ErrorBoundary extends React.Component {
static displayName = "ErrorBoundary";
state = { hasError: null };
static getDerivedStateFromError(hasError) {
return { hasError };
}
componentDidCatch(error) {
this.props.onError?.(error);
}
render() {
const error = this.state.hasError;
if (error) {
return /* @__PURE__ */ React.createElement("div", { style: errorStyle }, this.props.fallback?.(error) ?? /* @__PURE__ */ React.createElement("div", { style: { fontWeight: "bold" } }, "An error has occurred, see console for more information."));
}
return this.props.children;
}
}
const RemotionThumbnail = React.forwardRef(
({ frame, renderLoading, errorFallback, onError, ...props }, ref) => {
let $src = useMediaState("currentSrc"), layoutEngine = React.useMemo(() => new RemotionLayoutEngine(), []);
if (isRemotionSrc($src) && !$src.compositionWidth) {
$src = {
compositionWidth: 1920,
compositionHeight: 1080,
fps: 30,
...$src
};
}
React.useEffect(() => {
layoutEngine.setSrc(isRemotionSrc($src) ? $src : null);
return () => void layoutEngine.setSrc(null);
}, [$src]);
const Component = Internals.useLazyComponent({
component: $src.src
});
const thumbnailId = React.useMemo(() => String(random(null)), []), baseTimeline = React.useMemo(
() => ({
rootId: thumbnailId,
frame: { [REMOTION_PROVIDER_ID]: frame },
playing: false,
playbackRate: 1,
setPlaybackRate: noop,
audioAndVideoTags: { current: [] },
imperativePlaying: { current: false }
}),
[thumbnailId]
), timeline = React.useMemo(
() => ({
...baseTimeline,
frame: { [REMOTION_PROVIDER_ID]: frame }
}),
[baseTimeline, frame]
), volume = React.useMemo(
() => ({
mediaMuted: true,
mediaVolume: 0,
setMediaMuted: noop,
setMediaVolume: noop
}),
[]
);
const [, update] = React.useState(0);
React.useEffect(() => {
update(1);
}, []);
if (!isRemotionSrc($src)) return null;
return /* @__PURE__ */ React.createElement(
RemotionContextProvider,
{
src: $src,
component: Component,
timeline,
mediaVolume: volume,
setMediaVolume: volume,
numberOfSharedAudioTags: 0
},
/* @__PURE__ */ React.createElement(Primitive.div, { ...props, ref, "data-remotion-thumbnail": true }, /* @__PURE__ */ React.createElement("div", { "data-remotion-canvas": true }, /* @__PURE__ */ React.createElement(
"div",
{
"data-remotion-container": true,
ref: (el) => {
layoutEngine.setContainer(el);
}
},
/* @__PURE__ */ React.createElement(
RemotionThumbnailUI,
{
inputProps: $src.inputProps,
renderLoading: renderLoading ?? $src.renderLoading,
errorFallback: errorFallback ?? $src.errorFallback,
onError: onError ?? $src.onError
}
)
)))
);
}
);
RemotionThumbnail.displayName = "RemotionThumbnail";
function RemotionThumbnailUI({
inputProps,
renderLoading,
errorFallback,
onError
}) {
const video = Internals.useVideo(), Video = video ? video.component : null, LoadingContent = React.useMemo(() => renderLoading?.(), [renderLoading]);
return /* @__PURE__ */ React.createElement(React.Suspense, { fallback: LoadingContent }, Video ? /* @__PURE__ */ React.createElement(ErrorBoundary, { fallback: errorFallback, onError }, /* @__PURE__ */ React.createElement(Internals.ClipComposition, null, /* @__PURE__ */ React.createElement(Video, { ...video?.props, ...inputProps }))) : null);
}
RemotionThumbnailUI.displayName = "RemotionThumbnailUI";
var thumbnail = /*#__PURE__*/Object.freeze({
__proto__: null,
default: RemotionThumbnail
});
const RemotionPoster = React.forwardRef((props, ref) => {
const $isVisible = !useMediaState("started");
return /* @__PURE__ */ React.createElement(
RemotionThumbnail,
{
...props,
ref,
"data-remotion-poster": true,
"data-visible": $isVisible || null
}
);
});
RemotionPoster.displayName = "RemotionPoster";
var poster = /*#__PURE__*/Object.freeze({
__proto__: null,
default: RemotionPoster
});
const RemotionSliderThumbnail = React.forwardRef(
(props, ref) => {
const $src = useMediaState("currentSrc"), $percent = useSliderState("pointerPercent");
if (!isRemotionSrc($src)) return null;
return /* @__PURE__ */ React.createElement(
RemotionThumbnail,
{
...props,
frame: $src.durationInFrames * ($percent / 100),
ref,
"data-remotion-slider-thumbnail": true
}
);
}
);
RemotionSliderThumbnail.displayName = "RemotionSliderThumbnail";
var sliderThumbnail = /*#__PURE__*/Object.freeze({
__proto__: null,
default: RemotionSliderThumbnail
});
class RemotionProviderLoader {
name = "remotion";
target;
constructor() {
RemotionThumbnail$1.set(React.lazy(() => Promise.resolve().then(function () { return thumbnail; })));
RemotionSliderThumbnail$1.set(React.lazy(() => Promise.resolve().then(function () { return sliderThumbnail; })));
RemotionPoster$1.set(React.lazy(() => Promise.resolve().then(function () { return poster; })));
}
canPlay(src) {
return src.type === "video/remotion";
}
mediaType() {
return "video";
}
async load(ctx) {
return new (await import('../chunks/vidstack-D4k9kb58.js')).RemotionProvider(this.target, ctx);
}
}
export { ErrorBoundary, REMOTION_PROVIDER_ID, RemotionContextProvider, RemotionLayoutEngine, RemotionPoster, RemotionProviderLoader, RemotionSliderThumbnail, RemotionThumbnail, isRemotionSrc as isRemotionSource, isRemotionSrc };