@geekapps/silo-elements-nextjs 0.0.1

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 +3 -0
  2. package/dist/FileUploader.d.ts.map +1 -0
  3. package/dist/FileUploader.js +43 -0
  4. package/dist/FileUploader.js.map +1 -0
  5. package/dist/ImageUploader.d.ts +3 -0
  6. package/dist/ImageUploader.d.ts.map +1 -0
  7. package/dist/ImageUploader.js +71 -0
  8. package/dist/ImageUploader.js.map +1 -0
  9. package/dist/MediaUploader.d.ts +3 -0
  10. package/dist/MediaUploader.d.ts.map +1 -0
  11. package/dist/MediaUploader.js +47 -0
  12. package/dist/MediaUploader.js.map +1 -0
  13. package/dist/VideoPlayer.d.ts +56 -0
  14. package/dist/VideoPlayer.d.ts.map +1 -0
  15. package/dist/VideoPlayer.js +738 -0
  16. package/dist/VideoPlayer.js.map +1 -0
  17. package/dist/VideoUploader.d.ts +3 -0
  18. package/dist/VideoUploader.d.ts.map +1 -0
  19. package/dist/VideoUploader.js +48 -0
  20. package/dist/VideoUploader.js.map +1 -0
  21. package/dist/components/DropZone.d.ts +17 -0
  22. package/dist/components/DropZone.d.ts.map +1 -0
  23. package/dist/components/DropZone.js +52 -0
  24. package/dist/components/DropZone.js.map +1 -0
  25. package/dist/components/ProgressBar.d.ts +9 -0
  26. package/dist/components/ProgressBar.d.ts.map +1 -0
  27. package/dist/components/ProgressBar.js +18 -0
  28. package/dist/components/ProgressBar.js.map +1 -0
  29. package/dist/index.d.ts +12 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +11 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/types.d.ts +129 -0
  34. package/dist/types.d.ts.map +1 -0
  35. package/dist/types.js +2 -0
  36. package/dist/types.js.map +1 -0
  37. package/dist/utils/format.d.ts +3 -0
  38. package/dist/utils/format.d.ts.map +1 -0
  39. package/dist/utils/format.js +29 -0
  40. package/dist/utils/format.js.map +1 -0
  41. package/dist/utils/theme.d.ts +5 -0
  42. package/dist/utils/theme.d.ts.map +1 -0
  43. package/dist/utils/theme.js +34 -0
  44. package/dist/utils/theme.js.map +1 -0
  45. package/package.json +39 -0
@@ -0,0 +1,738 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react";
4
+ import gsap from "gsap";
5
+ const AUTO_QUALITY = {
6
+ id: "auto",
7
+ label: "Auto",
8
+ type: "auto",
9
+ };
10
+ export function Sources(_props) {
11
+ return null;
12
+ }
13
+ export function Source(_props) {
14
+ return null;
15
+ }
16
+ export function Subtitles(_props) {
17
+ return null;
18
+ }
19
+ export function Subtitle(_props) {
20
+ return null;
21
+ }
22
+ export function Storyboard(_props) {
23
+ return null;
24
+ }
25
+ export function StoryboardFrame(_props) {
26
+ return null;
27
+ }
28
+ export function Video({ title, description, poster, children, className, autoHideControls = true, defaultVolume = 0.72, }) {
29
+ const parsed = useMemo(() => parseVideoChildren(children), [children]);
30
+ const initialSourceIndex = useMemo(() => {
31
+ const index = parsed.sources.findIndex((source) => source.default);
32
+ return index >= 0 ? index : 0;
33
+ }, [parsed.sources]);
34
+ const initialSubtitleMode = useMemo(() => {
35
+ const track = parsed.subtitles.find((subtitle) => subtitle.default);
36
+ return track?.srclang ?? "off";
37
+ }, [parsed.subtitles]);
38
+ const containerRef = useRef(null);
39
+ const chromeRef = useRef(null);
40
+ const playerRef = useRef(null);
41
+ const videoRef = useRef(null);
42
+ const progressRef = useRef(null);
43
+ const hlsRef = useRef(null);
44
+ const dashRef = useRef(null);
45
+ const hideTimerRef = useRef(null);
46
+ const [sourceIndex, setSourceIndex] = useState(initialSourceIndex);
47
+ const [qualities, setQualities] = useState([AUTO_QUALITY]);
48
+ const [selectedQuality, setSelectedQuality] = useState("auto");
49
+ const [audioTracks, setAudioTracks] = useState([]);
50
+ const [selectedAudio, setSelectedAudio] = useState(0);
51
+ const [openMenu, setOpenMenu] = useState(null);
52
+ const [subtitleMode, setSubtitleMode] = useState(initialSubtitleMode);
53
+ const [storyboardCues, setStoryboardCues] = useState([]);
54
+ const [preview, setPreview] = useState(null);
55
+ const [duration, setDuration] = useState(0);
56
+ const [currentTime, setCurrentTime] = useState(0);
57
+ const [bufferedTime, setBufferedTime] = useState(0);
58
+ const [isPlaying, setIsPlaying] = useState(false);
59
+ const [isLoading, setIsLoading] = useState(true);
60
+ const [controlsVisible, setControlsVisible] = useState(true);
61
+ const [volume, setVolume] = useState(defaultVolume);
62
+ const [isMuted, setIsMuted] = useState(false);
63
+ const [isFullscreen, setIsFullscreen] = useState(false);
64
+ const [error, setError] = useState(null);
65
+ const activeSource = parsed.sources[sourceIndex] ?? parsed.sources[0] ?? null;
66
+ const progressPercent = duration ? (currentTime / duration) * 100 : 0;
67
+ const bufferedPercent = duration ? (bufferedTime / duration) * 100 : 0;
68
+ const destroyMediaEngines = useCallback(() => {
69
+ if (hlsRef.current) {
70
+ hlsRef.current.destroy();
71
+ hlsRef.current = null;
72
+ }
73
+ if (dashRef.current) {
74
+ dashRef.current.reset();
75
+ dashRef.current = null;
76
+ }
77
+ }, []);
78
+ const applySubtitleMode = useCallback((mode) => {
79
+ const video = videoRef.current;
80
+ if (!video)
81
+ return;
82
+ Array.from(video.textTracks).forEach((track) => {
83
+ track.mode =
84
+ mode !== "off" && track.language === mode ? "showing" : "disabled";
85
+ });
86
+ }, []);
87
+ const showControlsTemporarily = useCallback(() => {
88
+ setControlsVisible(true);
89
+ if (hideTimerRef.current) {
90
+ window.clearTimeout(hideTimerRef.current);
91
+ }
92
+ const video = videoRef.current;
93
+ if (autoHideControls && video && !video.paused) {
94
+ hideTimerRef.current = window.setTimeout(() => {
95
+ setControlsVisible(false);
96
+ }, 2400);
97
+ }
98
+ }, [autoHideControls]);
99
+ useEffect(() => {
100
+ if (!containerRef.current)
101
+ return;
102
+ gsap.fromTo(containerRef.current, {
103
+ opacity: 0,
104
+ y: 36,
105
+ scale: 0.985,
106
+ filter: "blur(10px)",
107
+ }, {
108
+ opacity: 1,
109
+ y: 0,
110
+ scale: 1,
111
+ filter: "blur(0px)",
112
+ duration: 0.85,
113
+ ease: "power3.out",
114
+ });
115
+ }, []);
116
+ useEffect(() => {
117
+ if (!chromeRef.current)
118
+ return;
119
+ gsap.to(chromeRef.current, {
120
+ opacity: controlsVisible ? 1 : 0,
121
+ y: controlsVisible ? 0 : 10,
122
+ duration: 0.22,
123
+ ease: "power2.out",
124
+ });
125
+ }, [controlsVisible]);
126
+ useEffect(() => {
127
+ if (sourceIndex >= parsed.sources.length) {
128
+ setSourceIndex(initialSourceIndex);
129
+ }
130
+ }, [sourceIndex, parsed.sources.length, initialSourceIndex]);
131
+ useEffect(() => {
132
+ if (subtitleMode !== "off" &&
133
+ !parsed.subtitles.some((subtitle) => subtitle.srclang === subtitleMode)) {
134
+ setSubtitleMode(initialSubtitleMode);
135
+ }
136
+ }, [subtitleMode, parsed.subtitles, initialSubtitleMode]);
137
+ useEffect(() => {
138
+ const video = videoRef.current;
139
+ if (!video)
140
+ return;
141
+ const syncTime = () => {
142
+ setCurrentTime(video.currentTime || 0);
143
+ if (video.buffered.length > 0) {
144
+ setBufferedTime(video.buffered.end(video.buffered.length - 1));
145
+ }
146
+ };
147
+ const syncDuration = () => {
148
+ setDuration(Number.isFinite(video.duration) ? video.duration : 0);
149
+ applySubtitleMode(subtitleMode);
150
+ };
151
+ const onPlay = () => {
152
+ setIsPlaying(true);
153
+ showControlsTemporarily();
154
+ };
155
+ const onPause = () => {
156
+ setIsPlaying(false);
157
+ setControlsVisible(true);
158
+ };
159
+ const onWaiting = () => setIsLoading(true);
160
+ const onCanPlay = () => setIsLoading(false);
161
+ const onEnded = () => setControlsVisible(true);
162
+ video.addEventListener("timeupdate", syncTime);
163
+ video.addEventListener("progress", syncTime);
164
+ video.addEventListener("loadedmetadata", syncDuration);
165
+ video.addEventListener("durationchange", syncDuration);
166
+ video.addEventListener("play", onPlay);
167
+ video.addEventListener("pause", onPause);
168
+ video.addEventListener("waiting", onWaiting);
169
+ video.addEventListener("canplay", onCanPlay);
170
+ video.addEventListener("ended", onEnded);
171
+ return () => {
172
+ video.removeEventListener("timeupdate", syncTime);
173
+ video.removeEventListener("progress", syncTime);
174
+ video.removeEventListener("loadedmetadata", syncDuration);
175
+ video.removeEventListener("durationchange", syncDuration);
176
+ video.removeEventListener("play", onPlay);
177
+ video.removeEventListener("pause", onPause);
178
+ video.removeEventListener("waiting", onWaiting);
179
+ video.removeEventListener("canplay", onCanPlay);
180
+ video.removeEventListener("ended", onEnded);
181
+ };
182
+ }, [applySubtitleMode, subtitleMode, showControlsTemporarily]);
183
+ useEffect(() => {
184
+ const video = videoRef.current;
185
+ if (!video)
186
+ return;
187
+ video.volume = volume;
188
+ video.muted = isMuted || volume === 0;
189
+ }, [volume, isMuted]);
190
+ useEffect(() => {
191
+ applySubtitleMode(subtitleMode);
192
+ }, [subtitleMode, applySubtitleMode]);
193
+ useEffect(() => {
194
+ const onFullscreenChange = () => {
195
+ setIsFullscreen(Boolean(document.fullscreenElement));
196
+ };
197
+ document.addEventListener("fullscreenchange", onFullscreenChange);
198
+ return () => {
199
+ document.removeEventListener("fullscreenchange", onFullscreenChange);
200
+ };
201
+ }, []);
202
+ useEffect(() => {
203
+ let cancelled = false;
204
+ async function loadStoryboard() {
205
+ if (!parsed.storyboard) {
206
+ setStoryboardCues([]);
207
+ return;
208
+ }
209
+ if (parsed.storyboard.frames.length > 0) {
210
+ setStoryboardCues(parsed.storyboard.frames);
211
+ return;
212
+ }
213
+ if (!parsed.storyboard.src) {
214
+ setStoryboardCues([]);
215
+ return;
216
+ }
217
+ try {
218
+ const response = await fetch(parsed.storyboard.src);
219
+ if (!response.ok) {
220
+ throw new Error("Storyboard not found");
221
+ }
222
+ const text = await response.text();
223
+ const cues = parseStoryboardVtt(text, new URL(parsed.storyboard.src, window.location.href).href);
224
+ if (!cancelled) {
225
+ setStoryboardCues(cues);
226
+ }
227
+ }
228
+ catch {
229
+ if (!cancelled && parsed.storyboard.fallbackImage) {
230
+ setStoryboardCues([
231
+ {
232
+ start: 0,
233
+ end: Number.MAX_SAFE_INTEGER,
234
+ image: parsed.storyboard.fallbackImage,
235
+ w: parsed.storyboard.width ?? 160,
236
+ h: parsed.storyboard.height ?? 90,
237
+ },
238
+ ]);
239
+ }
240
+ }
241
+ }
242
+ loadStoryboard();
243
+ return () => {
244
+ cancelled = true;
245
+ };
246
+ }, [parsed.storyboard]);
247
+ useEffect(() => {
248
+ const video = videoRef.current;
249
+ let cancelled = false;
250
+ if (!video || !activeSource) {
251
+ return;
252
+ }
253
+ destroyMediaEngines();
254
+ setError(null);
255
+ setIsLoading(true);
256
+ setCurrentTime(0);
257
+ setBufferedTime(0);
258
+ setDuration(0);
259
+ setSelectedQuality("auto");
260
+ setQualities([AUTO_QUALITY]);
261
+ setAudioTracks([]);
262
+ setSelectedAudio(0);
263
+ setOpenMenu(null);
264
+ video.pause();
265
+ video.removeAttribute("src");
266
+ video.load();
267
+ const sourceType = inferSourceType(activeSource);
268
+ if (sourceType === "dash") {
269
+ void (async () => {
270
+ try {
271
+ const dashModule = await import("dashjs");
272
+ if (cancelled)
273
+ return;
274
+ const dashjs = dashModule;
275
+ const dash = dashjs.MediaPlayer().create();
276
+ dashRef.current = dash;
277
+ dash.updateSettings({
278
+ streaming: {
279
+ abr: {
280
+ autoSwitchBitrate: {
281
+ video: true,
282
+ },
283
+ },
284
+ },
285
+ });
286
+ dash.on(dashjs.MediaPlayer.events.STREAM_INITIALIZED, () => {
287
+ const bitrates = dash.getBitrateInfoListFor("video") ?? [];
288
+ setQualities([
289
+ AUTO_QUALITY,
290
+ ...bitrates.map((item, index) => ({
291
+ id: `dash-${index}`,
292
+ label: item.height
293
+ ? `${item.height}p`
294
+ : `${Math.round((item.bitrate ?? 0) / 1000)} kbps`,
295
+ type: "dash",
296
+ index,
297
+ })),
298
+ ]);
299
+ setIsLoading(false);
300
+ });
301
+ dash.on(dashjs.MediaPlayer.events.ERROR, () => {
302
+ setError("Não foi possível reproduzir o stream MPEG-DASH.");
303
+ setIsLoading(false);
304
+ });
305
+ dash.initialize(video, activeSource.src, false);
306
+ }
307
+ catch {
308
+ if (!cancelled) {
309
+ setError("Não foi possível carregar o player MPEG-DASH.");
310
+ setIsLoading(false);
311
+ }
312
+ }
313
+ })();
314
+ return () => {
315
+ cancelled = true;
316
+ destroyMediaEngines();
317
+ };
318
+ }
319
+ if (sourceType === "hls") {
320
+ void (async () => {
321
+ try {
322
+ const HlsModule = await import("hls.js");
323
+ if (cancelled)
324
+ return;
325
+ const Hls = HlsModule.default;
326
+ if (Hls.isSupported()) {
327
+ const hls = new Hls({
328
+ enableWorker: true,
329
+ maxBufferLength: 30,
330
+ maxMaxBufferLength: 60,
331
+ maxBufferSize: 60 * 1000 * 1000,
332
+ });
333
+ hlsRef.current = hls;
334
+ hls.loadSource(activeSource.src);
335
+ hls.attachMedia(video);
336
+ hls.on(Hls.Events.MANIFEST_PARSED, (_, data) => {
337
+ const levels = data.levels ?? hls.levels ?? [];
338
+ setQualities([
339
+ AUTO_QUALITY,
340
+ ...levels.map((level, index) => ({
341
+ id: `hls-${index}`,
342
+ label: level.height
343
+ ? `${level.height}p`
344
+ : `${Math.round((level.bitrate ?? 0) / 1000)} kbps`,
345
+ type: "hls",
346
+ index,
347
+ })),
348
+ ]);
349
+ const tracks = hls.audioTracks ?? [];
350
+ if (tracks.length > 1) {
351
+ setAudioTracks(tracks.map((t, i) => ({
352
+ id: i,
353
+ label: t.name ?? t.lang ?? `Track ${i + 1}`,
354
+ })));
355
+ setSelectedAudio(hls.audioTrack ?? 0);
356
+ }
357
+ setIsLoading(false);
358
+ });
359
+ hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, (_, data) => {
360
+ const tracks = data.audioTracks ?? [];
361
+ if (tracks.length > 1) {
362
+ setAudioTracks(tracks.map((t, i) => ({
363
+ id: i,
364
+ label: t.name ?? t.lang ?? `Track ${i + 1}`,
365
+ })));
366
+ }
367
+ });
368
+ hls.on(Hls.Events.ERROR, (_, data) => {
369
+ if (!data.fatal)
370
+ return;
371
+ if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
372
+ hls.recoverMediaError();
373
+ return;
374
+ }
375
+ setError("Não foi possível reproduzir o stream HLS.");
376
+ setIsLoading(false);
377
+ });
378
+ return;
379
+ }
380
+ if (video.canPlayType("application/vnd.apple.mpegurl")) {
381
+ video.src = activeSource.src;
382
+ video.load();
383
+ setIsLoading(false);
384
+ return;
385
+ }
386
+ setError("Este navegador não suporta HLS.");
387
+ setIsLoading(false);
388
+ }
389
+ catch {
390
+ if (!cancelled) {
391
+ setError("Não foi possível carregar o player HLS.");
392
+ setIsLoading(false);
393
+ }
394
+ }
395
+ })();
396
+ return () => {
397
+ cancelled = true;
398
+ destroyMediaEngines();
399
+ };
400
+ }
401
+ video.src = activeSource.src;
402
+ video.load();
403
+ setIsLoading(false);
404
+ return () => {
405
+ cancelled = true;
406
+ video.removeAttribute("src");
407
+ video.load();
408
+ };
409
+ }, [activeSource, destroyMediaEngines]);
410
+ const togglePlay = useCallback(async () => {
411
+ const video = videoRef.current;
412
+ if (!video)
413
+ return;
414
+ try {
415
+ if (video.paused) {
416
+ await video.play();
417
+ }
418
+ else {
419
+ video.pause();
420
+ }
421
+ }
422
+ catch {
423
+ setError("O navegador bloqueou a reprodução automática.");
424
+ }
425
+ }, []);
426
+ const seekRelative = useCallback((seconds) => {
427
+ const video = videoRef.current;
428
+ if (!video)
429
+ return;
430
+ video.currentTime = Math.max(0, Math.min(video.currentTime + seconds, video.duration || 0));
431
+ }, []);
432
+ const toggleFullscreen = useCallback(async () => {
433
+ const player = playerRef.current;
434
+ if (!player)
435
+ return;
436
+ try {
437
+ if (!document.fullscreenElement) {
438
+ await player.requestFullscreen();
439
+ }
440
+ else {
441
+ await document.exitFullscreen();
442
+ }
443
+ }
444
+ catch {
445
+ setError("Não foi possível alterar o modo fullscreen.");
446
+ }
447
+ }, []);
448
+ const changeAudio = useCallback((trackId) => {
449
+ setSelectedAudio(trackId);
450
+ setOpenMenu(null);
451
+ if (hlsRef.current) {
452
+ hlsRef.current.audioTrack = trackId;
453
+ }
454
+ }, []);
455
+ const changeQuality = useCallback((qualityId) => {
456
+ const option = qualities.find((quality) => quality.id === qualityId);
457
+ if (!option)
458
+ return;
459
+ setSelectedQuality(qualityId);
460
+ setOpenMenu(null);
461
+ if (option.type === "auto") {
462
+ if (hlsRef.current) {
463
+ hlsRef.current.currentLevel = -1;
464
+ }
465
+ if (dashRef.current) {
466
+ dashRef.current.updateSettings({
467
+ streaming: {
468
+ abr: {
469
+ autoSwitchBitrate: {
470
+ video: true,
471
+ },
472
+ },
473
+ },
474
+ });
475
+ }
476
+ return;
477
+ }
478
+ if (option.type === "hls" && hlsRef.current && option.index != null) {
479
+ hlsRef.current.currentLevel = option.index;
480
+ return;
481
+ }
482
+ if (option.type === "dash" && dashRef.current && option.index != null) {
483
+ dashRef.current.updateSettings({
484
+ streaming: {
485
+ abr: {
486
+ autoSwitchBitrate: {
487
+ video: false,
488
+ },
489
+ },
490
+ },
491
+ });
492
+ dashRef.current.setQualityFor("video", option.index);
493
+ }
494
+ }, [qualities]);
495
+ const handleProgressPointerMove = useCallback((event) => {
496
+ const progress = progressRef.current;
497
+ if (!progress || !duration || storyboardCues.length === 0) {
498
+ setPreview(null);
499
+ return;
500
+ }
501
+ const rect = progress.getBoundingClientRect();
502
+ const x = Math.max(0, Math.min(event.clientX - rect.left, rect.width));
503
+ const time = (x / rect.width) * duration;
504
+ const cue = findStoryboardCue(storyboardCues, time);
505
+ if (!cue) {
506
+ setPreview(null);
507
+ return;
508
+ }
509
+ setPreview({
510
+ cue,
511
+ time,
512
+ left: Math.max(80, Math.min(x, rect.width - 80)),
513
+ });
514
+ }, [duration, storyboardCues]);
515
+ const handleProgressPointerLeave = useCallback(() => {
516
+ setPreview(null);
517
+ }, []);
518
+ const handleProgressPointerDown = useCallback((event) => {
519
+ const video = videoRef.current;
520
+ const progress = progressRef.current;
521
+ if (!video || !progress || !duration)
522
+ return;
523
+ const rect = progress.getBoundingClientRect();
524
+ const x = Math.max(0, Math.min(event.clientX - rect.left, rect.width));
525
+ const nextTime = (x / rect.width) * duration;
526
+ video.currentTime = nextTime;
527
+ setCurrentTime(nextTime);
528
+ }, [duration]);
529
+ const handleKeyDown = useCallback((event) => {
530
+ const target = event.target;
531
+ const tagName = target.tagName.toLowerCase();
532
+ if (["input", "select", "button"].includes(tagName)) {
533
+ return;
534
+ }
535
+ if (event.key === " ") {
536
+ event.preventDefault();
537
+ togglePlay();
538
+ }
539
+ if (event.key === "ArrowRight") {
540
+ seekRelative(10);
541
+ }
542
+ if (event.key === "ArrowLeft") {
543
+ seekRelative(-10);
544
+ }
545
+ if (event.key.toLowerCase() === "f") {
546
+ toggleFullscreen();
547
+ }
548
+ if (event.key.toLowerCase() === "m") {
549
+ setIsMuted((value) => !value);
550
+ }
551
+ }, [seekRelative, toggleFullscreen, togglePlay]);
552
+ if (!activeSource) {
553
+ return (_jsxs("div", { className: "rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-700", children: ["O componente Video precisa de pelo menos um", " ", _jsx("code", { children: "<Source />" }), "."] }));
554
+ }
555
+ return (_jsx("div", { ref: containerRef, className: `mx-auto w-full max-w-6xl ${className ?? ""}`, children: _jsxs("div", { ref: playerRef, tabIndex: 0, onKeyDown: handleKeyDown, onMouseMove: showControlsTemporarily, onMouseLeave: () => {
556
+ if (isPlaying && autoHideControls) {
557
+ setControlsVisible(false);
558
+ }
559
+ }, 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", children: [_jsx("video", { ref: videoRef, className: "h-full w-full object-contain", poster: poster, playsInline: true, preload: "metadata", crossOrigin: "anonymous", children: parsed.subtitles.map((subtitle) => (_jsx("track", { kind: "subtitles", src: subtitle.src, srcLang: subtitle.srclang, label: subtitle.label, default: subtitle.default }, `${activeSource.src}-${subtitle.srclang}`))) }), _jsx("div", { className: `pointer-events-none absolute inset-0 z-10 grid place-items-center transition ${isPlaying ? "opacity-0" : "opacity-100"}`, children: _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: _jsx(PlayIcon, { className: "ml-1 size-9" }) }) }), isLoading && (_jsx("div", { className: "pointer-events-none absolute inset-0 z-20 grid place-items-center bg-black/10", children: _jsx("div", { className: "size-9 animate-spin rounded-full border-2 border-white/25 border-t-white" }) })), error && (_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 })), _jsxs("div", { ref: chromeRef, onClick: togglePlay, 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"}`, children: [_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: [_jsxs("div", { children: [title && (_jsx("h1", { className: "text-lg font-bold tracking-wide md:text-xl", children: title })), description && (_jsx("p", { className: "mt-1 text-sm font-medium text-white/85", children: description }))] }), parsed.sources.length > 1 && (_jsx("select", { value: String(sourceIndex), onChange: (e) => setSourceIndex(Number(e.target.value)), "aria-label": "Video source", 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", children: parsed.sources.map((source, index) => (_jsx("option", { value: String(index), className: "text-black", children: source.label ?? source.type ?? `Source ${index + 1}` }, `${source.src}-${index}`))) }))] }), _jsxs("footer", { onClick: (e) => e.stopPropagation(), className: "px-7 pb-7 text-white md:px-9 md:pb-8", children: [_jsxs("div", { ref: progressRef, onPointerMove: handleProgressPointerMove, onPointerLeave: handleProgressPointerLeave, onPointerDown: handleProgressPointerDown, className: "relative mb-6 h-5 cursor-pointer", children: [preview && (_jsxs("div", { 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", style: { left: preview.left }, children: [_jsx("div", { className: "overflow-hidden rounded-md bg-neutral-900", style: {
560
+ width: preview.cue.w ?? 160,
561
+ height: preview.cue.h ?? 90,
562
+ backgroundImage: `url(${preview.cue.image})`,
563
+ backgroundPosition: preview.cue.x != null && preview.cue.y != null
564
+ ? `-${preview.cue.x}px -${preview.cue.y}px`
565
+ : "center",
566
+ backgroundSize: preview.cue.x != null ? "auto" : "cover",
567
+ backgroundRepeat: "no-repeat",
568
+ } }), _jsx("div", { className: "pt-1 text-center text-[11px] font-semibold text-white/80", children: formatTime(preview.time) })] })), _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: [_jsx("div", { className: "absolute inset-y-0 left-0 bg-white/30", style: { width: `${bufferedPercent}%` } }), _jsx("div", { className: "absolute inset-y-0 left-0 bg-white/85", style: { width: `${progressPercent}%` } })] })] }), _jsxs("div", { className: "flex items-center justify-between gap-5", children: [_jsxs("div", { className: "flex items-center gap-5", children: [_jsx("button", { type: "button", onClick: togglePlay, className: "grid size-8 place-items-center text-white transition hover:scale-105 hover:text-white/80", "aria-label": isPlaying ? "Pause" : "Play", children: isPlaying ? (_jsx(PauseIcon, { className: "size-7" })) : (_jsx(PlayIcon, { className: "size-7" })) }), _jsx("button", { type: "button", onClick: () => seekRelative(10), className: "hidden size-8 place-items-center text-white transition hover:scale-105 hover:text-white/80 sm:grid", "aria-label": "Forward 10 seconds", children: _jsx(ForwardIcon, { className: "size-7" }) }), _jsxs("div", { className: "hidden items-center gap-2 text-sm font-semibold text-white/75 sm:flex", children: [_jsx("span", { children: formatTime(currentTime) }), _jsx("span", { className: "text-white/35", children: "/" }), _jsx("span", { children: formatTime(duration) })] })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => setIsMuted((value) => !value), className: "grid size-8 place-items-center text-white transition hover:scale-105 hover:text-white/80", "aria-label": isMuted ? "Unmute" : "Mute", children: isMuted || volume === 0 ? (_jsx(MutedIcon, { className: "size-6" })) : (_jsx(VolumeIcon, { className: "size-6" })) }), _jsx("input", { type: "range", min: "0", max: "1", step: "0.01", value: isMuted ? 0 : volume, onChange: (event) => {
569
+ const nextVolume = Number(event.target.value);
570
+ setVolume(nextVolume);
571
+ setIsMuted(nextVolume === 0);
572
+ }, className: "hidden h-1 w-20 accent-white md:block", "aria-label": "Audio level" }), _jsx("div", { className: "mx-1 hidden h-4 w-px bg-white/20 md:block" }), audioTracks.length > 1 && (_jsxs("div", { className: "relative", children: [_jsx("button", { type: "button", onClick: () => setOpenMenu(openMenu === "audio" ? null : "audio"), className: `grid size-8 place-items-center rounded transition hover:text-white/80 ${openMenu === "audio" ? "text-white" : "text-white/60"}`, "aria-label": "Audio track", children: _jsx(AudioIcon, { className: "size-5" }) }), openMenu === "audio" && (_jsx(PopoverMenu, { onClose: () => setOpenMenu(null), children: audioTracks.map((track) => (_jsx(PopoverItem, { active: selectedAudio === track.id, onClick: () => changeAudio(track.id), children: track.label }, track.id))) }))] })), parsed.subtitles.length > 0 && (_jsxs("div", { className: "relative", children: [_jsx("button", { type: "button", onClick: () => setOpenMenu(openMenu === "captions" ? null : "captions"), className: `grid size-8 place-items-center rounded transition hover:text-white/80 ${subtitleMode !== "off" || openMenu === "captions" ? "text-white" : "text-white/60"}`, "aria-label": "Captions", children: _jsx(CaptionsIcon, { className: "size-5" }) }), openMenu === "captions" && (_jsxs(PopoverMenu, { onClose: () => setOpenMenu(null), children: [_jsx(PopoverItem, { active: subtitleMode === "off", onClick: () => { setSubtitleMode("off"); setOpenMenu(null); }, children: "Off" }), parsed.subtitles.map((subtitle) => (_jsx(PopoverItem, { active: subtitleMode === subtitle.srclang, onClick: () => { setSubtitleMode(subtitle.srclang); setOpenMenu(null); }, children: subtitle.label }, subtitle.srclang)))] }))] })), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => setOpenMenu(openMenu === "quality" ? null : "quality"), 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"}`, "aria-label": "Quality", children: [_jsx(QualityIcon, { className: "size-4" }), _jsx("span", { className: "hidden sm:inline", children: qualities.find((q) => q.id === selectedQuality)?.label ?? "Auto" })] }), openMenu === "quality" && (_jsx(PopoverMenu, { onClose: () => setOpenMenu(null), children: [...qualities].reverse().map((quality) => (_jsxs(PopoverItem, { active: selectedQuality === quality.id, onClick: () => changeQuality(quality.id), children: [quality.label, quality.id === "auto" && (_jsx("span", { className: "ml-1 text-[10px] text-white/40", children: "ABR" }))] }, quality.id))) }))] }), _jsx("button", { type: "button", onClick: toggleFullscreen, className: "grid size-8 place-items-center text-white/60 transition hover:scale-105 hover:text-white/80", "aria-label": isFullscreen ? "Exit fullscreen" : "Fullscreen", children: _jsx(FullscreenIcon, { className: "size-7" }) })] })] })] })] })] }) }));
573
+ }
574
+ export const VideoPlayer = Video;
575
+ function PopoverMenu({ children, onClose }) {
576
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40", onClick: onClose }), _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: children })] }));
577
+ }
578
+ function PopoverItem({ children, active, onClick, }) {
579
+ return (_jsxs("button", { type: "button", onClick: onClick, 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"}`, children: [_jsx("span", { className: `size-1.5 rounded-full ${active ? "bg-white" : "bg-transparent"}` }), children] }));
580
+ }
581
+ function parseVideoChildren(children) {
582
+ const parsed = {
583
+ sources: [],
584
+ subtitles: [],
585
+ };
586
+ React.Children.forEach(children, (child) => {
587
+ if (!React.isValidElement(child))
588
+ return;
589
+ if (child.type === Sources) {
590
+ const element = child;
591
+ React.Children.forEach(element.props.children, (sourceChild) => {
592
+ if (!React.isValidElement(sourceChild))
593
+ return;
594
+ if (sourceChild.type !== Source)
595
+ return;
596
+ const sourceElement = sourceChild;
597
+ parsed.sources.push(sourceElement.props);
598
+ });
599
+ }
600
+ if (child.type === Subtitles) {
601
+ const element = child;
602
+ React.Children.forEach(element.props.children, (subtitleChild) => {
603
+ if (!React.isValidElement(subtitleChild))
604
+ return;
605
+ if (subtitleChild.type !== Subtitle)
606
+ return;
607
+ const subtitleElement = subtitleChild;
608
+ parsed.subtitles.push(subtitleElement.props);
609
+ });
610
+ }
611
+ if (child.type === Storyboard) {
612
+ const element = child;
613
+ const frames = [];
614
+ React.Children.forEach(element.props.children, (frameChild) => {
615
+ if (!React.isValidElement(frameChild))
616
+ return;
617
+ if (frameChild.type !== StoryboardFrame)
618
+ return;
619
+ const frameElement = frameChild;
620
+ frames.push(frameElement.props);
621
+ });
622
+ parsed.storyboard = {
623
+ ...(element.props.src !== undefined && { src: element.props.src }),
624
+ ...(element.props.fallbackImage !== undefined && { fallbackImage: element.props.fallbackImage }),
625
+ ...(element.props.width !== undefined && { width: element.props.width }),
626
+ ...(element.props.height !== undefined && { height: element.props.height }),
627
+ frames,
628
+ };
629
+ }
630
+ });
631
+ return parsed;
632
+ }
633
+ function inferSourceType(source) {
634
+ if (source.type)
635
+ return source.type;
636
+ const src = source.src.toLowerCase();
637
+ if (src.includes(".mpd") || src.includes("/dash"))
638
+ return "dash";
639
+ if (src.includes(".m3u8") || src.includes("/hls"))
640
+ return "hls";
641
+ return "file";
642
+ }
643
+ function findStoryboardCue(cues, time) {
644
+ return cues.find((cue) => time >= cue.start && time <= cue.end) ?? null;
645
+ }
646
+ function parseStoryboardVtt(text, baseUrl) {
647
+ const lines = text
648
+ .split(/\r?\n/)
649
+ .map((line) => line.trim())
650
+ .filter(Boolean);
651
+ const cues = [];
652
+ for (let index = 0; index < lines.length; index++) {
653
+ const line = lines[index];
654
+ if (!line || !line.includes("-->"))
655
+ continue;
656
+ const parts = line.split(/\s+-->\s+/);
657
+ const startRaw = parts[0];
658
+ const endRaw = parts[1];
659
+ const imageLine = lines[index + 1];
660
+ if (!startRaw || !endRaw || !imageLine)
661
+ continue;
662
+ const image = parseStoryboardImageLine(imageLine, baseUrl);
663
+ cues.push({
664
+ start: parseVttTimestamp(startRaw),
665
+ end: parseVttTimestamp(endRaw),
666
+ ...image,
667
+ });
668
+ }
669
+ return cues;
670
+ }
671
+ function parseStoryboardImageLine(line, baseUrl) {
672
+ const splitIndex = line.indexOf("#xywh=");
673
+ const imageRaw = splitIndex >= 0 ? line.slice(0, splitIndex) : line;
674
+ const xywhRaw = splitIndex >= 0 ? line.slice(splitIndex + 6) : null;
675
+ const image = new URL(imageRaw, baseUrl).href;
676
+ if (!xywhRaw) {
677
+ return { image };
678
+ }
679
+ const [x, y, w, h] = xywhRaw.split(",").map(Number);
680
+ return {
681
+ image,
682
+ ...(x !== undefined && !isNaN(x) && { x }),
683
+ ...(y !== undefined && !isNaN(y) && { y }),
684
+ ...(w !== undefined && !isNaN(w) && { w }),
685
+ ...(h !== undefined && !isNaN(h) && { h }),
686
+ };
687
+ }
688
+ function parseVttTimestamp(value) {
689
+ const normalized = value.replace(",", ".");
690
+ const parts = normalized.split(":").map(Number);
691
+ if (parts.length === 3) {
692
+ return (parts[0] ?? 0) * 3600 + (parts[1] ?? 0) * 60 + (parts[2] ?? 0);
693
+ }
694
+ if (parts.length === 2) {
695
+ return (parts[0] ?? 0) * 60 + (parts[1] ?? 0);
696
+ }
697
+ return Number(normalized) || 0;
698
+ }
699
+ function formatTime(seconds) {
700
+ if (!Number.isFinite(seconds))
701
+ return "00:00";
702
+ const safeSeconds = Math.max(0, Math.floor(seconds));
703
+ const hours = Math.floor(safeSeconds / 3600);
704
+ const minutes = Math.floor((safeSeconds % 3600) / 60);
705
+ const secs = safeSeconds % 60;
706
+ if (hours > 0) {
707
+ return `${hours}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
708
+ }
709
+ return `${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
710
+ }
711
+ function PlayIcon({ className }) {
712
+ return (_jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", className: className, children: _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" }) }));
713
+ }
714
+ function PauseIcon({ className }) {
715
+ return (_jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", className: className, children: _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" }) }));
716
+ }
717
+ function ForwardIcon({ className }) {
718
+ return (_jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", className: className, children: _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" }) }));
719
+ }
720
+ function VolumeIcon({ className }) {
721
+ return (_jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", className: className, children: _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" }) }));
722
+ }
723
+ function MutedIcon({ className }) {
724
+ return (_jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", className: className, children: _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" }) }));
725
+ }
726
+ function FullscreenIcon({ className }) {
727
+ return (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", className: className, children: _jsx("path", { 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", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }));
728
+ }
729
+ function CaptionsIcon({ className }) {
730
+ return (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", className: className, children: [_jsx("rect", { x: "2", y: "5", width: "20", height: "14", rx: "2", stroke: "currentColor", strokeWidth: "1.75" }), _jsx("path", { d: "M6 12h4M6 15h8M14 12h4", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" })] }));
731
+ }
732
+ function AudioIcon({ className }) {
733
+ return (_jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", className: className, children: _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" }) }));
734
+ }
735
+ function QualityIcon({ className }) {
736
+ return (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", className: className, children: [_jsx("rect", { x: "2", y: "7", width: "20", height: "13", rx: "2", stroke: "currentColor", strokeWidth: "1.75" }), _jsx("path", { d: "M8 4h8", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" }), _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" })] }));
737
+ }
738
+ //# sourceMappingURL=VideoPlayer.js.map