@geekapps/silo-elements-nextjs 0.0.1 → 0.0.3

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.
Files changed (45) hide show
  1. package/dist/FileUploader.d.ts +7 -3
  2. package/dist/FileUploader.js +279 -38
  3. package/dist/FileUploader.js.map +1 -1
  4. package/dist/ImageUploader.d.ts +7 -3
  5. package/dist/ImageUploader.js +329 -66
  6. package/dist/ImageUploader.js.map +1 -1
  7. package/dist/MediaUploader.d.ts +7 -3
  8. package/dist/MediaUploader.js +654 -43
  9. package/dist/MediaUploader.js.map +1 -1
  10. package/dist/VideoPlayer.d.ts +20 -18
  11. package/dist/VideoPlayer.js +980 -675
  12. package/dist/VideoPlayer.js.map +1 -1
  13. package/dist/VideoUploader.d.ts +7 -3
  14. package/dist/VideoUploader.js +295 -43
  15. package/dist/VideoUploader.js.map +1 -1
  16. package/dist/components/DropZone.d.ts +8 -5
  17. package/dist/components/DropZone.js +134 -49
  18. package/dist/components/DropZone.js.map +1 -1
  19. package/dist/components/ProgressBar.d.ts +6 -4
  20. package/dist/components/ProgressBar.js +35 -15
  21. package/dist/components/ProgressBar.js.map +1 -1
  22. package/dist/index.d.ts +12 -12
  23. package/dist/index.js +1696 -10
  24. package/dist/index.js.map +1 -1
  25. package/dist/types.d.ts +12 -10
  26. package/dist/types.js +2 -1
  27. package/dist/types.js.map +1 -1
  28. package/dist/utils/format.d.ts +4 -3
  29. package/dist/utils/format.js +19 -26
  30. package/dist/utils/format.js.map +1 -1
  31. package/dist/utils/theme.d.ts +8 -5
  32. package/dist/utils/theme.js +34 -30
  33. package/dist/utils/theme.js.map +1 -1
  34. package/package.json +4 -3
  35. package/dist/FileUploader.d.ts.map +0 -1
  36. package/dist/ImageUploader.d.ts.map +0 -1
  37. package/dist/MediaUploader.d.ts.map +0 -1
  38. package/dist/VideoPlayer.d.ts.map +0 -1
  39. package/dist/VideoUploader.d.ts.map +0 -1
  40. package/dist/components/DropZone.d.ts.map +0 -1
  41. package/dist/components/ProgressBar.d.ts.map +0 -1
  42. package/dist/index.d.ts.map +0 -1
  43. package/dist/types.d.ts.map +0 -1
  44. package/dist/utils/format.d.ts.map +0 -1
  45. package/dist/utils/theme.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,11 +1,1697 @@
1
- export { ImageUploader } from "./ImageUploader.js";
2
- export { VideoUploader } from "./VideoUploader.js";
3
- export { FileUploader } from "./FileUploader.js";
4
- export { MediaUploader } from "./MediaUploader.js";
5
- export { Video, Sources, Source, Subtitles, Subtitle, Storyboard, StoryboardFrame, VideoPlayer, } from "./VideoPlayer.js";
6
- export { DropZone } from "./components/DropZone.js";
7
- export { ProgressBar } from "./components/ProgressBar.js";
8
- export { defaultTheme, resolveTheme } from "./utils/theme.js";
9
- // Re-export provider and hooks so consumers only need @silo/elements
10
- export { SiloProvider, useUpload, useSignedUrl, useFileStatus, useSiloClient, } from "@silo/nextjs";
1
+ import React, { useState, useRef, useCallback, useMemo, useEffect } from 'react';
2
+ import { useUpload } from '@geekapps/silo-nextjs';
3
+ export { SiloProvider, useFileStatus, useSignedUrl, useSiloClient, useUpload } from '@geekapps/silo-nextjs';
4
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
+ import gsap from 'gsap';
6
+
7
+ // src/ImageUploader.tsx
8
+
9
+ // src/utils/theme.ts
10
+ var defaultTheme = {
11
+ borderColor: "#e2e8f0",
12
+ borderColorActive: "#6366f1",
13
+ backgroundColor: "#f8fafc",
14
+ backgroundColorHover: "#f1f5f9",
15
+ textColor: "#0f172a",
16
+ textColorMuted: "#64748b",
17
+ accentColor: "#6366f1",
18
+ accentColorHover: "#4f46e5",
19
+ errorColor: "#ef4444",
20
+ successColor: "#22c55e",
21
+ borderRadius: "12px",
22
+ fontFamily: "inherit"
23
+ };
24
+ function resolveTheme(theme) {
25
+ return { ...defaultTheme, ...theme };
26
+ }
27
+ function themeToVars(theme) {
28
+ return {
29
+ "--silo-border": theme.borderColor,
30
+ "--silo-border-active": theme.borderColorActive,
31
+ "--silo-bg": theme.backgroundColor,
32
+ "--silo-bg-hover": theme.backgroundColorHover,
33
+ "--silo-text": theme.textColor,
34
+ "--silo-text-muted": theme.textColorMuted,
35
+ "--silo-accent": theme.accentColor,
36
+ "--silo-accent-hover": theme.accentColorHover,
37
+ "--silo-error": theme.errorColor,
38
+ "--silo-success": theme.successColor,
39
+ "--silo-radius": theme.borderRadius,
40
+ "--silo-font": theme.fontFamily
41
+ };
42
+ }
43
+ function DropZone({
44
+ accept,
45
+ multiple = false,
46
+ disabled = false,
47
+ maxSize,
48
+ onFiles,
49
+ onError,
50
+ className = "",
51
+ style,
52
+ theme,
53
+ children
54
+ }) {
55
+ const [dragging, setDragging] = useState(false);
56
+ const inputRef = useRef(null);
57
+ const t = resolveTheme(theme);
58
+ const vars = themeToVars(t);
59
+ const validate = useCallback(
60
+ (files) => {
61
+ return files.filter((f) => {
62
+ if (maxSize && f.size > maxSize) {
63
+ onError?.(new Error(`File "${f.name}" exceeds max size of ${maxSize} bytes`));
64
+ return false;
65
+ }
66
+ return true;
67
+ });
68
+ },
69
+ [maxSize, onError]
70
+ );
71
+ const handleDrop = useCallback(
72
+ (e) => {
73
+ e.preventDefault();
74
+ setDragging(false);
75
+ if (disabled) return;
76
+ const files = Array.from(e.dataTransfer.files);
77
+ const valid = validate(files);
78
+ if (valid.length) onFiles(valid);
79
+ },
80
+ [disabled, validate, onFiles]
81
+ );
82
+ const handleChange = useCallback(
83
+ (e) => {
84
+ const files = Array.from(e.target.files ?? []);
85
+ const valid = validate(files);
86
+ if (valid.length) onFiles(valid);
87
+ e.target.value = "";
88
+ },
89
+ [validate, onFiles]
90
+ );
91
+ const rootStyle = {
92
+ ...vars,
93
+ fontFamily: "var(--silo-font)",
94
+ border: `2px dashed ${dragging ? "var(--silo-border-active)" : "var(--silo-border)"}`,
95
+ borderRadius: "var(--silo-radius)",
96
+ backgroundColor: dragging ? "var(--silo-bg-hover)" : "var(--silo-bg)",
97
+ color: "var(--silo-text)",
98
+ cursor: disabled ? "not-allowed" : "pointer",
99
+ transition: "border-color 0.15s, background-color 0.15s",
100
+ opacity: disabled ? 0.5 : 1,
101
+ ...style
102
+ };
103
+ return /* @__PURE__ */ jsxs(
104
+ "div",
105
+ {
106
+ className: `silo-dropzone${className ? ` ${className}` : ""}`,
107
+ style: rootStyle,
108
+ onDragOver: (e) => {
109
+ e.preventDefault();
110
+ if (!disabled) setDragging(true);
111
+ },
112
+ onDragLeave: () => setDragging(false),
113
+ onDrop: handleDrop,
114
+ onClick: () => !disabled && inputRef.current?.click(),
115
+ role: "button",
116
+ tabIndex: disabled ? -1 : 0,
117
+ onKeyDown: (e) => {
118
+ if (e.key === "Enter" || e.key === " ") inputRef.current?.click();
119
+ },
120
+ "aria-label": "Upload area",
121
+ children: [
122
+ /* @__PURE__ */ jsx(
123
+ "input",
124
+ {
125
+ ref: inputRef,
126
+ type: "file",
127
+ accept,
128
+ multiple,
129
+ style: { display: "none" },
130
+ onChange: handleChange,
131
+ disabled
132
+ }
133
+ ),
134
+ children
135
+ ]
136
+ }
137
+ );
138
+ }
139
+ function ProgressBar({ progress, className = "", style }) {
140
+ return /* @__PURE__ */ jsx(
141
+ "div",
142
+ {
143
+ className: `silo-progress-track${className ? ` ${className}` : ""}`,
144
+ style: {
145
+ height: "6px",
146
+ borderRadius: "3px",
147
+ backgroundColor: "rgba(99,102,241,0.15)",
148
+ overflow: "hidden",
149
+ ...style
150
+ },
151
+ role: "progressbar",
152
+ "aria-valuenow": progress,
153
+ "aria-valuemin": 0,
154
+ "aria-valuemax": 100,
155
+ children: /* @__PURE__ */ jsx(
156
+ "div",
157
+ {
158
+ className: "silo-progress-fill",
159
+ style: {
160
+ height: "100%",
161
+ width: `${progress}%`,
162
+ backgroundColor: "var(--silo-accent, #6366f1)",
163
+ borderRadius: "3px",
164
+ transition: "width 0.2s ease"
165
+ }
166
+ }
167
+ )
168
+ }
169
+ );
170
+ }
171
+
172
+ // src/utils/format.ts
173
+ function formatBytes(bytes) {
174
+ if (bytes < 1024) return `${bytes} B`;
175
+ if (bytes < 1024 ** 2) return `${(bytes / 1024).toFixed(1)} KB`;
176
+ if (bytes < 1024 ** 3) return `${(bytes / 1024 ** 2).toFixed(1)} MB`;
177
+ return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
178
+ }
179
+ function ImageUploader({
180
+ bucket,
181
+ expiresIn,
182
+ private: isPrivate = true,
183
+ onUpload,
184
+ onError,
185
+ className = "",
186
+ style,
187
+ disabled = false,
188
+ maxSize,
189
+ accept = "image/*",
190
+ showPreview = true,
191
+ theme,
192
+ renderIcon,
193
+ renderProgress,
194
+ renderSuccess,
195
+ renderError,
196
+ children
197
+ }) {
198
+ const uploadOpts = { private: isPrivate, ...bucket !== void 0 && { bucket }, ...expiresIn !== void 0 && { expiresIn } };
199
+ const { state, upload, reset } = useUpload(uploadOpts);
200
+ const [preview, setPreview] = useState(null);
201
+ const t = resolveTheme(theme);
202
+ const vars = themeToVars(t);
203
+ const handleFiles = useCallback(
204
+ async (files) => {
205
+ const file = files[0];
206
+ if (!file) return;
207
+ if (showPreview) {
208
+ const url = URL.createObjectURL(file);
209
+ setPreview(url);
210
+ }
211
+ try {
212
+ const result = await upload(file);
213
+ onUpload?.(result);
214
+ } catch (err) {
215
+ onError?.(err instanceof Error ? err : new Error(String(err)));
216
+ }
217
+ },
218
+ [upload, onUpload, onError, showPreview]
219
+ );
220
+ const containerStyle = {
221
+ ...vars,
222
+ display: "flex",
223
+ flexDirection: "column",
224
+ gap: "12px",
225
+ width: "100%",
226
+ fontFamily: "var(--silo-font)",
227
+ ...style
228
+ };
229
+ if (state.status === "error" && renderError) {
230
+ return /* @__PURE__ */ jsx("div", { style: containerStyle, children: renderError(state.error, reset) });
231
+ }
232
+ if (state.status === "done" && renderSuccess) {
233
+ return /* @__PURE__ */ jsx("div", { style: containerStyle, children: renderSuccess(state.result) });
234
+ }
235
+ return /* @__PURE__ */ jsxs("div", { className: `silo-image-uploader${className ? ` ${className}` : ""}`, style: containerStyle, children: [
236
+ /* @__PURE__ */ jsx(
237
+ DropZone,
238
+ {
239
+ ...accept !== void 0 && { accept },
240
+ ...maxSize !== void 0 && { maxSize },
241
+ ...onError !== void 0 && { onError },
242
+ ...theme !== void 0 && { theme },
243
+ disabled: disabled || state.status === "uploading",
244
+ onFiles: handleFiles,
245
+ style: { padding: "32px 24px", textAlign: "center" },
246
+ children: preview && state.status !== "uploading" ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: "8px" }, children: [
247
+ /* @__PURE__ */ jsx(
248
+ "img",
249
+ {
250
+ src: preview,
251
+ alt: "Preview",
252
+ style: {
253
+ maxWidth: "100%",
254
+ maxHeight: "200px",
255
+ borderRadius: "8px",
256
+ objectFit: "contain"
257
+ }
258
+ }
259
+ ),
260
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "12px", color: "var(--silo-text-muted)" }, children: "Click or drag to replace" })
261
+ ] }) : /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: "8px" }, children: [
262
+ renderIcon ? renderIcon() : /* @__PURE__ */ jsx("svg", { width: "40", height: "40", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", style: { color: "var(--silo-text-muted)", opacity: 0.6 }, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
263
+ children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
264
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, color: "var(--silo-text)" }, children: "Drop image here" }),
265
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "13px", color: "var(--silo-text-muted)" }, children: "or click to browse" }),
266
+ maxSize && /* @__PURE__ */ jsxs("span", { style: { fontSize: "12px", color: "var(--silo-text-muted)" }, children: [
267
+ "Max ",
268
+ formatBytes(maxSize)
269
+ ] })
270
+ ] })
271
+ ] })
272
+ }
273
+ ),
274
+ state.status === "uploading" && /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: renderProgress ? renderProgress(state.progress) : /* @__PURE__ */ jsxs(Fragment, { children: [
275
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", fontSize: "13px", color: "var(--silo-text-muted)" }, children: [
276
+ /* @__PURE__ */ jsx("span", { children: "Uploading\u2026" }),
277
+ /* @__PURE__ */ jsxs("span", { children: [
278
+ state.progress,
279
+ "%"
280
+ ] })
281
+ ] }),
282
+ /* @__PURE__ */ jsx(ProgressBar, { progress: state.progress })
283
+ ] }) }),
284
+ state.status === "done" && !renderSuccess && /* @__PURE__ */ jsxs("div", { style: {
285
+ display: "flex",
286
+ alignItems: "center",
287
+ gap: "8px",
288
+ padding: "10px 14px",
289
+ borderRadius: "8px",
290
+ backgroundColor: "rgba(34,197,94,0.1)",
291
+ color: "var(--silo-success, #22c55e)",
292
+ fontSize: "14px"
293
+ }, children: [
294
+ /* @__PURE__ */ jsx("svg", { width: "18", height: "18", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }),
295
+ /* @__PURE__ */ jsx("span", { children: "Upload complete" }),
296
+ /* @__PURE__ */ jsx(
297
+ "button",
298
+ {
299
+ onClick: (e) => {
300
+ e.stopPropagation();
301
+ reset();
302
+ setPreview(null);
303
+ },
304
+ style: { marginLeft: "auto", background: "none", border: "none", cursor: "pointer", fontSize: "12px", color: "var(--silo-text-muted)" },
305
+ children: "Upload another"
306
+ }
307
+ )
308
+ ] }),
309
+ state.status === "error" && !renderError && /* @__PURE__ */ jsxs("div", { style: {
310
+ display: "flex",
311
+ alignItems: "center",
312
+ gap: "8px",
313
+ padding: "10px 14px",
314
+ borderRadius: "8px",
315
+ backgroundColor: "rgba(239,68,68,0.1)",
316
+ color: "var(--silo-error, #ef4444)",
317
+ fontSize: "14px"
318
+ }, children: [
319
+ /* @__PURE__ */ jsx("svg", { width: "18", height: "18", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }),
320
+ /* @__PURE__ */ jsx("span", { children: state.error.message }),
321
+ /* @__PURE__ */ jsx(
322
+ "button",
323
+ {
324
+ onClick: (e) => {
325
+ e.stopPropagation();
326
+ reset();
327
+ },
328
+ style: { marginLeft: "auto", background: "none", border: "none", cursor: "pointer", fontSize: "12px", color: "var(--silo-text-muted)" },
329
+ children: "Retry"
330
+ }
331
+ )
332
+ ] })
333
+ ] });
334
+ }
335
+ function VideoUploader({
336
+ bucket,
337
+ expiresIn,
338
+ private: isPrivate = true,
339
+ onUpload,
340
+ onError,
341
+ className = "",
342
+ style,
343
+ disabled = false,
344
+ maxSize,
345
+ accept = "video/*",
346
+ showPreview = true,
347
+ theme,
348
+ renderIcon,
349
+ renderProgress,
350
+ renderSuccess,
351
+ renderError,
352
+ children
353
+ }) {
354
+ const uploadOpts = { private: isPrivate, ...bucket !== void 0 && { bucket }, ...expiresIn !== void 0 && { expiresIn } };
355
+ const { state, upload, reset } = useUpload(uploadOpts);
356
+ const [preview, setPreview] = useState(null);
357
+ const t = resolveTheme(theme);
358
+ const vars = themeToVars(t);
359
+ const handleFiles = useCallback(
360
+ async (files) => {
361
+ const file = files[0];
362
+ if (!file) return;
363
+ if (showPreview) {
364
+ const url = URL.createObjectURL(file);
365
+ setPreview(url);
366
+ }
367
+ try {
368
+ const result = await upload(file);
369
+ onUpload?.(result);
370
+ } catch (err) {
371
+ onError?.(err instanceof Error ? err : new Error(String(err)));
372
+ }
373
+ },
374
+ [upload, onUpload, onError, showPreview]
375
+ );
376
+ const containerStyle = {
377
+ ...vars,
378
+ display: "flex",
379
+ flexDirection: "column",
380
+ gap: "12px",
381
+ width: "100%",
382
+ fontFamily: "var(--silo-font)",
383
+ ...style
384
+ };
385
+ if (state.status === "error" && renderError) {
386
+ return /* @__PURE__ */ jsx("div", { style: containerStyle, children: renderError(state.error, reset) });
387
+ }
388
+ if (state.status === "done" && renderSuccess) {
389
+ return /* @__PURE__ */ jsx("div", { style: containerStyle, children: renderSuccess(state.result) });
390
+ }
391
+ return /* @__PURE__ */ jsxs("div", { className: `silo-video-uploader${className ? ` ${className}` : ""}`, style: containerStyle, children: [
392
+ /* @__PURE__ */ jsx(
393
+ DropZone,
394
+ {
395
+ ...accept !== void 0 && { accept },
396
+ ...maxSize !== void 0 && { maxSize },
397
+ ...onError !== void 0 && { onError },
398
+ ...theme !== void 0 && { theme },
399
+ disabled: disabled || state.status === "uploading",
400
+ onFiles: handleFiles,
401
+ style: { padding: "32px 24px", textAlign: "center" },
402
+ children: preview && state.status !== "uploading" ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: "8px" }, children: [
403
+ /* @__PURE__ */ jsx(
404
+ "video",
405
+ {
406
+ src: preview,
407
+ style: { maxWidth: "100%", maxHeight: "180px", borderRadius: "8px" },
408
+ controls: false,
409
+ muted: true,
410
+ playsInline: true
411
+ }
412
+ ),
413
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "12px", color: "var(--silo-text-muted)" }, children: "Click or drag to replace" })
414
+ ] }) : /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: "8px" }, children: [
415
+ renderIcon ? renderIcon() : /* @__PURE__ */ jsx("svg", { width: "40", height: "40", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", style: { color: "var(--silo-text-muted)", opacity: 0.6 }, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M15 10l4.553-2.069A1 1 0 0121 8.878v6.244a1 1 0 01-1.447.894L15 14M3 8a2 2 0 012-2h8a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2V8z" }) }),
416
+ children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
417
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, color: "var(--silo-text)" }, children: "Drop video here" }),
418
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "13px", color: "var(--silo-text-muted)" }, children: "or click to browse" }),
419
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: "12px", color: "var(--silo-text-muted)" }, children: [
420
+ "MP4, MOV, MKV, WebM",
421
+ maxSize ? ` \xB7 Max ${formatBytes(maxSize)}` : ""
422
+ ] })
423
+ ] })
424
+ ] })
425
+ }
426
+ ),
427
+ state.status === "uploading" && /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: renderProgress ? renderProgress(state.progress) : /* @__PURE__ */ jsxs(Fragment, { children: [
428
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", fontSize: "13px", color: "var(--silo-text-muted)" }, children: [
429
+ /* @__PURE__ */ jsx("span", { children: "Uploading video\u2026" }),
430
+ /* @__PURE__ */ jsxs("span", { children: [
431
+ state.progress,
432
+ "%"
433
+ ] })
434
+ ] }),
435
+ /* @__PURE__ */ jsx(ProgressBar, { progress: state.progress }),
436
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "12px", color: "var(--silo-text-muted)" }, children: "Processing will start after upload completes" })
437
+ ] }) }),
438
+ state.status === "done" && !renderSuccess && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", padding: "10px 14px", borderRadius: "8px", backgroundColor: "rgba(34,197,94,0.1)", color: "var(--silo-success, #22c55e)", fontSize: "14px" }, children: [
439
+ /* @__PURE__ */ jsx("svg", { width: "18", height: "18", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }),
440
+ /* @__PURE__ */ jsx("span", { children: "Video uploaded \u2014 processing in background" }),
441
+ /* @__PURE__ */ jsx("button", { onClick: (e) => {
442
+ e.stopPropagation();
443
+ reset();
444
+ setPreview(null);
445
+ }, style: { marginLeft: "auto", background: "none", border: "none", cursor: "pointer", fontSize: "12px", color: "var(--silo-text-muted)" }, children: "Upload another" })
446
+ ] }),
447
+ state.status === "error" && !renderError && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", padding: "10px 14px", borderRadius: "8px", backgroundColor: "rgba(239,68,68,0.1)", color: "var(--silo-error, #ef4444)", fontSize: "14px" }, children: [
448
+ /* @__PURE__ */ jsx("svg", { width: "18", height: "18", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) }),
449
+ /* @__PURE__ */ jsx("span", { children: state.error.message }),
450
+ /* @__PURE__ */ jsx("button", { onClick: (e) => {
451
+ e.stopPropagation();
452
+ reset();
453
+ }, style: { marginLeft: "auto", background: "none", border: "none", cursor: "pointer", fontSize: "12px", color: "var(--silo-text-muted)" }, children: "Retry" })
454
+ ] })
455
+ ] });
456
+ }
457
+ function FileUploader({
458
+ bucket,
459
+ expiresIn,
460
+ private: isPrivate = true,
461
+ onUpload,
462
+ onError,
463
+ className = "",
464
+ style,
465
+ disabled = false,
466
+ maxSize,
467
+ accept,
468
+ multiple = false,
469
+ theme,
470
+ renderIcon,
471
+ renderProgress,
472
+ renderSuccess,
473
+ renderError,
474
+ children
475
+ }) {
476
+ const uploadOpts = { private: isPrivate, ...bucket !== void 0 && { bucket }, ...expiresIn !== void 0 && { expiresIn } };
477
+ const { state, upload, reset } = useUpload(uploadOpts);
478
+ const t = resolveTheme(theme);
479
+ const vars = themeToVars(t);
480
+ const handleFiles = useCallback(
481
+ async (files) => {
482
+ const file = files[0];
483
+ if (!file) return;
484
+ try {
485
+ const result = await upload(file);
486
+ onUpload?.(result);
487
+ } catch (err) {
488
+ onError?.(err instanceof Error ? err : new Error(String(err)));
489
+ }
490
+ },
491
+ [upload, onUpload, onError]
492
+ );
493
+ const containerStyle = {
494
+ ...vars,
495
+ display: "flex",
496
+ flexDirection: "column",
497
+ gap: "12px",
498
+ width: "100%",
499
+ fontFamily: "var(--silo-font)",
500
+ ...style
501
+ };
502
+ if (state.status === "error" && renderError) {
503
+ return /* @__PURE__ */ jsx("div", { style: containerStyle, children: renderError(state.error, reset) });
504
+ }
505
+ if (state.status === "done" && renderSuccess) {
506
+ return /* @__PURE__ */ jsx("div", { style: containerStyle, children: renderSuccess(state.result) });
507
+ }
508
+ return /* @__PURE__ */ jsxs("div", { className: `silo-file-uploader${className ? ` ${className}` : ""}`, style: containerStyle, children: [
509
+ /* @__PURE__ */ jsx(
510
+ DropZone,
511
+ {
512
+ ...accept !== void 0 && { accept },
513
+ ...maxSize !== void 0 && { maxSize },
514
+ ...onError !== void 0 && { onError },
515
+ ...theme !== void 0 && { theme },
516
+ multiple,
517
+ disabled: disabled || state.status === "uploading",
518
+ onFiles: handleFiles,
519
+ style: { padding: "28px 24px", textAlign: "center" },
520
+ children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: "8px" }, children: [
521
+ renderIcon ? renderIcon() : /* @__PURE__ */ jsx("svg", { width: "40", height: "40", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", style: { color: "var(--silo-text-muted)", opacity: 0.6 }, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" }) }),
522
+ children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
523
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, color: "var(--silo-text)" }, children: multiple ? "Drop files here" : "Drop a file here" }),
524
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "13px", color: "var(--silo-text-muted)" }, children: "or click to browse" }),
525
+ maxSize && /* @__PURE__ */ jsxs("span", { style: { fontSize: "12px", color: "var(--silo-text-muted)" }, children: [
526
+ "Max ",
527
+ formatBytes(maxSize)
528
+ ] })
529
+ ] })
530
+ ] })
531
+ }
532
+ ),
533
+ state.status === "uploading" && /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: renderProgress ? renderProgress(state.progress) : /* @__PURE__ */ jsxs(Fragment, { children: [
534
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", fontSize: "13px", color: "var(--silo-text-muted)" }, children: [
535
+ /* @__PURE__ */ jsx("span", { children: "Uploading\u2026" }),
536
+ /* @__PURE__ */ jsxs("span", { children: [
537
+ state.progress,
538
+ "%"
539
+ ] })
540
+ ] }),
541
+ /* @__PURE__ */ jsx(ProgressBar, { progress: state.progress })
542
+ ] }) }),
543
+ state.status === "done" && !renderSuccess && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "10px", padding: "10px 14px", borderRadius: "8px", backgroundColor: "rgba(34,197,94,0.08)", border: "1px solid rgba(34,197,94,0.2)", fontSize: "14px" }, children: [
544
+ /* @__PURE__ */ jsx("svg", { width: "18", height: "18", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", style: { color: "#22c55e", flexShrink: 0 }, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }),
545
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
546
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 500, color: "var(--silo-text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: state.result.key }),
547
+ /* @__PURE__ */ jsx("div", { style: { fontSize: "12px", color: "var(--silo-text-muted)" }, children: formatBytes(state.result.size) })
548
+ ] }),
549
+ /* @__PURE__ */ jsx("button", { onClick: (e) => {
550
+ e.stopPropagation();
551
+ reset();
552
+ }, style: { background: "none", border: "none", cursor: "pointer", fontSize: "12px", color: "var(--silo-text-muted)", flexShrink: 0 }, children: "Replace" })
553
+ ] }),
554
+ state.status === "error" && !renderError && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", padding: "10px 14px", borderRadius: "8px", backgroundColor: "rgba(239,68,68,0.1)", color: "var(--silo-error, #ef4444)", fontSize: "14px" }, children: [
555
+ /* @__PURE__ */ jsx("span", { children: state.error.message }),
556
+ /* @__PURE__ */ jsx("button", { onClick: (e) => {
557
+ e.stopPropagation();
558
+ reset();
559
+ }, style: { marginLeft: "auto", background: "none", border: "none", cursor: "pointer", fontSize: "12px" }, children: "Retry" })
560
+ ] })
561
+ ] });
562
+ }
563
+ var TAB_LABELS = {
564
+ image: { label: "Image", icon: "\u{1F5BC}\uFE0F" },
565
+ video: { label: "Video", icon: "\u{1F3AC}" },
566
+ file: { label: "File", icon: "\u{1F4CE}" }
567
+ };
568
+ function MediaUploader({
569
+ tabs = ["image", "video", "file"],
570
+ defaultTab,
571
+ imageProps,
572
+ videoProps,
573
+ fileProps,
574
+ className = "",
575
+ style,
576
+ theme,
577
+ onUpload,
578
+ onError,
579
+ ...shared
580
+ }) {
581
+ const [activeTab, setActiveTab] = useState(defaultTab ?? tabs[0] ?? "image");
582
+ const t = resolveTheme(theme);
583
+ const vars = themeToVars(t);
584
+ const containerStyle = {
585
+ ...vars,
586
+ display: "flex",
587
+ flexDirection: "column",
588
+ gap: "0",
589
+ width: "100%",
590
+ fontFamily: "var(--silo-font)",
591
+ border: "1px solid var(--silo-border)",
592
+ borderRadius: "var(--silo-radius)",
593
+ overflow: "hidden",
594
+ backgroundColor: "var(--silo-bg)",
595
+ ...style
596
+ };
597
+ return /* @__PURE__ */ jsxs("div", { className: `silo-media-uploader${className ? ` ${className}` : ""}`, style: containerStyle, children: [
598
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", borderBottom: "1px solid var(--silo-border)" }, children: tabs.map((tab) => /* @__PURE__ */ jsxs(
599
+ "button",
600
+ {
601
+ onClick: () => setActiveTab(tab),
602
+ style: {
603
+ flex: 1,
604
+ padding: "10px 8px",
605
+ background: "none",
606
+ border: "none",
607
+ borderBottom: `2px solid ${activeTab === tab ? "var(--silo-accent)" : "transparent"}`,
608
+ cursor: "pointer",
609
+ fontSize: "13px",
610
+ fontWeight: activeTab === tab ? 600 : 400,
611
+ color: activeTab === tab ? "var(--silo-accent)" : "var(--silo-text-muted)",
612
+ transition: "color 0.15s, border-color 0.15s",
613
+ display: "flex",
614
+ alignItems: "center",
615
+ justifyContent: "center",
616
+ gap: "6px"
617
+ },
618
+ children: [
619
+ /* @__PURE__ */ jsx("span", { children: TAB_LABELS[tab].icon }),
620
+ /* @__PURE__ */ jsx("span", { children: TAB_LABELS[tab].label })
621
+ ]
622
+ },
623
+ tab
624
+ )) }),
625
+ /* @__PURE__ */ jsxs("div", { style: { padding: "16px" }, children: [
626
+ activeTab === "image" && /* @__PURE__ */ jsx(
627
+ ImageUploader,
628
+ {
629
+ ...shared,
630
+ ...imageProps,
631
+ ...theme !== void 0 && { theme },
632
+ ...onUpload !== void 0 && { onUpload },
633
+ ...onError !== void 0 && { onError }
634
+ }
635
+ ),
636
+ activeTab === "video" && /* @__PURE__ */ jsx(
637
+ VideoUploader,
638
+ {
639
+ ...shared,
640
+ ...videoProps,
641
+ ...theme !== void 0 && { theme },
642
+ ...onUpload !== void 0 && { onUpload },
643
+ ...onError !== void 0 && { onError }
644
+ }
645
+ ),
646
+ activeTab === "file" && /* @__PURE__ */ jsx(
647
+ FileUploader,
648
+ {
649
+ ...shared,
650
+ ...fileProps,
651
+ ...theme !== void 0 && { theme },
652
+ ...onUpload !== void 0 && { onUpload },
653
+ ...onError !== void 0 && { onError }
654
+ }
655
+ )
656
+ ] })
657
+ ] });
658
+ }
659
+ var AUTO_QUALITY = {
660
+ id: "auto",
661
+ label: "Auto",
662
+ type: "auto"
663
+ };
664
+ function Sources(_props) {
665
+ return null;
666
+ }
667
+ function Source(_props) {
668
+ return null;
669
+ }
670
+ function Subtitles(_props) {
671
+ return null;
672
+ }
673
+ function Subtitle(_props) {
674
+ return null;
675
+ }
676
+ function Storyboard(_props) {
677
+ return null;
678
+ }
679
+ function StoryboardFrame(_props) {
680
+ return null;
681
+ }
682
+ function Video({
683
+ title,
684
+ description,
685
+ poster,
686
+ children,
687
+ className,
688
+ autoHideControls = true,
689
+ defaultVolume = 0.72
690
+ }) {
691
+ const parsed = useMemo(() => parseVideoChildren(children), [children]);
692
+ const initialSourceIndex = useMemo(() => {
693
+ const index = parsed.sources.findIndex((source) => source.default);
694
+ return index >= 0 ? index : 0;
695
+ }, [parsed.sources]);
696
+ const initialSubtitleMode = useMemo(() => {
697
+ const track = parsed.subtitles.find((subtitle) => subtitle.default);
698
+ return track?.srclang ?? "off";
699
+ }, [parsed.subtitles]);
700
+ const containerRef = useRef(null);
701
+ const chromeRef = useRef(null);
702
+ const playerRef = useRef(null);
703
+ const videoRef = useRef(null);
704
+ const progressRef = useRef(null);
705
+ const hlsRef = useRef(null);
706
+ const dashRef = useRef(null);
707
+ const hideTimerRef = useRef(null);
708
+ const [sourceIndex, setSourceIndex] = useState(initialSourceIndex);
709
+ const [qualities, setQualities] = useState([AUTO_QUALITY]);
710
+ const [selectedQuality, setSelectedQuality] = useState("auto");
711
+ const [audioTracks, setAudioTracks] = useState([]);
712
+ const [selectedAudio, setSelectedAudio] = useState(0);
713
+ const [openMenu, setOpenMenu] = useState(null);
714
+ const [subtitleMode, setSubtitleMode] = useState(initialSubtitleMode);
715
+ const [storyboardCues, setStoryboardCues] = useState(
716
+ []
717
+ );
718
+ const [preview, setPreview] = useState(null);
719
+ const [duration, setDuration] = useState(0);
720
+ const [currentTime, setCurrentTime] = useState(0);
721
+ const [bufferedTime, setBufferedTime] = useState(0);
722
+ const [isPlaying, setIsPlaying] = useState(false);
723
+ const [isLoading, setIsLoading] = useState(true);
724
+ const [controlsVisible, setControlsVisible] = useState(true);
725
+ const [volume, setVolume] = useState(defaultVolume);
726
+ const [isMuted, setIsMuted] = useState(false);
727
+ const [isFullscreen, setIsFullscreen] = useState(false);
728
+ const [error, setError] = useState(null);
729
+ const activeSource = parsed.sources[sourceIndex] ?? parsed.sources[0] ?? null;
730
+ const progressPercent = duration ? currentTime / duration * 100 : 0;
731
+ const bufferedPercent = duration ? bufferedTime / duration * 100 : 0;
732
+ const destroyMediaEngines = useCallback(() => {
733
+ if (hlsRef.current) {
734
+ hlsRef.current.destroy();
735
+ hlsRef.current = null;
736
+ }
737
+ if (dashRef.current) {
738
+ dashRef.current.reset();
739
+ dashRef.current = null;
740
+ }
741
+ }, []);
742
+ const applySubtitleMode = useCallback((mode) => {
743
+ const video = videoRef.current;
744
+ if (!video) return;
745
+ Array.from(video.textTracks).forEach((track) => {
746
+ track.mode = mode !== "off" && track.language === mode ? "showing" : "disabled";
747
+ });
748
+ }, []);
749
+ const showControlsTemporarily = useCallback(() => {
750
+ setControlsVisible(true);
751
+ if (hideTimerRef.current) {
752
+ window.clearTimeout(hideTimerRef.current);
753
+ }
754
+ const video = videoRef.current;
755
+ if (autoHideControls && video && !video.paused) {
756
+ hideTimerRef.current = window.setTimeout(() => {
757
+ setControlsVisible(false);
758
+ }, 2400);
759
+ }
760
+ }, [autoHideControls]);
761
+ useEffect(() => {
762
+ if (!containerRef.current) return;
763
+ gsap.fromTo(
764
+ containerRef.current,
765
+ {
766
+ opacity: 0,
767
+ y: 36,
768
+ scale: 0.985,
769
+ filter: "blur(10px)"
770
+ },
771
+ {
772
+ opacity: 1,
773
+ y: 0,
774
+ scale: 1,
775
+ filter: "blur(0px)",
776
+ duration: 0.85,
777
+ ease: "power3.out"
778
+ }
779
+ );
780
+ }, []);
781
+ useEffect(() => {
782
+ if (!chromeRef.current) return;
783
+ gsap.to(chromeRef.current, {
784
+ opacity: controlsVisible ? 1 : 0,
785
+ y: controlsVisible ? 0 : 10,
786
+ duration: 0.22,
787
+ ease: "power2.out"
788
+ });
789
+ }, [controlsVisible]);
790
+ useEffect(() => {
791
+ if (sourceIndex >= parsed.sources.length) {
792
+ setSourceIndex(initialSourceIndex);
793
+ }
794
+ }, [sourceIndex, parsed.sources.length, initialSourceIndex]);
795
+ useEffect(() => {
796
+ if (subtitleMode !== "off" && !parsed.subtitles.some((subtitle) => subtitle.srclang === subtitleMode)) {
797
+ setSubtitleMode(initialSubtitleMode);
798
+ }
799
+ }, [subtitleMode, parsed.subtitles, initialSubtitleMode]);
800
+ useEffect(() => {
801
+ const video = videoRef.current;
802
+ if (!video) return;
803
+ const syncTime = () => {
804
+ setCurrentTime(video.currentTime || 0);
805
+ if (video.buffered.length > 0) {
806
+ setBufferedTime(video.buffered.end(video.buffered.length - 1));
807
+ }
808
+ };
809
+ const syncDuration = () => {
810
+ setDuration(Number.isFinite(video.duration) ? video.duration : 0);
811
+ applySubtitleMode(subtitleMode);
812
+ };
813
+ const onPlay = () => {
814
+ setIsPlaying(true);
815
+ showControlsTemporarily();
816
+ };
817
+ const onPause = () => {
818
+ setIsPlaying(false);
819
+ setControlsVisible(true);
820
+ };
821
+ const onWaiting = () => setIsLoading(true);
822
+ const onCanPlay = () => setIsLoading(false);
823
+ const onEnded = () => setControlsVisible(true);
824
+ video.addEventListener("timeupdate", syncTime);
825
+ video.addEventListener("progress", syncTime);
826
+ video.addEventListener("loadedmetadata", syncDuration);
827
+ video.addEventListener("durationchange", syncDuration);
828
+ video.addEventListener("play", onPlay);
829
+ video.addEventListener("pause", onPause);
830
+ video.addEventListener("waiting", onWaiting);
831
+ video.addEventListener("canplay", onCanPlay);
832
+ video.addEventListener("ended", onEnded);
833
+ return () => {
834
+ video.removeEventListener("timeupdate", syncTime);
835
+ video.removeEventListener("progress", syncTime);
836
+ video.removeEventListener("loadedmetadata", syncDuration);
837
+ video.removeEventListener("durationchange", syncDuration);
838
+ video.removeEventListener("play", onPlay);
839
+ video.removeEventListener("pause", onPause);
840
+ video.removeEventListener("waiting", onWaiting);
841
+ video.removeEventListener("canplay", onCanPlay);
842
+ video.removeEventListener("ended", onEnded);
843
+ };
844
+ }, [applySubtitleMode, subtitleMode, showControlsTemporarily]);
845
+ useEffect(() => {
846
+ const video = videoRef.current;
847
+ if (!video) return;
848
+ video.volume = volume;
849
+ video.muted = isMuted || volume === 0;
850
+ }, [volume, isMuted]);
851
+ useEffect(() => {
852
+ applySubtitleMode(subtitleMode);
853
+ }, [subtitleMode, applySubtitleMode]);
854
+ useEffect(() => {
855
+ const onFullscreenChange = () => {
856
+ setIsFullscreen(Boolean(document.fullscreenElement));
857
+ };
858
+ document.addEventListener("fullscreenchange", onFullscreenChange);
859
+ return () => {
860
+ document.removeEventListener("fullscreenchange", onFullscreenChange);
861
+ };
862
+ }, []);
863
+ useEffect(() => {
864
+ let cancelled = false;
865
+ async function loadStoryboard() {
866
+ if (!parsed.storyboard) {
867
+ setStoryboardCues([]);
868
+ return;
869
+ }
870
+ if (parsed.storyboard.frames.length > 0) {
871
+ setStoryboardCues(parsed.storyboard.frames);
872
+ return;
873
+ }
874
+ if (!parsed.storyboard.src) {
875
+ setStoryboardCues([]);
876
+ return;
877
+ }
878
+ try {
879
+ const response = await fetch(parsed.storyboard.src);
880
+ if (!response.ok) {
881
+ throw new Error("Storyboard not found");
882
+ }
883
+ const text = await response.text();
884
+ const cues = parseStoryboardVtt(
885
+ text,
886
+ new URL(parsed.storyboard.src, window.location.href).href
887
+ );
888
+ if (!cancelled) {
889
+ setStoryboardCues(cues);
890
+ }
891
+ } catch {
892
+ if (!cancelled && parsed.storyboard.fallbackImage) {
893
+ setStoryboardCues([
894
+ {
895
+ start: 0,
896
+ end: Number.MAX_SAFE_INTEGER,
897
+ image: parsed.storyboard.fallbackImage,
898
+ w: parsed.storyboard.width ?? 160,
899
+ h: parsed.storyboard.height ?? 90
900
+ }
901
+ ]);
902
+ }
903
+ }
904
+ }
905
+ loadStoryboard();
906
+ return () => {
907
+ cancelled = true;
908
+ };
909
+ }, [parsed.storyboard]);
910
+ useEffect(() => {
911
+ const video = videoRef.current;
912
+ let cancelled = false;
913
+ if (!video || !activeSource) {
914
+ return;
915
+ }
916
+ destroyMediaEngines();
917
+ setError(null);
918
+ setIsLoading(true);
919
+ setCurrentTime(0);
920
+ setBufferedTime(0);
921
+ setDuration(0);
922
+ setSelectedQuality("auto");
923
+ setQualities([AUTO_QUALITY]);
924
+ setAudioTracks([]);
925
+ setSelectedAudio(0);
926
+ setOpenMenu(null);
927
+ video.pause();
928
+ video.removeAttribute("src");
929
+ video.load();
930
+ const sourceType = inferSourceType(activeSource);
931
+ if (sourceType === "dash") {
932
+ void (async () => {
933
+ try {
934
+ const dashModule = await import('dashjs');
935
+ if (cancelled) return;
936
+ const dashjs = dashModule;
937
+ const dash = dashjs.MediaPlayer().create();
938
+ dashRef.current = dash;
939
+ dash.updateSettings({
940
+ streaming: {
941
+ abr: {
942
+ autoSwitchBitrate: {
943
+ video: true
944
+ }
945
+ }
946
+ }
947
+ });
948
+ dash.on(dashjs.MediaPlayer.events.STREAM_INITIALIZED, () => {
949
+ const bitrates = dash.getBitrateInfoListFor("video") ?? [];
950
+ setQualities([
951
+ AUTO_QUALITY,
952
+ ...bitrates.map((item, index) => ({
953
+ id: `dash-${index}`,
954
+ label: item.height ? `${item.height}p` : `${Math.round((item.bitrate ?? 0) / 1e3)} kbps`,
955
+ type: "dash",
956
+ index
957
+ }))
958
+ ]);
959
+ setIsLoading(false);
960
+ });
961
+ dash.on(dashjs.MediaPlayer.events.ERROR, () => {
962
+ setError("N\xE3o foi poss\xEDvel reproduzir o stream MPEG-DASH.");
963
+ setIsLoading(false);
964
+ });
965
+ dash.initialize(video, activeSource.src, false);
966
+ } catch {
967
+ if (!cancelled) {
968
+ setError("N\xE3o foi poss\xEDvel carregar o player MPEG-DASH.");
969
+ setIsLoading(false);
970
+ }
971
+ }
972
+ })();
973
+ return () => {
974
+ cancelled = true;
975
+ destroyMediaEngines();
976
+ };
977
+ }
978
+ if (sourceType === "hls") {
979
+ void (async () => {
980
+ try {
981
+ const HlsModule = await import('hls.js');
982
+ if (cancelled) return;
983
+ const Hls = HlsModule.default;
984
+ if (Hls.isSupported()) {
985
+ const hls = new Hls({
986
+ enableWorker: true,
987
+ maxBufferLength: 30,
988
+ maxMaxBufferLength: 60,
989
+ maxBufferSize: 60 * 1e3 * 1e3
990
+ });
991
+ hlsRef.current = hls;
992
+ hls.loadSource(activeSource.src);
993
+ hls.attachMedia(video);
994
+ hls.on(Hls.Events.MANIFEST_PARSED, (_, data) => {
995
+ const levels = data.levels ?? hls.levels ?? [];
996
+ setQualities([
997
+ AUTO_QUALITY,
998
+ ...levels.map((level, index) => ({
999
+ id: `hls-${index}`,
1000
+ label: level.height ? `${level.height}p` : `${Math.round((level.bitrate ?? 0) / 1e3)} kbps`,
1001
+ type: "hls",
1002
+ index
1003
+ }))
1004
+ ]);
1005
+ const tracks = hls.audioTracks ?? [];
1006
+ if (tracks.length > 1) {
1007
+ setAudioTracks(tracks.map((t, i) => ({
1008
+ id: i,
1009
+ label: t.name ?? t.lang ?? `Track ${i + 1}`
1010
+ })));
1011
+ setSelectedAudio(hls.audioTrack ?? 0);
1012
+ }
1013
+ setIsLoading(false);
1014
+ });
1015
+ hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, (_, data) => {
1016
+ const tracks = data.audioTracks ?? [];
1017
+ if (tracks.length > 1) {
1018
+ setAudioTracks(tracks.map((t, i) => ({
1019
+ id: i,
1020
+ label: t.name ?? t.lang ?? `Track ${i + 1}`
1021
+ })));
1022
+ }
1023
+ });
1024
+ hls.on(Hls.Events.ERROR, (_, data) => {
1025
+ if (!data.fatal) return;
1026
+ if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
1027
+ hls.recoverMediaError();
1028
+ return;
1029
+ }
1030
+ setError("N\xE3o foi poss\xEDvel reproduzir o stream HLS.");
1031
+ setIsLoading(false);
1032
+ });
1033
+ return;
1034
+ }
1035
+ if (video.canPlayType("application/vnd.apple.mpegurl")) {
1036
+ video.src = activeSource.src;
1037
+ video.load();
1038
+ setIsLoading(false);
1039
+ return;
1040
+ }
1041
+ setError("Este navegador n\xE3o suporta HLS.");
1042
+ setIsLoading(false);
1043
+ } catch {
1044
+ if (!cancelled) {
1045
+ setError("N\xE3o foi poss\xEDvel carregar o player HLS.");
1046
+ setIsLoading(false);
1047
+ }
1048
+ }
1049
+ })();
1050
+ return () => {
1051
+ cancelled = true;
1052
+ destroyMediaEngines();
1053
+ };
1054
+ }
1055
+ video.src = activeSource.src;
1056
+ video.load();
1057
+ setIsLoading(false);
1058
+ return () => {
1059
+ cancelled = true;
1060
+ video.removeAttribute("src");
1061
+ video.load();
1062
+ };
1063
+ }, [activeSource, destroyMediaEngines]);
1064
+ const togglePlay = useCallback(async () => {
1065
+ const video = videoRef.current;
1066
+ if (!video) return;
1067
+ try {
1068
+ if (video.paused) {
1069
+ await video.play();
1070
+ } else {
1071
+ video.pause();
1072
+ }
1073
+ } catch {
1074
+ setError("O navegador bloqueou a reprodu\xE7\xE3o autom\xE1tica.");
1075
+ }
1076
+ }, []);
1077
+ const seekRelative = useCallback((seconds) => {
1078
+ const video = videoRef.current;
1079
+ if (!video) return;
1080
+ video.currentTime = Math.max(
1081
+ 0,
1082
+ Math.min(video.currentTime + seconds, video.duration || 0)
1083
+ );
1084
+ }, []);
1085
+ const toggleFullscreen = useCallback(async () => {
1086
+ const player = playerRef.current;
1087
+ if (!player) return;
1088
+ try {
1089
+ if (!document.fullscreenElement) {
1090
+ await player.requestFullscreen();
1091
+ } else {
1092
+ await document.exitFullscreen();
1093
+ }
1094
+ } catch {
1095
+ setError("N\xE3o foi poss\xEDvel alterar o modo fullscreen.");
1096
+ }
1097
+ }, []);
1098
+ const changeAudio = useCallback((trackId) => {
1099
+ setSelectedAudio(trackId);
1100
+ setOpenMenu(null);
1101
+ if (hlsRef.current) {
1102
+ hlsRef.current.audioTrack = trackId;
1103
+ }
1104
+ }, []);
1105
+ const changeQuality = useCallback(
1106
+ (qualityId) => {
1107
+ const option = qualities.find((quality) => quality.id === qualityId);
1108
+ if (!option) return;
1109
+ setSelectedQuality(qualityId);
1110
+ setOpenMenu(null);
1111
+ if (option.type === "auto") {
1112
+ if (hlsRef.current) {
1113
+ hlsRef.current.currentLevel = -1;
1114
+ }
1115
+ if (dashRef.current) {
1116
+ dashRef.current.updateSettings({
1117
+ streaming: {
1118
+ abr: {
1119
+ autoSwitchBitrate: {
1120
+ video: true
1121
+ }
1122
+ }
1123
+ }
1124
+ });
1125
+ }
1126
+ return;
1127
+ }
1128
+ if (option.type === "hls" && hlsRef.current && option.index != null) {
1129
+ hlsRef.current.currentLevel = option.index;
1130
+ return;
1131
+ }
1132
+ if (option.type === "dash" && dashRef.current && option.index != null) {
1133
+ dashRef.current.updateSettings({
1134
+ streaming: {
1135
+ abr: {
1136
+ autoSwitchBitrate: {
1137
+ video: false
1138
+ }
1139
+ }
1140
+ }
1141
+ });
1142
+ dashRef.current.setQualityFor("video", option.index);
1143
+ }
1144
+ },
1145
+ [qualities]
1146
+ );
1147
+ const handleProgressPointerMove = useCallback(
1148
+ (event) => {
1149
+ const progress = progressRef.current;
1150
+ if (!progress || !duration || storyboardCues.length === 0) {
1151
+ setPreview(null);
1152
+ return;
1153
+ }
1154
+ const rect = progress.getBoundingClientRect();
1155
+ const x = Math.max(0, Math.min(event.clientX - rect.left, rect.width));
1156
+ const time = x / rect.width * duration;
1157
+ const cue = findStoryboardCue(storyboardCues, time);
1158
+ if (!cue) {
1159
+ setPreview(null);
1160
+ return;
1161
+ }
1162
+ setPreview({
1163
+ cue,
1164
+ time,
1165
+ left: Math.max(80, Math.min(x, rect.width - 80))
1166
+ });
1167
+ },
1168
+ [duration, storyboardCues]
1169
+ );
1170
+ const handleProgressPointerLeave = useCallback(() => {
1171
+ setPreview(null);
1172
+ }, []);
1173
+ const handleProgressPointerDown = useCallback(
1174
+ (event) => {
1175
+ const video = videoRef.current;
1176
+ const progress = progressRef.current;
1177
+ if (!video || !progress || !duration) return;
1178
+ const rect = progress.getBoundingClientRect();
1179
+ const x = Math.max(0, Math.min(event.clientX - rect.left, rect.width));
1180
+ const nextTime = x / rect.width * duration;
1181
+ video.currentTime = nextTime;
1182
+ setCurrentTime(nextTime);
1183
+ },
1184
+ [duration]
1185
+ );
1186
+ const handleKeyDown = useCallback(
1187
+ (event) => {
1188
+ const target = event.target;
1189
+ const tagName = target.tagName.toLowerCase();
1190
+ if (["input", "select", "button"].includes(tagName)) {
1191
+ return;
1192
+ }
1193
+ if (event.key === " ") {
1194
+ event.preventDefault();
1195
+ togglePlay();
1196
+ }
1197
+ if (event.key === "ArrowRight") {
1198
+ seekRelative(10);
1199
+ }
1200
+ if (event.key === "ArrowLeft") {
1201
+ seekRelative(-10);
1202
+ }
1203
+ if (event.key.toLowerCase() === "f") {
1204
+ toggleFullscreen();
1205
+ }
1206
+ if (event.key.toLowerCase() === "m") {
1207
+ setIsMuted((value) => !value);
1208
+ }
1209
+ },
1210
+ [seekRelative, toggleFullscreen, togglePlay]
1211
+ );
1212
+ if (!activeSource) {
1213
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-700", children: [
1214
+ "O componente Video precisa de pelo menos um",
1215
+ " ",
1216
+ /* @__PURE__ */ jsx("code", { children: "<Source />" }),
1217
+ "."
1218
+ ] });
1219
+ }
1220
+ return /* @__PURE__ */ jsx(
1221
+ "div",
1222
+ {
1223
+ ref: containerRef,
1224
+ className: `mx-auto w-full max-w-6xl ${className ?? ""}`,
1225
+ children: /* @__PURE__ */ jsxs(
1226
+ "div",
1227
+ {
1228
+ ref: playerRef,
1229
+ tabIndex: 0,
1230
+ onKeyDown: handleKeyDown,
1231
+ onMouseMove: showControlsTemporarily,
1232
+ onMouseLeave: () => {
1233
+ if (isPlaying && autoHideControls) {
1234
+ setControlsVisible(false);
1235
+ }
1236
+ },
1237
+ className: "relative aspect-video w-full overflow-hidden rounded-[14px] bg-black shadow-[0_30px_90px_rgba(15,15,15,0.22)] outline-none ring-1 ring-black/5",
1238
+ children: [
1239
+ /* @__PURE__ */ jsx(
1240
+ "video",
1241
+ {
1242
+ ref: videoRef,
1243
+ className: "h-full w-full object-contain",
1244
+ poster,
1245
+ playsInline: true,
1246
+ preload: "metadata",
1247
+ crossOrigin: "anonymous",
1248
+ children: parsed.subtitles.map((subtitle) => /* @__PURE__ */ jsx(
1249
+ "track",
1250
+ {
1251
+ kind: "subtitles",
1252
+ src: subtitle.src,
1253
+ srcLang: subtitle.srclang,
1254
+ label: subtitle.label,
1255
+ default: subtitle.default
1256
+ },
1257
+ `${activeSource.src}-${subtitle.srclang}`
1258
+ ))
1259
+ }
1260
+ ),
1261
+ /* @__PURE__ */ jsx(
1262
+ "div",
1263
+ {
1264
+ className: `pointer-events-none absolute inset-0 z-10 grid place-items-center transition ${isPlaying ? "opacity-0" : "opacity-100"}`,
1265
+ children: /* @__PURE__ */ jsx("span", { className: "grid size-20 place-items-center rounded-full bg-white/15 text-white backdrop-blur-xl ring-1 ring-white/20", children: /* @__PURE__ */ jsx(PlayIcon, { className: "ml-1 size-9" }) })
1266
+ }
1267
+ ),
1268
+ isLoading && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 z-20 grid place-items-center bg-black/10", children: /* @__PURE__ */ jsx("div", { className: "size-9 animate-spin rounded-full border-2 border-white/25 border-t-white" }) }),
1269
+ error && /* @__PURE__ */ jsx("div", { className: "absolute inset-x-8 top-1/2 z-40 -translate-y-1/2 rounded-2xl border border-white/10 bg-black/75 p-5 text-center text-sm text-white shadow-2xl backdrop-blur-xl", children: error }),
1270
+ /* @__PURE__ */ jsxs(
1271
+ "div",
1272
+ {
1273
+ ref: chromeRef,
1274
+ onClick: togglePlay,
1275
+ className: `absolute inset-0 z-30 flex flex-col justify-between bg-gradient-to-b from-black/55 via-black/10 to-black/75 ${controlsVisible ? "" : "pointer-events-none"}`,
1276
+ children: [
1277
+ /* @__PURE__ */ jsxs("header", { onClick: (e) => e.stopPropagation(), className: "flex items-start justify-between gap-4 px-7 pt-7 text-white md:px-9 md:pt-8", children: [
1278
+ /* @__PURE__ */ jsxs("div", { children: [
1279
+ title && /* @__PURE__ */ jsx("h1", { className: "text-lg font-bold tracking-wide md:text-xl", children: title }),
1280
+ description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm font-medium text-white/85", children: description })
1281
+ ] }),
1282
+ parsed.sources.length > 1 && /* @__PURE__ */ jsx(
1283
+ "select",
1284
+ {
1285
+ value: String(sourceIndex),
1286
+ onChange: (e) => setSourceIndex(Number(e.target.value)),
1287
+ "aria-label": "Video source",
1288
+ className: "h-8 rounded-full border border-white/15 bg-white/10 px-3 text-xs font-semibold text-white outline-none backdrop-blur-md transition hover:bg-white/15",
1289
+ children: parsed.sources.map((source, index) => /* @__PURE__ */ jsx("option", { value: String(index), className: "text-black", children: source.label ?? source.type ?? `Source ${index + 1}` }, `${source.src}-${index}`))
1290
+ }
1291
+ )
1292
+ ] }),
1293
+ /* @__PURE__ */ jsxs("footer", { onClick: (e) => e.stopPropagation(), className: "px-7 pb-7 text-white md:px-9 md:pb-8", children: [
1294
+ /* @__PURE__ */ jsxs(
1295
+ "div",
1296
+ {
1297
+ ref: progressRef,
1298
+ onPointerMove: handleProgressPointerMove,
1299
+ onPointerLeave: handleProgressPointerLeave,
1300
+ onPointerDown: handleProgressPointerDown,
1301
+ className: "relative mb-6 h-5 cursor-pointer",
1302
+ children: [
1303
+ preview && /* @__PURE__ */ jsxs(
1304
+ "div",
1305
+ {
1306
+ className: "pointer-events-none absolute bottom-8 z-20 -translate-x-1/2 rounded-lg bg-black/80 p-1 shadow-2xl ring-1 ring-white/15 backdrop-blur",
1307
+ style: { left: preview.left },
1308
+ children: [
1309
+ /* @__PURE__ */ jsx(
1310
+ "div",
1311
+ {
1312
+ className: "overflow-hidden rounded-md bg-neutral-900",
1313
+ style: {
1314
+ width: preview.cue.w ?? 160,
1315
+ height: preview.cue.h ?? 90,
1316
+ backgroundImage: `url(${preview.cue.image})`,
1317
+ backgroundPosition: preview.cue.x != null && preview.cue.y != null ? `-${preview.cue.x}px -${preview.cue.y}px` : "center",
1318
+ backgroundSize: preview.cue.x != null ? "auto" : "cover",
1319
+ backgroundRepeat: "no-repeat"
1320
+ }
1321
+ }
1322
+ ),
1323
+ /* @__PURE__ */ jsx("div", { className: "pt-1 text-center text-[11px] font-semibold text-white/80", children: formatTime(preview.time) })
1324
+ ]
1325
+ }
1326
+ ),
1327
+ /* @__PURE__ */ jsxs("div", { className: "absolute left-0 right-0 top-1/2 h-[5px] -translate-y-1/2 overflow-hidden rounded-full bg-white/22", children: [
1328
+ /* @__PURE__ */ jsx(
1329
+ "div",
1330
+ {
1331
+ className: "absolute inset-y-0 left-0 bg-white/30",
1332
+ style: { width: `${bufferedPercent}%` }
1333
+ }
1334
+ ),
1335
+ /* @__PURE__ */ jsx(
1336
+ "div",
1337
+ {
1338
+ className: "absolute inset-y-0 left-0 bg-white/85",
1339
+ style: { width: `${progressPercent}%` }
1340
+ }
1341
+ )
1342
+ ] })
1343
+ ]
1344
+ }
1345
+ ),
1346
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-5", children: [
1347
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-5", children: [
1348
+ /* @__PURE__ */ jsx(
1349
+ "button",
1350
+ {
1351
+ type: "button",
1352
+ onClick: togglePlay,
1353
+ className: "grid size-8 place-items-center text-white transition hover:scale-105 hover:text-white/80",
1354
+ "aria-label": isPlaying ? "Pause" : "Play",
1355
+ children: isPlaying ? /* @__PURE__ */ jsx(PauseIcon, { className: "size-7" }) : /* @__PURE__ */ jsx(PlayIcon, { className: "size-7" })
1356
+ }
1357
+ ),
1358
+ /* @__PURE__ */ jsx(
1359
+ "button",
1360
+ {
1361
+ type: "button",
1362
+ onClick: () => seekRelative(10),
1363
+ className: "hidden size-8 place-items-center text-white transition hover:scale-105 hover:text-white/80 sm:grid",
1364
+ "aria-label": "Forward 10 seconds",
1365
+ children: /* @__PURE__ */ jsx(ForwardIcon, { className: "size-7" })
1366
+ }
1367
+ ),
1368
+ /* @__PURE__ */ jsxs("div", { className: "hidden items-center gap-2 text-sm font-semibold text-white/75 sm:flex", children: [
1369
+ /* @__PURE__ */ jsx("span", { children: formatTime(currentTime) }),
1370
+ /* @__PURE__ */ jsx("span", { className: "text-white/35", children: "/" }),
1371
+ /* @__PURE__ */ jsx("span", { children: formatTime(duration) })
1372
+ ] })
1373
+ ] }),
1374
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1375
+ /* @__PURE__ */ jsx(
1376
+ "button",
1377
+ {
1378
+ type: "button",
1379
+ onClick: () => setIsMuted((value) => !value),
1380
+ className: "grid size-8 place-items-center text-white transition hover:scale-105 hover:text-white/80",
1381
+ "aria-label": isMuted ? "Unmute" : "Mute",
1382
+ children: isMuted || volume === 0 ? /* @__PURE__ */ jsx(MutedIcon, { className: "size-6" }) : /* @__PURE__ */ jsx(VolumeIcon, { className: "size-6" })
1383
+ }
1384
+ ),
1385
+ /* @__PURE__ */ jsx(
1386
+ "input",
1387
+ {
1388
+ type: "range",
1389
+ min: "0",
1390
+ max: "1",
1391
+ step: "0.01",
1392
+ value: isMuted ? 0 : volume,
1393
+ onChange: (event) => {
1394
+ const nextVolume = Number(event.target.value);
1395
+ setVolume(nextVolume);
1396
+ setIsMuted(nextVolume === 0);
1397
+ },
1398
+ className: "hidden h-1 w-20 accent-white md:block",
1399
+ "aria-label": "Audio level"
1400
+ }
1401
+ ),
1402
+ /* @__PURE__ */ jsx("div", { className: "mx-1 hidden h-4 w-px bg-white/20 md:block" }),
1403
+ audioTracks.length > 1 && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1404
+ /* @__PURE__ */ jsx(
1405
+ "button",
1406
+ {
1407
+ type: "button",
1408
+ onClick: () => setOpenMenu(openMenu === "audio" ? null : "audio"),
1409
+ className: `grid size-8 place-items-center rounded transition hover:text-white/80 ${openMenu === "audio" ? "text-white" : "text-white/60"}`,
1410
+ "aria-label": "Audio track",
1411
+ children: /* @__PURE__ */ jsx(AudioIcon, { className: "size-5" })
1412
+ }
1413
+ ),
1414
+ openMenu === "audio" && /* @__PURE__ */ jsx(PopoverMenu, { onClose: () => setOpenMenu(null), children: audioTracks.map((track) => /* @__PURE__ */ jsx(
1415
+ PopoverItem,
1416
+ {
1417
+ active: selectedAudio === track.id,
1418
+ onClick: () => changeAudio(track.id),
1419
+ children: track.label
1420
+ },
1421
+ track.id
1422
+ )) })
1423
+ ] }),
1424
+ parsed.subtitles.length > 0 && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1425
+ /* @__PURE__ */ jsx(
1426
+ "button",
1427
+ {
1428
+ type: "button",
1429
+ onClick: () => setOpenMenu(openMenu === "captions" ? null : "captions"),
1430
+ className: `grid size-8 place-items-center rounded transition hover:text-white/80 ${subtitleMode !== "off" || openMenu === "captions" ? "text-white" : "text-white/60"}`,
1431
+ "aria-label": "Captions",
1432
+ children: /* @__PURE__ */ jsx(CaptionsIcon, { className: "size-5" })
1433
+ }
1434
+ ),
1435
+ openMenu === "captions" && /* @__PURE__ */ jsxs(PopoverMenu, { onClose: () => setOpenMenu(null), children: [
1436
+ /* @__PURE__ */ jsx(
1437
+ PopoverItem,
1438
+ {
1439
+ active: subtitleMode === "off",
1440
+ onClick: () => {
1441
+ setSubtitleMode("off");
1442
+ setOpenMenu(null);
1443
+ },
1444
+ children: "Off"
1445
+ }
1446
+ ),
1447
+ parsed.subtitles.map((subtitle) => /* @__PURE__ */ jsx(
1448
+ PopoverItem,
1449
+ {
1450
+ active: subtitleMode === subtitle.srclang,
1451
+ onClick: () => {
1452
+ setSubtitleMode(subtitle.srclang);
1453
+ setOpenMenu(null);
1454
+ },
1455
+ children: subtitle.label
1456
+ },
1457
+ subtitle.srclang
1458
+ ))
1459
+ ] })
1460
+ ] }),
1461
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1462
+ /* @__PURE__ */ jsxs(
1463
+ "button",
1464
+ {
1465
+ type: "button",
1466
+ onClick: () => setOpenMenu(openMenu === "quality" ? null : "quality"),
1467
+ className: `flex h-8 items-center gap-1 rounded px-2 text-xs font-semibold transition hover:text-white/80 ${openMenu === "quality" ? "text-white" : "text-white/60"}`,
1468
+ "aria-label": "Quality",
1469
+ children: [
1470
+ /* @__PURE__ */ jsx(QualityIcon, { className: "size-4" }),
1471
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: qualities.find((q) => q.id === selectedQuality)?.label ?? "Auto" })
1472
+ ]
1473
+ }
1474
+ ),
1475
+ openMenu === "quality" && /* @__PURE__ */ jsx(PopoverMenu, { onClose: () => setOpenMenu(null), children: [...qualities].reverse().map((quality) => /* @__PURE__ */ jsxs(
1476
+ PopoverItem,
1477
+ {
1478
+ active: selectedQuality === quality.id,
1479
+ onClick: () => changeQuality(quality.id),
1480
+ children: [
1481
+ quality.label,
1482
+ quality.id === "auto" && /* @__PURE__ */ jsx("span", { className: "ml-1 text-[10px] text-white/40", children: "ABR" })
1483
+ ]
1484
+ },
1485
+ quality.id
1486
+ )) })
1487
+ ] }),
1488
+ /* @__PURE__ */ jsx(
1489
+ "button",
1490
+ {
1491
+ type: "button",
1492
+ onClick: toggleFullscreen,
1493
+ className: "grid size-8 place-items-center text-white/60 transition hover:scale-105 hover:text-white/80",
1494
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Fullscreen",
1495
+ children: /* @__PURE__ */ jsx(FullscreenIcon, { className: "size-7" })
1496
+ }
1497
+ )
1498
+ ] })
1499
+ ] })
1500
+ ] })
1501
+ ]
1502
+ }
1503
+ )
1504
+ ]
1505
+ }
1506
+ )
1507
+ }
1508
+ );
1509
+ }
1510
+ var VideoPlayer = Video;
1511
+ function PopoverMenu({ children, onClose }) {
1512
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1513
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-40", onClick: onClose }),
1514
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-full right-0 z-50 mb-2 min-w-[120px] overflow-hidden rounded-xl border border-white/10 bg-black/85 py-1 shadow-2xl backdrop-blur-xl", children })
1515
+ ] });
1516
+ }
1517
+ function PopoverItem({
1518
+ children,
1519
+ active,
1520
+ onClick
1521
+ }) {
1522
+ return /* @__PURE__ */ jsxs(
1523
+ "button",
1524
+ {
1525
+ type: "button",
1526
+ onClick,
1527
+ className: `flex w-full items-center gap-2 px-4 py-2 text-left text-sm font-medium transition hover:bg-white/10 ${active ? "text-white" : "text-white/55"}`,
1528
+ children: [
1529
+ /* @__PURE__ */ jsx("span", { className: `size-1.5 rounded-full ${active ? "bg-white" : "bg-transparent"}` }),
1530
+ children
1531
+ ]
1532
+ }
1533
+ );
1534
+ }
1535
+ function parseVideoChildren(children) {
1536
+ const parsed = {
1537
+ sources: [],
1538
+ subtitles: []
1539
+ };
1540
+ React.Children.forEach(children, (child) => {
1541
+ if (!React.isValidElement(child)) return;
1542
+ if (child.type === Sources) {
1543
+ const element = child;
1544
+ React.Children.forEach(element.props.children, (sourceChild) => {
1545
+ if (!React.isValidElement(sourceChild)) return;
1546
+ if (sourceChild.type !== Source) return;
1547
+ const sourceElement = sourceChild;
1548
+ parsed.sources.push(sourceElement.props);
1549
+ });
1550
+ }
1551
+ if (child.type === Subtitles) {
1552
+ const element = child;
1553
+ React.Children.forEach(element.props.children, (subtitleChild) => {
1554
+ if (!React.isValidElement(subtitleChild)) return;
1555
+ if (subtitleChild.type !== Subtitle) return;
1556
+ const subtitleElement = subtitleChild;
1557
+ parsed.subtitles.push(subtitleElement.props);
1558
+ });
1559
+ }
1560
+ if (child.type === Storyboard) {
1561
+ const element = child;
1562
+ const frames = [];
1563
+ React.Children.forEach(element.props.children, (frameChild) => {
1564
+ if (!React.isValidElement(frameChild)) return;
1565
+ if (frameChild.type !== StoryboardFrame) return;
1566
+ const frameElement = frameChild;
1567
+ frames.push(frameElement.props);
1568
+ });
1569
+ parsed.storyboard = {
1570
+ ...element.props.src !== void 0 && { src: element.props.src },
1571
+ ...element.props.fallbackImage !== void 0 && { fallbackImage: element.props.fallbackImage },
1572
+ ...element.props.width !== void 0 && { width: element.props.width },
1573
+ ...element.props.height !== void 0 && { height: element.props.height },
1574
+ frames
1575
+ };
1576
+ }
1577
+ });
1578
+ return parsed;
1579
+ }
1580
+ function inferSourceType(source) {
1581
+ if (source.type) return source.type;
1582
+ const src = source.src.toLowerCase();
1583
+ if (src.includes(".mpd") || src.includes("/dash")) return "dash";
1584
+ if (src.includes(".m3u8") || src.includes("/hls")) return "hls";
1585
+ return "file";
1586
+ }
1587
+ function findStoryboardCue(cues, time) {
1588
+ return cues.find((cue) => time >= cue.start && time <= cue.end) ?? null;
1589
+ }
1590
+ function parseStoryboardVtt(text, baseUrl) {
1591
+ const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1592
+ const cues = [];
1593
+ for (let index = 0; index < lines.length; index++) {
1594
+ const line = lines[index];
1595
+ if (!line || !line.includes("-->")) continue;
1596
+ const parts = line.split(/\s+-->\s+/);
1597
+ const startRaw = parts[0];
1598
+ const endRaw = parts[1];
1599
+ const imageLine = lines[index + 1];
1600
+ if (!startRaw || !endRaw || !imageLine) continue;
1601
+ const image = parseStoryboardImageLine(imageLine, baseUrl);
1602
+ cues.push({
1603
+ start: parseVttTimestamp(startRaw),
1604
+ end: parseVttTimestamp(endRaw),
1605
+ ...image
1606
+ });
1607
+ }
1608
+ return cues;
1609
+ }
1610
+ function parseStoryboardImageLine(line, baseUrl) {
1611
+ const splitIndex = line.indexOf("#xywh=");
1612
+ const imageRaw = splitIndex >= 0 ? line.slice(0, splitIndex) : line;
1613
+ const xywhRaw = splitIndex >= 0 ? line.slice(splitIndex + 6) : null;
1614
+ const image = new URL(imageRaw, baseUrl).href;
1615
+ if (!xywhRaw) {
1616
+ return { image };
1617
+ }
1618
+ const [x, y, w, h] = xywhRaw.split(",").map(Number);
1619
+ return {
1620
+ image,
1621
+ ...x !== void 0 && !isNaN(x) && { x },
1622
+ ...y !== void 0 && !isNaN(y) && { y },
1623
+ ...w !== void 0 && !isNaN(w) && { w },
1624
+ ...h !== void 0 && !isNaN(h) && { h }
1625
+ };
1626
+ }
1627
+ function parseVttTimestamp(value) {
1628
+ const normalized = value.replace(",", ".");
1629
+ const parts = normalized.split(":").map(Number);
1630
+ if (parts.length === 3) {
1631
+ return (parts[0] ?? 0) * 3600 + (parts[1] ?? 0) * 60 + (parts[2] ?? 0);
1632
+ }
1633
+ if (parts.length === 2) {
1634
+ return (parts[0] ?? 0) * 60 + (parts[1] ?? 0);
1635
+ }
1636
+ return Number(normalized) || 0;
1637
+ }
1638
+ function formatTime(seconds) {
1639
+ if (!Number.isFinite(seconds)) return "00:00";
1640
+ const safeSeconds = Math.max(0, Math.floor(seconds));
1641
+ const hours = Math.floor(safeSeconds / 3600);
1642
+ const minutes = Math.floor(safeSeconds % 3600 / 60);
1643
+ const secs = safeSeconds % 60;
1644
+ if (hours > 0) {
1645
+ return `${hours}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(
1646
+ 2,
1647
+ "0"
1648
+ )}`;
1649
+ }
1650
+ return `${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
1651
+ }
1652
+ function PlayIcon({ className }) {
1653
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", className, children: /* @__PURE__ */ jsx("path", { d: "M8 5.14v13.72c0 .76.84 1.22 1.48.8l10.2-6.86a.96.96 0 0 0 0-1.6L9.48 4.34A.96.96 0 0 0 8 5.14Z" }) });
1654
+ }
1655
+ function PauseIcon({ className }) {
1656
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", className, children: /* @__PURE__ */ jsx("path", { d: "M7 5.5A1.5 1.5 0 0 1 8.5 4h1A1.5 1.5 0 0 1 11 5.5v13A1.5 1.5 0 0 1 9.5 20h-1A1.5 1.5 0 0 1 7 18.5v-13Zm6 0A1.5 1.5 0 0 1 14.5 4h1A1.5 1.5 0 0 1 17 5.5v13a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5v-13Z" }) });
1657
+ }
1658
+ function ForwardIcon({ className }) {
1659
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", className, children: /* @__PURE__ */ jsx("path", { d: "M5 5.14v13.72c0 .76.84 1.22 1.48.8l8.7-5.86a.96.96 0 0 0 0-1.6L6.48 4.34A.96.96 0 0 0 5 5.14Zm12.5-.64A1.5 1.5 0 0 0 16 6v12a1.5 1.5 0 0 0 3 0V6a1.5 1.5 0 0 0-1.5-1.5Z" }) });
1660
+ }
1661
+ function VolumeIcon({ className }) {
1662
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", className, children: /* @__PURE__ */ jsx("path", { d: "M4 9.5A2.5 2.5 0 0 1 6.5 7H9l5-4v18l-5-4H6.5A2.5 2.5 0 0 1 4 14.5v-5Zm12.5-2.15a1 1 0 0 1 1.4.2 7.5 7.5 0 0 1 0 8.9 1 1 0 1 1-1.6-1.2 5.5 5.5 0 0 0 0-6.5 1 1 0 0 1 .2-1.4Zm3-2.25a1 1 0 0 1 1.4.2 11.25 11.25 0 0 1 0 13.4 1 1 0 1 1-1.6-1.2 9.25 9.25 0 0 0 0-11.2 1 1 0 0 1 .2-1.2Z" }) });
1663
+ }
1664
+ function MutedIcon({ className }) {
1665
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", className, children: /* @__PURE__ */ jsx("path", { d: "M4 9.5A2.5 2.5 0 0 1 6.5 7H9l5-4v18l-5-4H6.5A2.5 2.5 0 0 1 4 14.5v-5Zm13.7.1a1 1 0 0 1 1.4 0l1.4 1.4 1.4-1.4a1 1 0 1 1 1.4 1.4L21.9 12l1.4 1.4a1 1 0 0 1-1.4 1.4l-1.4-1.4-1.4 1.4a1 1 0 1 1-1.4-1.4L19.1 12l-1.4-1.4a1 1 0 0 1 0-1Z" }) });
1666
+ }
1667
+ function FullscreenIcon({ className }) {
1668
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", className, children: /* @__PURE__ */ jsx(
1669
+ "path",
1670
+ {
1671
+ d: "M8.5 4H5.75A1.75 1.75 0 0 0 4 5.75V8.5M15.5 4h2.75A1.75 1.75 0 0 1 20 5.75V8.5M20 15.5v2.75A1.75 1.75 0 0 1 18.25 20H15.5M8.5 20H5.75A1.75 1.75 0 0 1 4 18.25V15.5",
1672
+ stroke: "currentColor",
1673
+ strokeWidth: "2",
1674
+ strokeLinecap: "round"
1675
+ }
1676
+ ) });
1677
+ }
1678
+ function CaptionsIcon({ className }) {
1679
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", className, children: [
1680
+ /* @__PURE__ */ jsx("rect", { x: "2", y: "5", width: "20", height: "14", rx: "2", stroke: "currentColor", strokeWidth: "1.75" }),
1681
+ /* @__PURE__ */ jsx("path", { d: "M6 12h4M6 15h8M14 12h4", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" })
1682
+ ] });
1683
+ }
1684
+ function AudioIcon({ className }) {
1685
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", className, children: /* @__PURE__ */ jsx("path", { d: "M12 3a1 1 0 0 0-1.707-.707l-4 4H4a2 2 0 0 0-2 2v3.414a2 2 0 0 0 2 2h2.293l4 4A1 1 0 0 0 12 17V3ZM17.5 8.5a1 1 0 0 1 1.414 0 6 6 0 0 1 0 8.486 1 1 0 1 1-1.414-1.414 4 4 0 0 0 0-5.657 1 1 0 0 1 0-1.415ZM15.086 10.914a1 1 0 0 1 1.414 0 3 3 0 0 1 0 4.243 1 1 0 0 1-1.414-1.414 1 1 0 0 0 0-1.415 1 1 0 0 1 0-1.414Z" }) });
1686
+ }
1687
+ function QualityIcon({ className }) {
1688
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", className, children: [
1689
+ /* @__PURE__ */ jsx("rect", { x: "2", y: "7", width: "20", height: "13", rx: "2", stroke: "currentColor", strokeWidth: "1.75" }),
1690
+ /* @__PURE__ */ jsx("path", { d: "M8 4h8", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" }),
1691
+ /* @__PURE__ */ jsx("path", { d: "M9 13.5v-3l1.5 1.5L12 10v3.5M14 10.5h2.5M14 12.5h2", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
1692
+ ] });
1693
+ }
1694
+
1695
+ export { DropZone, FileUploader, ImageUploader, MediaUploader, ProgressBar, Source, Sources, Storyboard, StoryboardFrame, Subtitle, Subtitles, Video, VideoPlayer, VideoUploader, defaultTheme, resolveTheme };
1696
+ //# sourceMappingURL=index.js.map
11
1697
  //# sourceMappingURL=index.js.map