@effing/effie-preview 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.js +7 -4
- package/dist/react/index.js.map +1 -1
- package/package.json +4 -4
package/dist/react/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import '@effing/ffs/sse';
|
|
|
6
6
|
type UseVideoStreamResult = {
|
|
7
7
|
videoRef: React.RefObject<HTMLVideoElement | null>;
|
|
8
8
|
isFullyBuffered: boolean;
|
|
9
|
+
blobRef: React.RefObject<Blob | null>;
|
|
9
10
|
};
|
|
10
11
|
/**
|
|
11
12
|
* Streams a video URL into a `<video>` element using MediaSource Extensions.
|
|
@@ -24,7 +25,7 @@ type EffieVideoPreviewProps = {
|
|
|
24
25
|
/** Callback when video starts playing */
|
|
25
26
|
onPlay?: () => void;
|
|
26
27
|
/** Callback when video is fully buffered (entire video downloaded) */
|
|
27
|
-
onFullyBuffered?: () => void;
|
|
28
|
+
onFullyBuffered?: (blob: Blob) => void;
|
|
28
29
|
/** Class name for the video element */
|
|
29
30
|
className?: string;
|
|
30
31
|
/** Style for the video element */
|
|
@@ -49,7 +50,7 @@ type EffieCoverPreviewProps = {
|
|
|
49
50
|
/** Callback when video starts playing */
|
|
50
51
|
onPlay?: () => void;
|
|
51
52
|
/** Callback when video is fully buffered (entire video downloaded) */
|
|
52
|
-
onFullyBuffered?: () => void;
|
|
53
|
+
onFullyBuffered?: (blob: Blob) => void;
|
|
53
54
|
/** Class name for the img/video element */
|
|
54
55
|
className?: string;
|
|
55
56
|
/** Style for the img/video element */
|
package/dist/react/index.js
CHANGED
|
@@ -48,11 +48,13 @@ function hashUrlToId(url) {
|
|
|
48
48
|
import { useEffect, useRef, useState } from "react";
|
|
49
49
|
function useVideoStream(url) {
|
|
50
50
|
const videoRef = useRef(null);
|
|
51
|
+
const blobRef = useRef(null);
|
|
51
52
|
const [isFullyBuffered, setIsFullyBuffered] = useState(false);
|
|
52
53
|
const lastUrlRef = useRef(null);
|
|
53
54
|
if (url !== lastUrlRef.current) {
|
|
54
55
|
lastUrlRef.current = url;
|
|
55
56
|
if (isFullyBuffered) setIsFullyBuffered(false);
|
|
57
|
+
blobRef.current = null;
|
|
56
58
|
}
|
|
57
59
|
useEffect(() => {
|
|
58
60
|
if (!url) return;
|
|
@@ -123,6 +125,7 @@ function useVideoStream(url) {
|
|
|
123
125
|
} else if (mediaSource && mediaSource.readyState === "open") {
|
|
124
126
|
mediaSource.endOfStream();
|
|
125
127
|
}
|
|
128
|
+
blobRef.current = new Blob(chunks, { type: "video/mp4" });
|
|
126
129
|
setIsFullyBuffered(true);
|
|
127
130
|
};
|
|
128
131
|
if (canMSE) {
|
|
@@ -147,7 +150,7 @@ function useVideoStream(url) {
|
|
|
147
150
|
objectUrls.forEach((u) => URL.revokeObjectURL(u));
|
|
148
151
|
};
|
|
149
152
|
}, [url]);
|
|
150
|
-
return { videoRef, isFullyBuffered };
|
|
153
|
+
return { videoRef, isFullyBuffered, blobRef };
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
// src/react/index.tsx
|
|
@@ -160,7 +163,7 @@ function EffieVideoPreview({
|
|
|
160
163
|
className,
|
|
161
164
|
style
|
|
162
165
|
}) {
|
|
163
|
-
const { videoRef, isFullyBuffered } = useVideoStream(url);
|
|
166
|
+
const { videoRef, isFullyBuffered, blobRef } = useVideoStream(url);
|
|
164
167
|
const firedRef = useRef2(false);
|
|
165
168
|
const lastUrlRef = useRef2(url);
|
|
166
169
|
if (url !== lastUrlRef.current) {
|
|
@@ -168,9 +171,9 @@ function EffieVideoPreview({
|
|
|
168
171
|
firedRef.current = false;
|
|
169
172
|
}
|
|
170
173
|
useEffect2(() => {
|
|
171
|
-
if (isFullyBuffered && onFullyBuffered && !firedRef.current) {
|
|
174
|
+
if (isFullyBuffered && onFullyBuffered && !firedRef.current && blobRef.current) {
|
|
172
175
|
firedRef.current = true;
|
|
173
|
-
onFullyBuffered();
|
|
176
|
+
onFullyBuffered(blobRef.current);
|
|
174
177
|
}
|
|
175
178
|
}, [isFullyBuffered, onFullyBuffered]);
|
|
176
179
|
return /* @__PURE__ */ jsx(
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/index.tsx","../../src/warmup.ts","../../src/utils.ts","../../src/react/use-video-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport type {\n EffieBackground,\n EffieSegment,\n EffieLayer,\n EffieSources,\n EffieWebUrl,\n} from \"@effing/effie\";\nimport { AnniePlayer } from \"@effing/annie-player/react\";\nimport type { EffieSourceResolver, EffieValidationIssue } from \"../core\";\nimport { connectEffieWarmupStream, type EffieWarmupState } from \"../warmup\";\nimport { hashUrlToId } from \"../utils\";\nexport { useVideoStream } from \"./use-video-stream\";\nexport type { UseVideoStreamResult } from \"./use-video-stream\";\nimport { useVideoStream } from \"./use-video-stream\";\n\n// ============ Video Preview ============\n\nexport type EffieVideoPreviewProps = {\n /** Video URL to stream via MSE */\n url: string;\n /** Optional poster image URL */\n poster?: string;\n /** Callback when video starts playing */\n onPlay?: () => void;\n /** Callback when video is fully buffered (entire video downloaded) */\n onFullyBuffered?: () => void;\n /** Class name for the video element */\n className?: string;\n /** Style for the video element */\n style?: React.CSSProperties;\n};\n\n/**\n * Streams a video via MSE (with blob fallback) so the entire file is buffered\n * in memory. This avoids follow-up network requests on seek, which is critical\n * for one-time-consumption URLs like FFS render endpoints.\n */\nexport function EffieVideoPreview({\n url,\n poster,\n onPlay,\n onFullyBuffered,\n className,\n style,\n}: EffieVideoPreviewProps) {\n const { videoRef, isFullyBuffered } = useVideoStream(url);\n const firedRef = useRef(false);\n\n // Reset fired flag when URL changes\n const lastUrlRef = useRef(url);\n if (url !== lastUrlRef.current) {\n lastUrlRef.current = url;\n firedRef.current = false;\n }\n\n useEffect(() => {\n if (isFullyBuffered && onFullyBuffered && !firedRef.current) {\n firedRef.current = true;\n onFullyBuffered();\n }\n }, [isFullyBuffered, onFullyBuffered]);\n\n return (\n <video\n ref={videoRef}\n poster={poster}\n crossOrigin=\"anonymous\"\n className={className}\n style={style}\n controls\n autoPlay\n onPlay={onPlay}\n />\n );\n}\n\n// ============ Cover Preview ============\n\nexport type EffieCoverPreviewProps = {\n /** Cover image URL */\n cover: EffieWebUrl;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** Optional video URL to show instead of cover image (e.g., after rendering) */\n video?: string | null;\n /** Callback when video starts playing */\n onPlay?: () => void;\n /** Callback when video is fully buffered (entire video downloaded) */\n onFullyBuffered?: () => void;\n /** Class name for the img/video element */\n className?: string;\n /** Style for the img/video element */\n style?: React.CSSProperties;\n};\n\n/**\n * Displays the effie cover image, or a video if provided.\n * When a video URL is given, renders an {@link EffieVideoPreview} with the\n * cover as poster image. Otherwise renders a static `<img>`.\n */\nexport function EffieCoverPreview({\n cover,\n resolution,\n video,\n onPlay,\n onFullyBuffered,\n className,\n style,\n}: EffieCoverPreviewProps) {\n if (video) {\n return (\n <EffieVideoPreview\n url={video}\n poster={cover}\n onPlay={onPlay}\n onFullyBuffered={onFullyBuffered}\n className={className}\n style={{ ...style, height: resolution.height }}\n />\n );\n }\n\n return (\n <img\n src={cover}\n alt=\"Cover\"\n className={className}\n style={{ ...style, height: resolution.height }}\n />\n );\n}\n\n// ============ Background Preview - Compound Components ============\n\ntype EffieBackgroundPreviewRootProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieBackgroundPreviewRoot({\n className,\n style,\n children,\n}: EffieBackgroundPreviewRootProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieBackgroundPreviewMediaProps = {\n /** Background configuration from effie JSON */\n background: EffieBackground<EffieSources>;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Class name for the media element */\n className?: string;\n /** Style for the media element */\n style?: React.CSSProperties;\n};\n\nfunction EffieBackgroundPreviewMedia({\n background,\n resolveSource,\n className,\n style,\n}: EffieBackgroundPreviewMediaProps) {\n if (background.type === \"color\") {\n return (\n <div\n className={className}\n style={{ ...style, backgroundColor: background.color }}\n />\n );\n }\n\n if (background.type === \"image\") {\n return (\n <img\n src={resolveSource(background.source)}\n alt=\"Background\"\n className={className}\n style={{ objectFit: \"cover\", ...style }}\n />\n );\n }\n\n if (background.type === \"video\") {\n return (\n <video\n src={resolveSource(background.source)}\n className={className}\n style={{ objectFit: \"cover\", ...style }}\n autoPlay\n loop\n muted\n />\n );\n }\n\n return null;\n}\n\ntype EffieBackgroundPreviewInfoProps = {\n /** Background configuration from effie JSON */\n background: EffieBackground<EffieSources>;\n /** Class name for the info section */\n className?: string;\n /** Style for the info section */\n style?: React.CSSProperties;\n};\n\nfunction EffieBackgroundPreviewInfo({\n background,\n className,\n style,\n}: EffieBackgroundPreviewInfoProps) {\n return (\n <div className={className} style={style}>\n <strong>Type:</strong> {background.type}\n {background.type === \"color\" && (\n <div>\n <strong>Color:</strong> {background.color}\n </div>\n )}\n </div>\n );\n}\n\n// Simple pre-composed Background Preview\n\nexport type EffieBackgroundPreviewProps = {\n /** Background configuration from effie JSON */\n background: EffieBackground<EffieSources>;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\nfunction EffieBackgroundPreviewSimple({\n background,\n resolveSource,\n resolution,\n className,\n style,\n}: EffieBackgroundPreviewProps) {\n return (\n <EffieBackgroundPreviewRoot\n className={className}\n style={{\n display: \"flex\",\n gap: \"1rem\",\n ...style,\n }}\n >\n <EffieBackgroundPreviewMedia\n background={background}\n resolveSource={resolveSource}\n style={{\n border: \"1px solid #ddd\",\n width: resolution.width,\n height: resolution.height,\n }}\n />\n <EffieBackgroundPreviewInfo\n background={background}\n style={{\n fontSize: \"0.85rem\",\n color: \"#666\",\n }}\n />\n </EffieBackgroundPreviewRoot>\n );\n}\n\n/**\n * Displays a preview of the effie background (color, image, or video).\n *\n * Usage:\n * - Simple: `<EffieBackgroundPreview background={...} resolveSource={...} />`\n * - Compound: `<EffieBackgroundPreview.Root>...</EffieBackgroundPreview.Root>`\n */\nexport const EffieBackgroundPreview = Object.assign(\n EffieBackgroundPreviewSimple,\n {\n Root: EffieBackgroundPreviewRoot,\n Media: EffieBackgroundPreviewMedia,\n Info: EffieBackgroundPreviewInfo,\n },\n);\n\n// ============ Layer Preview - Compound Components ============\n\ntype EffieLayerPreviewRootProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieLayerPreviewRoot({\n className,\n style,\n children,\n}: EffieLayerPreviewRootProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieLayerPreviewMediaProps = {\n /** Layer configuration from effie JSON */\n layer: EffieLayer<EffieSources>;\n /** Layer index (0-based) for alt text */\n index: number;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** Class name for the media element */\n className?: string;\n /** Style for the media element */\n style?: React.CSSProperties;\n};\n\nfunction EffieLayerPreviewMedia({\n layer,\n index,\n resolveSource,\n resolution,\n className,\n style,\n}: EffieLayerPreviewMediaProps) {\n if (layer.type === \"animation\") {\n return (\n <AnniePlayer\n src={resolveSource(layer.source)}\n height={resolution.height}\n defaultWidth={resolution.width}\n autoLoad={false}\n autoPlay={true}\n className={className}\n style={style}\n />\n );\n }\n\n return (\n <img\n src={resolveSource(layer.source)}\n alt={`Layer ${index + 1}`}\n className={className}\n style={{ ...style, height: resolution.height }}\n />\n );\n}\n\ntype EffieLayerPreviewInfoProps = {\n /** Layer configuration from effie JSON */\n layer: EffieLayer<EffieSources>;\n /** Layer index (0-based) */\n index: number;\n /** Class name for the info section */\n className?: string;\n /** Style for the info section */\n style?: React.CSSProperties;\n};\n\nfunction EffieLayerPreviewInfo({\n layer,\n index,\n className,\n style,\n}: EffieLayerPreviewInfoProps) {\n return (\n <div className={className} style={style}>\n <strong>Layer {index + 1}</strong> ({layer.type})\n {layer.delay !== undefined && layer.delay > 0 && (\n <div>Delay: {layer.delay}s</div>\n )}\n {layer.from !== undefined && <div>From: {layer.from}s</div>}\n {layer.until !== undefined && <div>Until: {layer.until}s</div>}\n {layer.effects && layer.effects.length > 0 && (\n <EffieJsonBlock label=\"Effects\" data={layer.effects} />\n )}\n {layer.motion && <EffieJsonBlock label=\"Motion\" data={layer.motion} />}\n </div>\n );\n}\n\n// Simple pre-composed Layer Preview\n\nexport type EffieLayerPreviewProps = {\n /** Layer configuration from effie JSON */\n layer: EffieLayer<EffieSources>;\n /** Layer index (0-based) */\n index: number;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** How to stack info relative to media: \"horizontal\" (default, info beside) or \"vertical\" (info below) */\n stacking?: \"vertical\" | \"horizontal\";\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\nfunction EffieLayerPreviewSimple({\n layer,\n index,\n resolveSource,\n resolution,\n stacking = \"horizontal\",\n className,\n style,\n}: EffieLayerPreviewProps) {\n return (\n <EffieLayerPreviewRoot\n className={className}\n style={{\n display: \"flex\",\n flexDirection: stacking === \"vertical\" ? \"column\" : \"row\",\n gap: \"1rem\",\n ...style,\n }}\n >\n <EffieLayerPreviewMedia\n layer={layer}\n index={index}\n resolveSource={resolveSource}\n resolution={resolution}\n style={{\n border: \"1px solid #ddd\",\n }}\n />\n <EffieLayerPreviewInfo\n layer={layer}\n index={index}\n style={{\n fontSize: \"0.85rem\",\n color: \"#666\",\n }}\n />\n </EffieLayerPreviewRoot>\n );\n}\n\n/**\n * Displays a preview of a single layer, including its metadata.\n * Uses AnniePlayer for animation layers.\n *\n * Usage:\n * - Simple: `<EffieLayerPreview layer={...} index={...} resolveSource={...} />`\n * - Compound: `<EffieLayerPreview.Root>...</EffieLayerPreview.Root>`\n */\nexport const EffieLayerPreview = Object.assign(EffieLayerPreviewSimple, {\n Root: EffieLayerPreviewRoot,\n Media: EffieLayerPreviewMedia,\n Info: EffieLayerPreviewInfo,\n});\n\n// ============ Segment Preview - Compound Components ============\n\ntype EffieSegmentPreviewRootProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieSegmentPreviewRoot({\n className,\n style,\n children,\n}: EffieSegmentPreviewRootProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieSegmentPreviewHeaderProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieSegmentPreviewHeader({\n className,\n style,\n children,\n}: EffieSegmentPreviewHeaderProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieSegmentPreviewLayersProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieSegmentPreviewLayers({\n className,\n style,\n children,\n}: EffieSegmentPreviewLayersProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\n// Simple pre-composed Segment Preview\n\nexport type EffieSegmentPreviewProps = {\n /** Segment configuration from effie JSON */\n segment: EffieSegment<EffieSources>;\n /** Segment index (0-based) */\n index: number;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** How to stack layers: \"vertical\" (default) or \"horizontal\" */\n stacking?: \"vertical\" | \"horizontal\";\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\nfunction EffieSegmentPreviewSimple({\n segment,\n index,\n resolveSource,\n resolution,\n stacking = \"vertical\",\n className,\n style,\n}: EffieSegmentPreviewProps) {\n const layerStacking = stacking === \"horizontal\" ? \"vertical\" : \"horizontal\";\n\n return (\n <EffieSegmentPreviewRoot\n className={className}\n style={{\n padding: \"1rem\",\n border: \"1px solid #ddd\",\n borderRadius: 8,\n backgroundColor: \"#fafafa\",\n ...style,\n }}\n >\n <EffieSegmentPreviewHeader\n style={{\n fontWeight: 600,\n marginBottom: \"1rem\",\n }}\n >\n <strong>Segment {index + 1}</strong>\n {\" — \"}\n {segment.duration}s\n {segment.transition && (\n <span>\n {\" \"}\n (transition: {segment.transition.type},{\" \"}\n {segment.transition.duration}\n s)\n </span>\n )}\n </EffieSegmentPreviewHeader>\n\n <EffieSegmentPreviewLayers\n style={{\n display: \"flex\",\n flexDirection: stacking === \"horizontal\" ? \"row\" : \"column\",\n flexWrap: stacking === \"horizontal\" ? \"wrap\" : undefined,\n gap: \"1rem\",\n }}\n >\n {segment.layers.map((layer, j) => (\n <EffieLayerPreviewSimple\n key={j}\n layer={layer}\n index={j}\n resolveSource={resolveSource}\n resolution={resolution}\n stacking={layerStacking}\n />\n ))}\n </EffieSegmentPreviewLayers>\n </EffieSegmentPreviewRoot>\n );\n}\n\n/**\n * Displays a preview of a segment with all its layers.\n *\n * Usage:\n * - Simple: `<EffieSegmentPreview segment={...} index={...} resolveSource={...} />`\n * - Compound: `<EffieSegmentPreview.Root>...</EffieSegmentPreview.Root>`\n */\nexport const EffieSegmentPreview = Object.assign(EffieSegmentPreviewSimple, {\n Root: EffieSegmentPreviewRoot,\n Header: EffieSegmentPreviewHeader,\n Layers: EffieSegmentPreviewLayers,\n});\n\n// ============ Validation Errors ============\n\nexport type EffieValidationErrorsProps = {\n /** Error message (e.g., \"Invalid effie data\") */\n error: string;\n /** List of validation issues */\n issues?: EffieValidationIssue[];\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\n/**\n * Displays validation errors with detailed issue breakdown.\n * Simple component with built-in inline styles.\n */\nexport function EffieValidationErrors({\n error,\n issues,\n className,\n style,\n}: EffieValidationErrorsProps) {\n return (\n <div\n className={className}\n style={{\n padding: \"1rem\",\n backgroundColor: \"#fff5f5\",\n border: \"1px solid #ffcccc\",\n borderRadius: 8,\n ...style,\n }}\n >\n <div\n style={{\n color: \"red\",\n fontWeight: 600,\n }}\n >\n Error: {error}\n </div>\n {issues && issues.length > 0 && (\n <ul\n style={{\n margin: \"0.5rem 0\",\n paddingLeft: \"1.5rem\",\n }}\n >\n {issues.map((issue, i) => (\n <li\n key={i}\n style={{\n marginBottom: \"0.25rem\",\n }}\n >\n <code>{issue.path || \"(root)\"}</code>: {issue.message}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\n// ============ Internal Components ============\n\ntype EffieJsonBlockProps = {\n label: string;\n data: unknown;\n};\n\nfunction EffieJsonBlock({ label, data }: EffieJsonBlockProps) {\n return (\n <div>\n <div>{label}:</div>\n <pre\n style={{\n margin: \"4px 0\",\n fontSize: \"0.75rem\",\n backgroundColor: \"#f0f0f0\",\n padding: \"4px 8px\",\n borderRadius: 4,\n overflow: \"auto\",\n }}\n >\n {JSON.stringify(data, null, 2)}\n </pre>\n </div>\n );\n}\n\n// ============ Warmup Hook ============\n\nexport type UseEffieWarmupResult = {\n state: EffieWarmupState;\n isReady: boolean;\n isWarming: boolean;\n hasError: boolean;\n};\n\n/**\n * Hook to connect to a warmup SSE stream and track progress.\n *\n * @param streamUrl - The full SSE stream URL returned by the FFS server (null if FFS not configured)\n */\nexport function useEffieWarmup(streamUrl: string | null): UseEffieWarmupResult {\n const [state, setState] = useState<EffieWarmupState>({\n status: \"idle\",\n total: 0,\n cached: 0,\n failed: 0,\n skipped: 0,\n downloading: new Map(),\n });\n\n const cleanupRef = useRef<(() => void) | null>(null);\n\n useEffect(() => {\n if (!streamUrl) {\n return;\n }\n\n setState({\n status: \"connecting\",\n total: 0,\n cached: 0,\n failed: 0,\n skipped: 0,\n downloading: new Map(),\n startTime: Date.now(),\n });\n\n // streamUrl is now a full URL returned by the FFS server\n cleanupRef.current = connectEffieWarmupStream(streamUrl, (event) => {\n setState((prev) => {\n switch (event.type) {\n case \"start\":\n return { ...prev, status: \"warming\", total: event.data.total };\n\n case \"progress\": {\n const newDownloading = new Map(prev.downloading);\n newDownloading.delete(hashUrlToId(event.data.url));\n return {\n ...prev,\n cached: event.data.cached,\n failed: event.data.failed,\n skipped: event.data.skipped,\n downloading: newDownloading,\n };\n }\n\n case \"downloading\": {\n const newDownloading = new Map(prev.downloading);\n newDownloading.set(hashUrlToId(event.data.url), {\n url: event.data.url,\n bytesReceived: event.data.bytesReceived,\n });\n return { ...prev, downloading: newDownloading };\n }\n\n case \"keepalive\":\n case \"summary\":\n return {\n ...prev,\n cached: event.data.cached,\n failed: event.data.failed,\n skipped: event.data.skipped,\n };\n\n case \"complete\":\n return { ...prev, status: \"ready\", endTime: Date.now() };\n\n case \"error\":\n return { ...prev, status: \"error\", error: event.data.message };\n\n default:\n return prev;\n }\n });\n });\n\n return () => {\n cleanupRef.current?.();\n cleanupRef.current = null;\n };\n }, [streamUrl]);\n\n return {\n state,\n isReady: state.status === \"ready\",\n isWarming: state.status === \"warming\" || state.status === \"connecting\",\n hasError: state.status === \"error\",\n };\n}\n","import type { WarmupEventMap } from \"@effing/ffs/sse\";\n\n// ============ Types ============\n\n/**\n * Union of all warmup SSE event types, derived from the server-side\n * `WarmupEventMap` so the client stays in sync automatically.\n * Each variant is `{ type: K; data: WarmupEventMap[K] }`.\n */\nexport type EffieWarmupEvent = {\n [K in keyof WarmupEventMap & string]: { type: K; data: WarmupEventMap[K] };\n}[keyof WarmupEventMap & string];\n\n/** Current warmup state */\nexport type EffieWarmupState = {\n status: \"idle\" | \"connecting\" | \"warming\" | \"ready\" | \"error\";\n total: number;\n cached: number;\n failed: number;\n skipped: number;\n downloading: Map<string, { url: string; bytesReceived: number }>;\n error?: string;\n startTime?: number;\n endTime?: number;\n};\n\n// ============ SSE Connection ============\n\n/**\n * Connects to an SSE warmup stream and calls onEvent for each event.\n * Returns a cleanup function to close the connection.\n *\n * @param streamUrl - Full URL to the warmup SSE endpoint\n * @param onEvent - Callback for each SSE event\n */\nexport function connectEffieWarmupStream(\n streamUrl: string,\n onEvent: (event: EffieWarmupEvent) => void,\n): () => void {\n const eventSource = new EventSource(streamUrl);\n\n eventSource.addEventListener(\"start\", (e) => {\n onEvent({ type: \"start\", data: JSON.parse((e as MessageEvent).data) });\n });\n\n eventSource.addEventListener(\"progress\", (e) => {\n onEvent({ type: \"progress\", data: JSON.parse((e as MessageEvent).data) });\n });\n\n eventSource.addEventListener(\"downloading\", (e) => {\n onEvent({\n type: \"downloading\",\n data: JSON.parse((e as MessageEvent).data),\n });\n });\n\n eventSource.addEventListener(\"keepalive\", (e) => {\n onEvent({ type: \"keepalive\", data: JSON.parse((e as MessageEvent).data) });\n });\n\n eventSource.addEventListener(\"summary\", (e) => {\n onEvent({ type: \"summary\", data: JSON.parse((e as MessageEvent).data) });\n });\n\n eventSource.addEventListener(\"complete\", (e) => {\n onEvent({ type: \"complete\", data: JSON.parse((e as MessageEvent).data) });\n eventSource.close();\n });\n\n eventSource.addEventListener(\"error\", () => {\n onEvent({ type: \"error\", data: { message: \"Connection lost\" } });\n eventSource.close();\n });\n\n return () => eventSource.close();\n}\n","/**\n * Stable, sync ID for a URL for use as a Map key.\n * (We don't want to use the full URL as a Map key because it can be huge.)\n */\nexport function hashUrlToId(url: string): string {\n // FNV-1a 32-bit\n let hash = 0x811c9dc5;\n for (let i = 0; i < url.length; i++) {\n hash ^= url.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n // Convert to unsigned + compact string\n return (hash >>> 0).toString(36);\n}\n","import { useEffect, useRef, useState } from \"react\";\n\nexport type UseVideoStreamResult = {\n videoRef: React.RefObject<HTMLVideoElement | null>;\n isFullyBuffered: boolean;\n};\n\n/**\n * Streams a video URL into a `<video>` element using MediaSource Extensions.\n * Falls back to a blob URL if MSE is unavailable or fails mid-stream.\n *\n * This avoids follow-up network requests on seek, which is critical for\n * one-time-consumption URLs like FFS render endpoints.\n */\nexport function useVideoStream(url: string | null): UseVideoStreamResult {\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const [isFullyBuffered, setIsFullyBuffered] = useState(false);\n const lastUrlRef = useRef<string | null>(null);\n\n // Reset buffered state when URL changes\n if (url !== lastUrlRef.current) {\n lastUrlRef.current = url;\n if (isFullyBuffered) setIsFullyBuffered(false);\n }\n\n useEffect(() => {\n if (!url) return;\n const video = videoRef.current;\n if (!video) return;\n\n const controller = new AbortController();\n const objectUrls: string[] = [];\n\n // ManagedMediaSource is Safari's preferred MSE API\n const MS =\n typeof window !== \"undefined\" && \"ManagedMediaSource\" in window\n ? (window as unknown as { ManagedMediaSource: typeof MediaSource })\n .ManagedMediaSource\n : typeof MediaSource !== \"undefined\"\n ? MediaSource\n : null;\n const mimeCodec = 'video/mp4; codecs=\"avc1.42E01E,mp4a.40.2\"';\n const canMSE = MS && MS.isTypeSupported(mimeCodec);\n\n const setBlobSrc = (chunks: Uint8Array[]) => {\n if (controller.signal.aborted) return;\n const blob = new Blob(chunks as BlobPart[], { type: \"video/mp4\" });\n const blobUrl = URL.createObjectURL(blob);\n objectUrls.push(blobUrl);\n video.src = blobUrl;\n };\n\n const streamVideo = async (\n sourceBuffer: SourceBuffer | null,\n mediaSource: MediaSource | null,\n msUrl: string | null,\n ) => {\n const chunks: Uint8Array[] = [];\n let mseFailed = !sourceBuffer;\n\n let response: Response;\n try {\n response = await fetch(url, { signal: controller.signal });\n } catch {\n return;\n }\n if (!response.ok || !response.body) return;\n\n const reader = response.body.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n if (sourceBuffer && !mseFailed) {\n try {\n await new Promise<void>((resolve, reject) => {\n const onDone = () => {\n sourceBuffer.removeEventListener(\"error\", onError);\n resolve();\n };\n const onError = () => {\n sourceBuffer.removeEventListener(\"updateend\", onDone);\n reject(new Error(\"SourceBuffer append failed\"));\n };\n sourceBuffer.addEventListener(\"updateend\", onDone, {\n once: true,\n });\n sourceBuffer.addEventListener(\"error\", onError, {\n once: true,\n });\n sourceBuffer.appendBuffer(value as BufferSource);\n });\n } catch (e) {\n console.warn(\"MSE: append failed, will use blob fallback\", e);\n mseFailed = true;\n }\n }\n }\n } catch {\n if (controller.signal.aborted) return;\n }\n\n if (controller.signal.aborted) return;\n\n if (mseFailed) {\n if (msUrl) {\n URL.revokeObjectURL(msUrl);\n }\n setBlobSrc(chunks);\n } else if (mediaSource && mediaSource.readyState === \"open\") {\n mediaSource.endOfStream();\n }\n\n setIsFullyBuffered(true);\n };\n\n if (canMSE) {\n const mediaSource = new MS();\n const msUrl = URL.createObjectURL(mediaSource);\n objectUrls.push(msUrl);\n video.src = msUrl;\n\n mediaSource.addEventListener(\"sourceopen\", () => {\n let sourceBuffer: SourceBuffer | null = null;\n try {\n sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);\n } catch (e) {\n console.warn(\"MSE: addSourceBuffer failed\", e);\n }\n streamVideo(sourceBuffer, mediaSource, msUrl);\n });\n } else {\n streamVideo(null, null, null);\n }\n\n return () => {\n controller.abort();\n objectUrls.forEach((u) => URL.revokeObjectURL(u));\n };\n }, [url]);\n\n return { videoRef, isFullyBuffered };\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","useRef","useEffect","useState"]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/index.tsx","../../src/warmup.ts","../../src/utils.ts","../../src/react/use-video-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport type {\n EffieBackground,\n EffieSegment,\n EffieLayer,\n EffieSources,\n EffieWebUrl,\n} from \"@effing/effie\";\nimport { AnniePlayer } from \"@effing/annie-player/react\";\nimport type { EffieSourceResolver, EffieValidationIssue } from \"../core\";\nimport { connectEffieWarmupStream, type EffieWarmupState } from \"../warmup\";\nimport { hashUrlToId } from \"../utils\";\nexport { useVideoStream } from \"./use-video-stream\";\nexport type { UseVideoStreamResult } from \"./use-video-stream\";\nimport { useVideoStream } from \"./use-video-stream\";\n\n// ============ Video Preview ============\n\nexport type EffieVideoPreviewProps = {\n /** Video URL to stream via MSE */\n url: string;\n /** Optional poster image URL */\n poster?: string;\n /** Callback when video starts playing */\n onPlay?: () => void;\n /** Callback when video is fully buffered (entire video downloaded) */\n onFullyBuffered?: (blob: Blob) => void;\n /** Class name for the video element */\n className?: string;\n /** Style for the video element */\n style?: React.CSSProperties;\n};\n\n/**\n * Streams a video via MSE (with blob fallback) so the entire file is buffered\n * in memory. This avoids follow-up network requests on seek, which is critical\n * for one-time-consumption URLs like FFS render endpoints.\n */\nexport function EffieVideoPreview({\n url,\n poster,\n onPlay,\n onFullyBuffered,\n className,\n style,\n}: EffieVideoPreviewProps) {\n const { videoRef, isFullyBuffered, blobRef } = useVideoStream(url);\n const firedRef = useRef(false);\n\n // Reset fired flag when URL changes\n const lastUrlRef = useRef(url);\n if (url !== lastUrlRef.current) {\n lastUrlRef.current = url;\n firedRef.current = false;\n }\n\n useEffect(() => {\n if (\n isFullyBuffered &&\n onFullyBuffered &&\n !firedRef.current &&\n blobRef.current\n ) {\n firedRef.current = true;\n onFullyBuffered(blobRef.current);\n }\n }, [isFullyBuffered, onFullyBuffered]);\n\n return (\n <video\n ref={videoRef}\n poster={poster}\n crossOrigin=\"anonymous\"\n className={className}\n style={style}\n controls\n autoPlay\n onPlay={onPlay}\n />\n );\n}\n\n// ============ Cover Preview ============\n\nexport type EffieCoverPreviewProps = {\n /** Cover image URL */\n cover: EffieWebUrl;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** Optional video URL to show instead of cover image (e.g., after rendering) */\n video?: string | null;\n /** Callback when video starts playing */\n onPlay?: () => void;\n /** Callback when video is fully buffered (entire video downloaded) */\n onFullyBuffered?: (blob: Blob) => void;\n /** Class name for the img/video element */\n className?: string;\n /** Style for the img/video element */\n style?: React.CSSProperties;\n};\n\n/**\n * Displays the effie cover image, or a video if provided.\n * When a video URL is given, renders an {@link EffieVideoPreview} with the\n * cover as poster image. Otherwise renders a static `<img>`.\n */\nexport function EffieCoverPreview({\n cover,\n resolution,\n video,\n onPlay,\n onFullyBuffered,\n className,\n style,\n}: EffieCoverPreviewProps) {\n if (video) {\n return (\n <EffieVideoPreview\n url={video}\n poster={cover}\n onPlay={onPlay}\n onFullyBuffered={onFullyBuffered}\n className={className}\n style={{ ...style, height: resolution.height }}\n />\n );\n }\n\n return (\n <img\n src={cover}\n alt=\"Cover\"\n className={className}\n style={{ ...style, height: resolution.height }}\n />\n );\n}\n\n// ============ Background Preview - Compound Components ============\n\ntype EffieBackgroundPreviewRootProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieBackgroundPreviewRoot({\n className,\n style,\n children,\n}: EffieBackgroundPreviewRootProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieBackgroundPreviewMediaProps = {\n /** Background configuration from effie JSON */\n background: EffieBackground<EffieSources>;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Class name for the media element */\n className?: string;\n /** Style for the media element */\n style?: React.CSSProperties;\n};\n\nfunction EffieBackgroundPreviewMedia({\n background,\n resolveSource,\n className,\n style,\n}: EffieBackgroundPreviewMediaProps) {\n if (background.type === \"color\") {\n return (\n <div\n className={className}\n style={{ ...style, backgroundColor: background.color }}\n />\n );\n }\n\n if (background.type === \"image\") {\n return (\n <img\n src={resolveSource(background.source)}\n alt=\"Background\"\n className={className}\n style={{ objectFit: \"cover\", ...style }}\n />\n );\n }\n\n if (background.type === \"video\") {\n return (\n <video\n src={resolveSource(background.source)}\n className={className}\n style={{ objectFit: \"cover\", ...style }}\n autoPlay\n loop\n muted\n />\n );\n }\n\n return null;\n}\n\ntype EffieBackgroundPreviewInfoProps = {\n /** Background configuration from effie JSON */\n background: EffieBackground<EffieSources>;\n /** Class name for the info section */\n className?: string;\n /** Style for the info section */\n style?: React.CSSProperties;\n};\n\nfunction EffieBackgroundPreviewInfo({\n background,\n className,\n style,\n}: EffieBackgroundPreviewInfoProps) {\n return (\n <div className={className} style={style}>\n <strong>Type:</strong> {background.type}\n {background.type === \"color\" && (\n <div>\n <strong>Color:</strong> {background.color}\n </div>\n )}\n </div>\n );\n}\n\n// Simple pre-composed Background Preview\n\nexport type EffieBackgroundPreviewProps = {\n /** Background configuration from effie JSON */\n background: EffieBackground<EffieSources>;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\nfunction EffieBackgroundPreviewSimple({\n background,\n resolveSource,\n resolution,\n className,\n style,\n}: EffieBackgroundPreviewProps) {\n return (\n <EffieBackgroundPreviewRoot\n className={className}\n style={{\n display: \"flex\",\n gap: \"1rem\",\n ...style,\n }}\n >\n <EffieBackgroundPreviewMedia\n background={background}\n resolveSource={resolveSource}\n style={{\n border: \"1px solid #ddd\",\n width: resolution.width,\n height: resolution.height,\n }}\n />\n <EffieBackgroundPreviewInfo\n background={background}\n style={{\n fontSize: \"0.85rem\",\n color: \"#666\",\n }}\n />\n </EffieBackgroundPreviewRoot>\n );\n}\n\n/**\n * Displays a preview of the effie background (color, image, or video).\n *\n * Usage:\n * - Simple: `<EffieBackgroundPreview background={...} resolveSource={...} />`\n * - Compound: `<EffieBackgroundPreview.Root>...</EffieBackgroundPreview.Root>`\n */\nexport const EffieBackgroundPreview = Object.assign(\n EffieBackgroundPreviewSimple,\n {\n Root: EffieBackgroundPreviewRoot,\n Media: EffieBackgroundPreviewMedia,\n Info: EffieBackgroundPreviewInfo,\n },\n);\n\n// ============ Layer Preview - Compound Components ============\n\ntype EffieLayerPreviewRootProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieLayerPreviewRoot({\n className,\n style,\n children,\n}: EffieLayerPreviewRootProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieLayerPreviewMediaProps = {\n /** Layer configuration from effie JSON */\n layer: EffieLayer<EffieSources>;\n /** Layer index (0-based) for alt text */\n index: number;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** Class name for the media element */\n className?: string;\n /** Style for the media element */\n style?: React.CSSProperties;\n};\n\nfunction EffieLayerPreviewMedia({\n layer,\n index,\n resolveSource,\n resolution,\n className,\n style,\n}: EffieLayerPreviewMediaProps) {\n if (layer.type === \"animation\") {\n return (\n <AnniePlayer\n src={resolveSource(layer.source)}\n height={resolution.height}\n defaultWidth={resolution.width}\n autoLoad={false}\n autoPlay={true}\n className={className}\n style={style}\n />\n );\n }\n\n return (\n <img\n src={resolveSource(layer.source)}\n alt={`Layer ${index + 1}`}\n className={className}\n style={{ ...style, height: resolution.height }}\n />\n );\n}\n\ntype EffieLayerPreviewInfoProps = {\n /** Layer configuration from effie JSON */\n layer: EffieLayer<EffieSources>;\n /** Layer index (0-based) */\n index: number;\n /** Class name for the info section */\n className?: string;\n /** Style for the info section */\n style?: React.CSSProperties;\n};\n\nfunction EffieLayerPreviewInfo({\n layer,\n index,\n className,\n style,\n}: EffieLayerPreviewInfoProps) {\n return (\n <div className={className} style={style}>\n <strong>Layer {index + 1}</strong> ({layer.type})\n {layer.delay !== undefined && layer.delay > 0 && (\n <div>Delay: {layer.delay}s</div>\n )}\n {layer.from !== undefined && <div>From: {layer.from}s</div>}\n {layer.until !== undefined && <div>Until: {layer.until}s</div>}\n {layer.effects && layer.effects.length > 0 && (\n <EffieJsonBlock label=\"Effects\" data={layer.effects} />\n )}\n {layer.motion && <EffieJsonBlock label=\"Motion\" data={layer.motion} />}\n </div>\n );\n}\n\n// Simple pre-composed Layer Preview\n\nexport type EffieLayerPreviewProps = {\n /** Layer configuration from effie JSON */\n layer: EffieLayer<EffieSources>;\n /** Layer index (0-based) */\n index: number;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** How to stack info relative to media: \"horizontal\" (default, info beside) or \"vertical\" (info below) */\n stacking?: \"vertical\" | \"horizontal\";\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\nfunction EffieLayerPreviewSimple({\n layer,\n index,\n resolveSource,\n resolution,\n stacking = \"horizontal\",\n className,\n style,\n}: EffieLayerPreviewProps) {\n return (\n <EffieLayerPreviewRoot\n className={className}\n style={{\n display: \"flex\",\n flexDirection: stacking === \"vertical\" ? \"column\" : \"row\",\n gap: \"1rem\",\n ...style,\n }}\n >\n <EffieLayerPreviewMedia\n layer={layer}\n index={index}\n resolveSource={resolveSource}\n resolution={resolution}\n style={{\n border: \"1px solid #ddd\",\n }}\n />\n <EffieLayerPreviewInfo\n layer={layer}\n index={index}\n style={{\n fontSize: \"0.85rem\",\n color: \"#666\",\n }}\n />\n </EffieLayerPreviewRoot>\n );\n}\n\n/**\n * Displays a preview of a single layer, including its metadata.\n * Uses AnniePlayer for animation layers.\n *\n * Usage:\n * - Simple: `<EffieLayerPreview layer={...} index={...} resolveSource={...} />`\n * - Compound: `<EffieLayerPreview.Root>...</EffieLayerPreview.Root>`\n */\nexport const EffieLayerPreview = Object.assign(EffieLayerPreviewSimple, {\n Root: EffieLayerPreviewRoot,\n Media: EffieLayerPreviewMedia,\n Info: EffieLayerPreviewInfo,\n});\n\n// ============ Segment Preview - Compound Components ============\n\ntype EffieSegmentPreviewRootProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieSegmentPreviewRoot({\n className,\n style,\n children,\n}: EffieSegmentPreviewRootProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieSegmentPreviewHeaderProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieSegmentPreviewHeader({\n className,\n style,\n children,\n}: EffieSegmentPreviewHeaderProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieSegmentPreviewLayersProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieSegmentPreviewLayers({\n className,\n style,\n children,\n}: EffieSegmentPreviewLayersProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\n// Simple pre-composed Segment Preview\n\nexport type EffieSegmentPreviewProps = {\n /** Segment configuration from effie JSON */\n segment: EffieSegment<EffieSources>;\n /** Segment index (0-based) */\n index: number;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** How to stack layers: \"vertical\" (default) or \"horizontal\" */\n stacking?: \"vertical\" | \"horizontal\";\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\nfunction EffieSegmentPreviewSimple({\n segment,\n index,\n resolveSource,\n resolution,\n stacking = \"vertical\",\n className,\n style,\n}: EffieSegmentPreviewProps) {\n const layerStacking = stacking === \"horizontal\" ? \"vertical\" : \"horizontal\";\n\n return (\n <EffieSegmentPreviewRoot\n className={className}\n style={{\n padding: \"1rem\",\n border: \"1px solid #ddd\",\n borderRadius: 8,\n backgroundColor: \"#fafafa\",\n ...style,\n }}\n >\n <EffieSegmentPreviewHeader\n style={{\n fontWeight: 600,\n marginBottom: \"1rem\",\n }}\n >\n <strong>Segment {index + 1}</strong>\n {\" — \"}\n {segment.duration}s\n {segment.transition && (\n <span>\n {\" \"}\n (transition: {segment.transition.type},{\" \"}\n {segment.transition.duration}\n s)\n </span>\n )}\n </EffieSegmentPreviewHeader>\n\n <EffieSegmentPreviewLayers\n style={{\n display: \"flex\",\n flexDirection: stacking === \"horizontal\" ? \"row\" : \"column\",\n flexWrap: stacking === \"horizontal\" ? \"wrap\" : undefined,\n gap: \"1rem\",\n }}\n >\n {segment.layers.map((layer, j) => (\n <EffieLayerPreviewSimple\n key={j}\n layer={layer}\n index={j}\n resolveSource={resolveSource}\n resolution={resolution}\n stacking={layerStacking}\n />\n ))}\n </EffieSegmentPreviewLayers>\n </EffieSegmentPreviewRoot>\n );\n}\n\n/**\n * Displays a preview of a segment with all its layers.\n *\n * Usage:\n * - Simple: `<EffieSegmentPreview segment={...} index={...} resolveSource={...} />`\n * - Compound: `<EffieSegmentPreview.Root>...</EffieSegmentPreview.Root>`\n */\nexport const EffieSegmentPreview = Object.assign(EffieSegmentPreviewSimple, {\n Root: EffieSegmentPreviewRoot,\n Header: EffieSegmentPreviewHeader,\n Layers: EffieSegmentPreviewLayers,\n});\n\n// ============ Validation Errors ============\n\nexport type EffieValidationErrorsProps = {\n /** Error message (e.g., \"Invalid effie data\") */\n error: string;\n /** List of validation issues */\n issues?: EffieValidationIssue[];\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\n/**\n * Displays validation errors with detailed issue breakdown.\n * Simple component with built-in inline styles.\n */\nexport function EffieValidationErrors({\n error,\n issues,\n className,\n style,\n}: EffieValidationErrorsProps) {\n return (\n <div\n className={className}\n style={{\n padding: \"1rem\",\n backgroundColor: \"#fff5f5\",\n border: \"1px solid #ffcccc\",\n borderRadius: 8,\n ...style,\n }}\n >\n <div\n style={{\n color: \"red\",\n fontWeight: 600,\n }}\n >\n Error: {error}\n </div>\n {issues && issues.length > 0 && (\n <ul\n style={{\n margin: \"0.5rem 0\",\n paddingLeft: \"1.5rem\",\n }}\n >\n {issues.map((issue, i) => (\n <li\n key={i}\n style={{\n marginBottom: \"0.25rem\",\n }}\n >\n <code>{issue.path || \"(root)\"}</code>: {issue.message}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\n// ============ Internal Components ============\n\ntype EffieJsonBlockProps = {\n label: string;\n data: unknown;\n};\n\nfunction EffieJsonBlock({ label, data }: EffieJsonBlockProps) {\n return (\n <div>\n <div>{label}:</div>\n <pre\n style={{\n margin: \"4px 0\",\n fontSize: \"0.75rem\",\n backgroundColor: \"#f0f0f0\",\n padding: \"4px 8px\",\n borderRadius: 4,\n overflow: \"auto\",\n }}\n >\n {JSON.stringify(data, null, 2)}\n </pre>\n </div>\n );\n}\n\n// ============ Warmup Hook ============\n\nexport type UseEffieWarmupResult = {\n state: EffieWarmupState;\n isReady: boolean;\n isWarming: boolean;\n hasError: boolean;\n};\n\n/**\n * Hook to connect to a warmup SSE stream and track progress.\n *\n * @param streamUrl - The full SSE stream URL returned by the FFS server (null if FFS not configured)\n */\nexport function useEffieWarmup(streamUrl: string | null): UseEffieWarmupResult {\n const [state, setState] = useState<EffieWarmupState>({\n status: \"idle\",\n total: 0,\n cached: 0,\n failed: 0,\n skipped: 0,\n downloading: new Map(),\n });\n\n const cleanupRef = useRef<(() => void) | null>(null);\n\n useEffect(() => {\n if (!streamUrl) {\n return;\n }\n\n setState({\n status: \"connecting\",\n total: 0,\n cached: 0,\n failed: 0,\n skipped: 0,\n downloading: new Map(),\n startTime: Date.now(),\n });\n\n // streamUrl is now a full URL returned by the FFS server\n cleanupRef.current = connectEffieWarmupStream(streamUrl, (event) => {\n setState((prev) => {\n switch (event.type) {\n case \"start\":\n return { ...prev, status: \"warming\", total: event.data.total };\n\n case \"progress\": {\n const newDownloading = new Map(prev.downloading);\n newDownloading.delete(hashUrlToId(event.data.url));\n return {\n ...prev,\n cached: event.data.cached,\n failed: event.data.failed,\n skipped: event.data.skipped,\n downloading: newDownloading,\n };\n }\n\n case \"downloading\": {\n const newDownloading = new Map(prev.downloading);\n newDownloading.set(hashUrlToId(event.data.url), {\n url: event.data.url,\n bytesReceived: event.data.bytesReceived,\n });\n return { ...prev, downloading: newDownloading };\n }\n\n case \"keepalive\":\n case \"summary\":\n return {\n ...prev,\n cached: event.data.cached,\n failed: event.data.failed,\n skipped: event.data.skipped,\n };\n\n case \"complete\":\n return { ...prev, status: \"ready\", endTime: Date.now() };\n\n case \"error\":\n return { ...prev, status: \"error\", error: event.data.message };\n\n default:\n return prev;\n }\n });\n });\n\n return () => {\n cleanupRef.current?.();\n cleanupRef.current = null;\n };\n }, [streamUrl]);\n\n return {\n state,\n isReady: state.status === \"ready\",\n isWarming: state.status === \"warming\" || state.status === \"connecting\",\n hasError: state.status === \"error\",\n };\n}\n","import type { WarmupEventMap } from \"@effing/ffs/sse\";\n\n// ============ Types ============\n\n/**\n * Union of all warmup SSE event types, derived from the server-side\n * `WarmupEventMap` so the client stays in sync automatically.\n * Each variant is `{ type: K; data: WarmupEventMap[K] }`.\n */\nexport type EffieWarmupEvent = {\n [K in keyof WarmupEventMap & string]: { type: K; data: WarmupEventMap[K] };\n}[keyof WarmupEventMap & string];\n\n/** Current warmup state */\nexport type EffieWarmupState = {\n status: \"idle\" | \"connecting\" | \"warming\" | \"ready\" | \"error\";\n total: number;\n cached: number;\n failed: number;\n skipped: number;\n downloading: Map<string, { url: string; bytesReceived: number }>;\n error?: string;\n startTime?: number;\n endTime?: number;\n};\n\n// ============ SSE Connection ============\n\n/**\n * Connects to an SSE warmup stream and calls onEvent for each event.\n * Returns a cleanup function to close the connection.\n *\n * @param streamUrl - Full URL to the warmup SSE endpoint\n * @param onEvent - Callback for each SSE event\n */\nexport function connectEffieWarmupStream(\n streamUrl: string,\n onEvent: (event: EffieWarmupEvent) => void,\n): () => void {\n const eventSource = new EventSource(streamUrl);\n\n eventSource.addEventListener(\"start\", (e) => {\n onEvent({ type: \"start\", data: JSON.parse((e as MessageEvent).data) });\n });\n\n eventSource.addEventListener(\"progress\", (e) => {\n onEvent({ type: \"progress\", data: JSON.parse((e as MessageEvent).data) });\n });\n\n eventSource.addEventListener(\"downloading\", (e) => {\n onEvent({\n type: \"downloading\",\n data: JSON.parse((e as MessageEvent).data),\n });\n });\n\n eventSource.addEventListener(\"keepalive\", (e) => {\n onEvent({ type: \"keepalive\", data: JSON.parse((e as MessageEvent).data) });\n });\n\n eventSource.addEventListener(\"summary\", (e) => {\n onEvent({ type: \"summary\", data: JSON.parse((e as MessageEvent).data) });\n });\n\n eventSource.addEventListener(\"complete\", (e) => {\n onEvent({ type: \"complete\", data: JSON.parse((e as MessageEvent).data) });\n eventSource.close();\n });\n\n eventSource.addEventListener(\"error\", () => {\n onEvent({ type: \"error\", data: { message: \"Connection lost\" } });\n eventSource.close();\n });\n\n return () => eventSource.close();\n}\n","/**\n * Stable, sync ID for a URL for use as a Map key.\n * (We don't want to use the full URL as a Map key because it can be huge.)\n */\nexport function hashUrlToId(url: string): string {\n // FNV-1a 32-bit\n let hash = 0x811c9dc5;\n for (let i = 0; i < url.length; i++) {\n hash ^= url.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n // Convert to unsigned + compact string\n return (hash >>> 0).toString(36);\n}\n","import { useEffect, useRef, useState } from \"react\";\n\nexport type UseVideoStreamResult = {\n videoRef: React.RefObject<HTMLVideoElement | null>;\n isFullyBuffered: boolean;\n blobRef: React.RefObject<Blob | null>;\n};\n\n/**\n * Streams a video URL into a `<video>` element using MediaSource Extensions.\n * Falls back to a blob URL if MSE is unavailable or fails mid-stream.\n *\n * This avoids follow-up network requests on seek, which is critical for\n * one-time-consumption URLs like FFS render endpoints.\n */\nexport function useVideoStream(url: string | null): UseVideoStreamResult {\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const blobRef = useRef<Blob | null>(null);\n const [isFullyBuffered, setIsFullyBuffered] = useState(false);\n const lastUrlRef = useRef<string | null>(null);\n\n // Reset buffered state when URL changes\n if (url !== lastUrlRef.current) {\n lastUrlRef.current = url;\n if (isFullyBuffered) setIsFullyBuffered(false);\n blobRef.current = null;\n }\n\n useEffect(() => {\n if (!url) return;\n const video = videoRef.current;\n if (!video) return;\n\n const controller = new AbortController();\n const objectUrls: string[] = [];\n\n // ManagedMediaSource is Safari's preferred MSE API\n const MS =\n typeof window !== \"undefined\" && \"ManagedMediaSource\" in window\n ? (window as unknown as { ManagedMediaSource: typeof MediaSource })\n .ManagedMediaSource\n : typeof MediaSource !== \"undefined\"\n ? MediaSource\n : null;\n const mimeCodec = 'video/mp4; codecs=\"avc1.42E01E,mp4a.40.2\"';\n const canMSE = MS && MS.isTypeSupported(mimeCodec);\n\n const setBlobSrc = (chunks: Uint8Array[]) => {\n if (controller.signal.aborted) return;\n const blob = new Blob(chunks as BlobPart[], { type: \"video/mp4\" });\n const blobUrl = URL.createObjectURL(blob);\n objectUrls.push(blobUrl);\n video.src = blobUrl;\n };\n\n const streamVideo = async (\n sourceBuffer: SourceBuffer | null,\n mediaSource: MediaSource | null,\n msUrl: string | null,\n ) => {\n const chunks: Uint8Array[] = [];\n let mseFailed = !sourceBuffer;\n\n let response: Response;\n try {\n response = await fetch(url, { signal: controller.signal });\n } catch {\n return;\n }\n if (!response.ok || !response.body) return;\n\n const reader = response.body.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n if (sourceBuffer && !mseFailed) {\n try {\n await new Promise<void>((resolve, reject) => {\n const onDone = () => {\n sourceBuffer.removeEventListener(\"error\", onError);\n resolve();\n };\n const onError = () => {\n sourceBuffer.removeEventListener(\"updateend\", onDone);\n reject(new Error(\"SourceBuffer append failed\"));\n };\n sourceBuffer.addEventListener(\"updateend\", onDone, {\n once: true,\n });\n sourceBuffer.addEventListener(\"error\", onError, {\n once: true,\n });\n sourceBuffer.appendBuffer(value as BufferSource);\n });\n } catch (e) {\n console.warn(\"MSE: append failed, will use blob fallback\", e);\n mseFailed = true;\n }\n }\n }\n } catch {\n if (controller.signal.aborted) return;\n }\n\n if (controller.signal.aborted) return;\n\n if (mseFailed) {\n if (msUrl) {\n URL.revokeObjectURL(msUrl);\n }\n setBlobSrc(chunks);\n } else if (mediaSource && mediaSource.readyState === \"open\") {\n mediaSource.endOfStream();\n }\n\n blobRef.current = new Blob(chunks as BlobPart[], { type: \"video/mp4\" });\n setIsFullyBuffered(true);\n };\n\n if (canMSE) {\n const mediaSource = new MS();\n const msUrl = URL.createObjectURL(mediaSource);\n objectUrls.push(msUrl);\n video.src = msUrl;\n\n mediaSource.addEventListener(\"sourceopen\", () => {\n let sourceBuffer: SourceBuffer | null = null;\n try {\n sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);\n } catch (e) {\n console.warn(\"MSE: addSourceBuffer failed\", e);\n }\n streamVideo(sourceBuffer, mediaSource, msUrl);\n });\n } else {\n streamVideo(null, null, null);\n }\n\n return () => {\n controller.abort();\n objectUrls.forEach((u) => URL.revokeObjectURL(u));\n };\n }, [url]);\n\n return { videoRef, isFullyBuffered, blobRef };\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","useRef","useEffect","useState"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effing/effie-preview",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Preview components for Effie video compositions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@effing/annie-player": "0.
|
|
21
|
-
"@effing/effie": "0.
|
|
20
|
+
"@effing/annie-player": "0.15.0",
|
|
21
|
+
"@effing/effie": "0.15.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/react": "^19.0.0",
|
|
25
25
|
"tsup": "^8.0.0",
|
|
26
26
|
"typescript": "^5.9.3",
|
|
27
27
|
"vitest": "^3.2.4",
|
|
28
|
-
"@effing/ffs": "0.
|
|
28
|
+
"@effing/ffs": "0.15.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"react": "^18.0.0 || ^19.0.0"
|