@effing/effie-preview 0.13.1 → 0.14.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/README.md +16 -1
- package/dist/react/index.d.ts +36 -2
- package/dist/react/index.js +153 -30
- package/dist/react/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -145,9 +145,24 @@ type EffieSourceResolver = (src: string) => string;
|
|
|
145
145
|
|
|
146
146
|
All preview components support both simple and compound usage patterns.
|
|
147
147
|
|
|
148
|
+
#### `EffieVideoPreview`
|
|
149
|
+
|
|
150
|
+
Standalone video player that streams a video via MSE (with blob fallback) so the entire file is buffered in memory. This avoids follow-up network requests on seek, which is critical for one-time-consumption URLs like FFS render endpoints.
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
<EffieVideoPreview
|
|
154
|
+
url={string} // Video URL to stream
|
|
155
|
+
poster={string} // Optional poster image URL
|
|
156
|
+
onPlay={() => void} // Callback when video starts playing
|
|
157
|
+
onFullyBuffered={() => void} // Callback when video is fully buffered
|
|
158
|
+
className={string} // Class name for the video element
|
|
159
|
+
style={CSSProperties} // Style for the video element
|
|
160
|
+
/>
|
|
161
|
+
```
|
|
162
|
+
|
|
148
163
|
#### `EffieCoverPreview`
|
|
149
164
|
|
|
150
|
-
Display the cover image, or a video player if a rendered video is available.
|
|
165
|
+
Display the cover image, or a video player if a rendered video is available. Uses `EffieVideoPreview` internally when a video URL is provided.
|
|
151
166
|
|
|
152
167
|
```tsx
|
|
153
168
|
<EffieCoverPreview
|
package/dist/react/index.d.ts
CHANGED
|
@@ -3,6 +3,39 @@ import { EffieWebUrl, EffieBackground, EffieSources, EffieLayer, EffieSegment }
|
|
|
3
3
|
import { EffieSourceResolver, EffieValidationIssue, EffieWarmupState } from '../index.js';
|
|
4
4
|
import '@effing/ffs/sse';
|
|
5
5
|
|
|
6
|
+
type UseVideoStreamResult = {
|
|
7
|
+
videoRef: React.RefObject<HTMLVideoElement | null>;
|
|
8
|
+
isFullyBuffered: boolean;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Streams a video URL into a `<video>` element using MediaSource Extensions.
|
|
12
|
+
* Falls back to a blob URL if MSE is unavailable or fails mid-stream.
|
|
13
|
+
*
|
|
14
|
+
* This avoids follow-up network requests on seek, which is critical for
|
|
15
|
+
* one-time-consumption URLs like FFS render endpoints.
|
|
16
|
+
*/
|
|
17
|
+
declare function useVideoStream(url: string | null): UseVideoStreamResult;
|
|
18
|
+
|
|
19
|
+
type EffieVideoPreviewProps = {
|
|
20
|
+
/** Video URL to stream via MSE */
|
|
21
|
+
url: string;
|
|
22
|
+
/** Optional poster image URL */
|
|
23
|
+
poster?: string;
|
|
24
|
+
/** Callback when video starts playing */
|
|
25
|
+
onPlay?: () => void;
|
|
26
|
+
/** Callback when video is fully buffered (entire video downloaded) */
|
|
27
|
+
onFullyBuffered?: () => void;
|
|
28
|
+
/** Class name for the video element */
|
|
29
|
+
className?: string;
|
|
30
|
+
/** Style for the video element */
|
|
31
|
+
style?: React.CSSProperties;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Streams a video via MSE (with blob fallback) so the entire file is buffered
|
|
35
|
+
* in memory. This avoids follow-up network requests on seek, which is critical
|
|
36
|
+
* for one-time-consumption URLs like FFS render endpoints.
|
|
37
|
+
*/
|
|
38
|
+
declare function EffieVideoPreview({ url, poster, onPlay, onFullyBuffered, className, style, }: EffieVideoPreviewProps): react_jsx_runtime.JSX.Element;
|
|
6
39
|
type EffieCoverPreviewProps = {
|
|
7
40
|
/** Cover image URL */
|
|
8
41
|
cover: EffieWebUrl;
|
|
@@ -24,7 +57,8 @@ type EffieCoverPreviewProps = {
|
|
|
24
57
|
};
|
|
25
58
|
/**
|
|
26
59
|
* Displays the effie cover image, or a video if provided.
|
|
27
|
-
*
|
|
60
|
+
* When a video URL is given, renders an {@link EffieVideoPreview} with the
|
|
61
|
+
* cover as poster image. Otherwise renders a static `<img>`.
|
|
28
62
|
*/
|
|
29
63
|
declare function EffieCoverPreview({ cover, resolution, video, onPlay, onFullyBuffered, className, style, }: EffieCoverPreviewProps): react_jsx_runtime.JSX.Element;
|
|
30
64
|
type EffieBackgroundPreviewRootProps = {
|
|
@@ -227,4 +261,4 @@ type UseEffieWarmupResult = {
|
|
|
227
261
|
*/
|
|
228
262
|
declare function useEffieWarmup(streamUrl: string | null): UseEffieWarmupResult;
|
|
229
263
|
|
|
230
|
-
export { EffieBackgroundPreview, type EffieBackgroundPreviewProps, EffieCoverPreview, type EffieCoverPreviewProps, EffieLayerPreview, type EffieLayerPreviewProps, EffieSegmentPreview, type EffieSegmentPreviewProps, EffieValidationErrors, type EffieValidationErrorsProps, type UseEffieWarmupResult, useEffieWarmup };
|
|
264
|
+
export { EffieBackgroundPreview, type EffieBackgroundPreviewProps, EffieCoverPreview, type EffieCoverPreviewProps, EffieLayerPreview, type EffieLayerPreviewProps, EffieSegmentPreview, type EffieSegmentPreviewProps, EffieValidationErrors, type EffieValidationErrorsProps, EffieVideoPreview, type EffieVideoPreviewProps, type UseEffieWarmupResult, type UseVideoStreamResult, useEffieWarmup, useVideoStream };
|
package/dist/react/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/react/index.tsx
|
|
2
|
-
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
3
3
|
import { AnniePlayer } from "@effing/annie-player/react";
|
|
4
4
|
|
|
5
5
|
// src/warmup.ts
|
|
@@ -44,8 +44,149 @@ function hashUrlToId(url) {
|
|
|
44
44
|
return (hash >>> 0).toString(36);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// src/react/use-video-stream.ts
|
|
48
|
+
import { useEffect, useRef, useState } from "react";
|
|
49
|
+
function useVideoStream(url) {
|
|
50
|
+
const videoRef = useRef(null);
|
|
51
|
+
const [isFullyBuffered, setIsFullyBuffered] = useState(false);
|
|
52
|
+
const lastUrlRef = useRef(null);
|
|
53
|
+
if (url !== lastUrlRef.current) {
|
|
54
|
+
lastUrlRef.current = url;
|
|
55
|
+
if (isFullyBuffered) setIsFullyBuffered(false);
|
|
56
|
+
}
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!url) return;
|
|
59
|
+
const video = videoRef.current;
|
|
60
|
+
if (!video) return;
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
const objectUrls = [];
|
|
63
|
+
const MS = typeof window !== "undefined" && "ManagedMediaSource" in window ? window.ManagedMediaSource : typeof MediaSource !== "undefined" ? MediaSource : null;
|
|
64
|
+
const mimeCodec = 'video/mp4; codecs="avc1.42E01E,mp4a.40.2"';
|
|
65
|
+
const canMSE = MS && MS.isTypeSupported(mimeCodec);
|
|
66
|
+
const setBlobSrc = (chunks) => {
|
|
67
|
+
if (controller.signal.aborted) return;
|
|
68
|
+
const blob = new Blob(chunks, { type: "video/mp4" });
|
|
69
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
70
|
+
objectUrls.push(blobUrl);
|
|
71
|
+
video.src = blobUrl;
|
|
72
|
+
};
|
|
73
|
+
const streamVideo = async (sourceBuffer, mediaSource, msUrl) => {
|
|
74
|
+
const chunks = [];
|
|
75
|
+
let mseFailed = !sourceBuffer;
|
|
76
|
+
let response;
|
|
77
|
+
try {
|
|
78
|
+
response = await fetch(url, { signal: controller.signal });
|
|
79
|
+
} catch {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (!response.ok || !response.body) return;
|
|
83
|
+
const reader = response.body.getReader();
|
|
84
|
+
try {
|
|
85
|
+
for (; ; ) {
|
|
86
|
+
const { done, value } = await reader.read();
|
|
87
|
+
if (done) break;
|
|
88
|
+
chunks.push(value);
|
|
89
|
+
if (sourceBuffer && !mseFailed) {
|
|
90
|
+
try {
|
|
91
|
+
await new Promise((resolve, reject) => {
|
|
92
|
+
const onDone = () => {
|
|
93
|
+
sourceBuffer.removeEventListener("error", onError);
|
|
94
|
+
resolve();
|
|
95
|
+
};
|
|
96
|
+
const onError = () => {
|
|
97
|
+
sourceBuffer.removeEventListener("updateend", onDone);
|
|
98
|
+
reject(new Error("SourceBuffer append failed"));
|
|
99
|
+
};
|
|
100
|
+
sourceBuffer.addEventListener("updateend", onDone, {
|
|
101
|
+
once: true
|
|
102
|
+
});
|
|
103
|
+
sourceBuffer.addEventListener("error", onError, {
|
|
104
|
+
once: true
|
|
105
|
+
});
|
|
106
|
+
sourceBuffer.appendBuffer(value);
|
|
107
|
+
});
|
|
108
|
+
} catch (e) {
|
|
109
|
+
console.warn("MSE: append failed, will use blob fallback", e);
|
|
110
|
+
mseFailed = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
if (controller.signal.aborted) return;
|
|
116
|
+
}
|
|
117
|
+
if (controller.signal.aborted) return;
|
|
118
|
+
if (mseFailed) {
|
|
119
|
+
if (msUrl) {
|
|
120
|
+
URL.revokeObjectURL(msUrl);
|
|
121
|
+
}
|
|
122
|
+
setBlobSrc(chunks);
|
|
123
|
+
} else if (mediaSource && mediaSource.readyState === "open") {
|
|
124
|
+
mediaSource.endOfStream();
|
|
125
|
+
}
|
|
126
|
+
setIsFullyBuffered(true);
|
|
127
|
+
};
|
|
128
|
+
if (canMSE) {
|
|
129
|
+
const mediaSource = new MS();
|
|
130
|
+
const msUrl = URL.createObjectURL(mediaSource);
|
|
131
|
+
objectUrls.push(msUrl);
|
|
132
|
+
video.src = msUrl;
|
|
133
|
+
mediaSource.addEventListener("sourceopen", () => {
|
|
134
|
+
let sourceBuffer = null;
|
|
135
|
+
try {
|
|
136
|
+
sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
console.warn("MSE: addSourceBuffer failed", e);
|
|
139
|
+
}
|
|
140
|
+
streamVideo(sourceBuffer, mediaSource, msUrl);
|
|
141
|
+
});
|
|
142
|
+
} else {
|
|
143
|
+
streamVideo(null, null, null);
|
|
144
|
+
}
|
|
145
|
+
return () => {
|
|
146
|
+
controller.abort();
|
|
147
|
+
objectUrls.forEach((u) => URL.revokeObjectURL(u));
|
|
148
|
+
};
|
|
149
|
+
}, [url]);
|
|
150
|
+
return { videoRef, isFullyBuffered };
|
|
151
|
+
}
|
|
152
|
+
|
|
47
153
|
// src/react/index.tsx
|
|
48
154
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
155
|
+
function EffieVideoPreview({
|
|
156
|
+
url,
|
|
157
|
+
poster,
|
|
158
|
+
onPlay,
|
|
159
|
+
onFullyBuffered,
|
|
160
|
+
className,
|
|
161
|
+
style
|
|
162
|
+
}) {
|
|
163
|
+
const { videoRef, isFullyBuffered } = useVideoStream(url);
|
|
164
|
+
const firedRef = useRef2(false);
|
|
165
|
+
const lastUrlRef = useRef2(url);
|
|
166
|
+
if (url !== lastUrlRef.current) {
|
|
167
|
+
lastUrlRef.current = url;
|
|
168
|
+
firedRef.current = false;
|
|
169
|
+
}
|
|
170
|
+
useEffect2(() => {
|
|
171
|
+
if (isFullyBuffered && onFullyBuffered && !firedRef.current) {
|
|
172
|
+
firedRef.current = true;
|
|
173
|
+
onFullyBuffered();
|
|
174
|
+
}
|
|
175
|
+
}, [isFullyBuffered, onFullyBuffered]);
|
|
176
|
+
return /* @__PURE__ */ jsx(
|
|
177
|
+
"video",
|
|
178
|
+
{
|
|
179
|
+
ref: videoRef,
|
|
180
|
+
poster,
|
|
181
|
+
crossOrigin: "anonymous",
|
|
182
|
+
className,
|
|
183
|
+
style,
|
|
184
|
+
controls: true,
|
|
185
|
+
autoPlay: true,
|
|
186
|
+
onPlay
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
}
|
|
49
190
|
function EffieCoverPreview({
|
|
50
191
|
cover,
|
|
51
192
|
resolution,
|
|
@@ -55,36 +196,16 @@ function EffieCoverPreview({
|
|
|
55
196
|
className,
|
|
56
197
|
style
|
|
57
198
|
}) {
|
|
58
|
-
const fullyBufferedRef = useRef(false);
|
|
59
|
-
const lastVideoRef = useRef(null);
|
|
60
|
-
if (video !== lastVideoRef.current) {
|
|
61
|
-
lastVideoRef.current = video ?? null;
|
|
62
|
-
fullyBufferedRef.current = false;
|
|
63
|
-
}
|
|
64
|
-
const handleProgress = (e) => {
|
|
65
|
-
if (fullyBufferedRef.current || !onFullyBuffered) return;
|
|
66
|
-
const vid = e.currentTarget;
|
|
67
|
-
if (vid.buffered.length > 0 && vid.duration > 0) {
|
|
68
|
-
const bufferedEnd = vid.buffered.end(vid.buffered.length - 1);
|
|
69
|
-
if (bufferedEnd >= vid.duration) {
|
|
70
|
-
fullyBufferedRef.current = true;
|
|
71
|
-
onFullyBuffered();
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
199
|
if (video) {
|
|
76
200
|
return /* @__PURE__ */ jsx(
|
|
77
|
-
|
|
201
|
+
EffieVideoPreview,
|
|
78
202
|
{
|
|
79
|
-
|
|
203
|
+
url: video,
|
|
80
204
|
poster: cover,
|
|
81
|
-
crossOrigin: "anonymous",
|
|
82
|
-
className,
|
|
83
|
-
style: { ...style, height: resolution.height },
|
|
84
|
-
controls: true,
|
|
85
|
-
autoPlay: true,
|
|
86
205
|
onPlay,
|
|
87
|
-
|
|
206
|
+
onFullyBuffered,
|
|
207
|
+
className,
|
|
208
|
+
style: { ...style, height: resolution.height }
|
|
88
209
|
}
|
|
89
210
|
);
|
|
90
211
|
}
|
|
@@ -519,7 +640,7 @@ function EffieJsonBlock({ label, data }) {
|
|
|
519
640
|
] });
|
|
520
641
|
}
|
|
521
642
|
function useEffieWarmup(streamUrl) {
|
|
522
|
-
const [state, setState] =
|
|
643
|
+
const [state, setState] = useState2({
|
|
523
644
|
status: "idle",
|
|
524
645
|
total: 0,
|
|
525
646
|
cached: 0,
|
|
@@ -527,8 +648,8 @@ function useEffieWarmup(streamUrl) {
|
|
|
527
648
|
skipped: 0,
|
|
528
649
|
downloading: /* @__PURE__ */ new Map()
|
|
529
650
|
});
|
|
530
|
-
const cleanupRef =
|
|
531
|
-
|
|
651
|
+
const cleanupRef = useRef2(null);
|
|
652
|
+
useEffect2(() => {
|
|
532
653
|
if (!streamUrl) {
|
|
533
654
|
return;
|
|
534
655
|
}
|
|
@@ -600,6 +721,8 @@ export {
|
|
|
600
721
|
EffieLayerPreview,
|
|
601
722
|
EffieSegmentPreview,
|
|
602
723
|
EffieValidationErrors,
|
|
603
|
-
|
|
724
|
+
EffieVideoPreview,
|
|
725
|
+
useEffieWarmup,
|
|
726
|
+
useVideoStream
|
|
604
727
|
};
|
|
605
728
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/index.tsx","../../src/warmup.ts","../../src/utils.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\";\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 * Simple component with built-in inline styles.\n */\nexport function EffieCoverPreview({\n cover,\n resolution,\n video,\n onPlay,\n onFullyBuffered,\n className,\n style,\n}: EffieCoverPreviewProps) {\n const fullyBufferedRef = useRef(false);\n const lastVideoRef = useRef<string | null>(null);\n\n // Reset the fully buffered flag when video URL changes\n if (video !== lastVideoRef.current) {\n lastVideoRef.current = video ?? null;\n fullyBufferedRef.current = false;\n }\n\n const handleProgress = (e: React.SyntheticEvent<HTMLVideoElement>) => {\n if (fullyBufferedRef.current || !onFullyBuffered) return;\n\n const vid = e.currentTarget;\n if (vid.buffered.length > 0 && vid.duration > 0) {\n const bufferedEnd = vid.buffered.end(vid.buffered.length - 1);\n if (bufferedEnd >= vid.duration) {\n fullyBufferedRef.current = true;\n onFullyBuffered();\n }\n }\n };\n\n if (video) {\n return (\n <video\n src={video}\n poster={cover}\n crossOrigin=\"anonymous\"\n className={className}\n style={{ ...style, height: resolution.height }}\n controls\n autoPlay\n onPlay={onPlay}\n onProgress={handleProgress}\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"],"mappings":"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","names":[]}
|
|
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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effing/effie-preview",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.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.14.0",
|
|
21
|
+
"@effing/effie": "0.14.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.14.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"react": "^18.0.0 || ^19.0.0"
|