analytica-frontend-lib 1.1.9 → 1.1.11

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.
@@ -145,6 +145,85 @@ var formatTime = (seconds) => {
145
145
  const secs = Math.floor(seconds % 60);
146
146
  return `${mins}:${secs.toString().padStart(2, "0")}`;
147
147
  };
148
+ var ProgressBar = ({
149
+ currentTime,
150
+ duration,
151
+ progressPercentage,
152
+ onSeek
153
+ }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "px-4 pb-2", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
154
+ "input",
155
+ {
156
+ type: "range",
157
+ min: 0,
158
+ max: duration || 100,
159
+ value: currentTime,
160
+ onChange: (e) => onSeek(parseFloat(e.target.value)),
161
+ className: "w-full h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer slider:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary-500",
162
+ "aria-label": "Video progress",
163
+ style: {
164
+ background: `linear-gradient(to right, var(--color-primary-700) ${progressPercentage}%, var(--color-secondary-300) ${progressPercentage}%)`
165
+ }
166
+ }
167
+ ) });
168
+ var VolumeControls = ({
169
+ volume,
170
+ isMuted,
171
+ onVolumeChange,
172
+ onToggleMute
173
+ }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", children: [
174
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
175
+ IconButton_default,
176
+ {
177
+ icon: isMuted ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_phosphor_react.SpeakerSlash, { size: 24 }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_phosphor_react.SpeakerHigh, { size: 24 }),
178
+ onClick: onToggleMute,
179
+ "aria-label": isMuted ? "Unmute" : "Mute",
180
+ className: "!bg-transparent !text-white hover:!bg-white/20"
181
+ }
182
+ ),
183
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
184
+ "input",
185
+ {
186
+ type: "range",
187
+ min: 0,
188
+ max: 100,
189
+ value: Math.round(volume * 100),
190
+ onChange: (e) => onVolumeChange(parseInt(e.target.value)),
191
+ className: "w-20 h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500",
192
+ "aria-label": "Volume control",
193
+ style: {
194
+ background: `linear-gradient(to right, var(--color-primary-700) ${volume * 100}%, var(--color-secondary-300) ${volume * 100}%)`
195
+ }
196
+ }
197
+ )
198
+ ] });
199
+ var SpeedMenu = ({
200
+ showSpeedMenu,
201
+ playbackRate,
202
+ onToggleMenu,
203
+ onSpeedChange
204
+ }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative", children: [
205
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
206
+ IconButton_default,
207
+ {
208
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_phosphor_react.DotsThreeVertical, { size: 24 }),
209
+ onClick: onToggleMenu,
210
+ "aria-label": "Playback speed",
211
+ className: "!bg-transparent !text-white hover:!bg-white/20"
212
+ }
213
+ ),
214
+ showSpeedMenu && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute bottom-12 right-0 bg-black/90 rounded-lg p-2 min-w-20", children: [0.5, 0.75, 1, 1.25, 1.5, 2].map((speed) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
215
+ "button",
216
+ {
217
+ onClick: () => onSpeedChange(speed),
218
+ className: `block w-full text-left px-3 py-1 text-sm rounded hover:bg-white/20 transition-colors ${playbackRate === speed ? "text-primary-400" : "text-white"}`,
219
+ children: [
220
+ speed,
221
+ "x"
222
+ ]
223
+ },
224
+ speed
225
+ )) })
226
+ ] });
148
227
  var VideoPlayer = ({
149
228
  src,
150
229
  poster,
@@ -169,10 +248,51 @@ var VideoPlayer = ({
169
248
  const [showControls, setShowControls] = (0, import_react2.useState)(true);
170
249
  const [hasCompleted, setHasCompleted] = (0, import_react2.useState)(false);
171
250
  const [showCaptions, setShowCaptions] = (0, import_react2.useState)(false);
251
+ (0, import_react2.useEffect)(() => {
252
+ setHasCompleted(false);
253
+ }, [src]);
172
254
  const [playbackRate, setPlaybackRate] = (0, import_react2.useState)(1);
173
255
  const [showSpeedMenu, setShowSpeedMenu] = (0, import_react2.useState)(false);
174
256
  const lastSaveTimeRef = (0, import_react2.useRef)(0);
175
257
  const trackRef = (0, import_react2.useRef)(null);
258
+ const controlsTimeoutRef = (0, import_react2.useRef)(null);
259
+ const lastMousePositionRef = (0, import_react2.useRef)({ x: 0, y: 0 });
260
+ const mouseMoveTimeoutRef = (0, import_react2.useRef)(null);
261
+ const clearControlsTimeout = (0, import_react2.useCallback)(() => {
262
+ if (controlsTimeoutRef.current) {
263
+ clearTimeout(controlsTimeoutRef.current);
264
+ controlsTimeoutRef.current = null;
265
+ }
266
+ }, []);
267
+ const clearMouseMoveTimeout = (0, import_react2.useCallback)(() => {
268
+ if (mouseMoveTimeoutRef.current) {
269
+ clearTimeout(mouseMoveTimeoutRef.current);
270
+ mouseMoveTimeoutRef.current = null;
271
+ }
272
+ }, []);
273
+ const showControlsWithTimer = (0, import_react2.useCallback)(() => {
274
+ setShowControls(true);
275
+ clearControlsTimeout();
276
+ if (isPlaying) {
277
+ const timeout = isFullscreen ? 2e3 : 3e3;
278
+ controlsTimeoutRef.current = window.setTimeout(() => {
279
+ setShowControls(false);
280
+ }, timeout);
281
+ }
282
+ }, [isPlaying, isFullscreen, clearControlsTimeout]);
283
+ const handleMouseMove = (0, import_react2.useCallback)(
284
+ (event) => {
285
+ const currentX = event.clientX;
286
+ const currentY = event.clientY;
287
+ const lastPos = lastMousePositionRef.current;
288
+ const hasMoved = Math.abs(currentX - lastPos.x) > 5 || Math.abs(currentY - lastPos.y) > 5;
289
+ if (hasMoved) {
290
+ lastMousePositionRef.current = { x: currentX, y: currentY };
291
+ showControlsWithTimer();
292
+ }
293
+ },
294
+ [showControlsWithTimer]
295
+ );
176
296
  (0, import_react2.useEffect)(() => {
177
297
  if (videoRef.current) {
178
298
  videoRef.current.volume = volume;
@@ -180,84 +300,129 @@ var VideoPlayer = ({
180
300
  }
181
301
  }, [volume, isMuted]);
182
302
  (0, import_react2.useEffect)(() => {
183
- if (!autoSave || !storageKey) return;
184
- const raw = localStorage.getItem(`${storageKey}-${src}`);
185
- const saved = raw !== null ? Number(raw) : NaN;
186
- const hasValidSaved = Number.isFinite(saved) && saved >= 0;
187
- const hasValidInitial = Number.isFinite(initialTime) && initialTime >= 0;
188
- let start;
189
- if (hasValidInitial) {
190
- start = initialTime;
191
- } else if (hasValidSaved) {
192
- start = saved;
303
+ const video = videoRef.current;
304
+ if (!video) return;
305
+ const onPlay = () => setIsPlaying(true);
306
+ const onPause = () => setIsPlaying(false);
307
+ const onEnded = () => setIsPlaying(false);
308
+ video.addEventListener("play", onPlay);
309
+ video.addEventListener("pause", onPause);
310
+ video.addEventListener("ended", onEnded);
311
+ return () => {
312
+ video.removeEventListener("play", onPlay);
313
+ video.removeEventListener("pause", onPause);
314
+ video.removeEventListener("ended", onEnded);
315
+ };
316
+ }, []);
317
+ (0, import_react2.useEffect)(() => {
318
+ if (isPlaying) {
319
+ showControlsWithTimer();
193
320
  } else {
194
- start = void 0;
321
+ clearControlsTimeout();
322
+ setShowControls(true);
195
323
  }
324
+ }, [isPlaying, showControlsWithTimer, clearControlsTimeout]);
325
+ (0, import_react2.useEffect)(() => {
326
+ const handleFullscreenChange = () => {
327
+ const isCurrentlyFullscreen = !!document.fullscreenElement;
328
+ setIsFullscreen(isCurrentlyFullscreen);
329
+ if (isCurrentlyFullscreen) {
330
+ showControlsWithTimer();
331
+ }
332
+ };
333
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
334
+ return () => {
335
+ document.removeEventListener("fullscreenchange", handleFullscreenChange);
336
+ };
337
+ }, [showControlsWithTimer]);
338
+ const getInitialTime = (0, import_react2.useCallback)(() => {
339
+ if (!autoSave || !storageKey) {
340
+ return Number.isFinite(initialTime) && initialTime >= 0 ? initialTime : void 0;
341
+ }
342
+ const saved = Number(localStorage.getItem(`${storageKey}-${src}`) || NaN);
343
+ const hasValidInitial = Number.isFinite(initialTime) && initialTime >= 0;
344
+ const hasValidSaved = Number.isFinite(saved) && saved >= 0;
345
+ if (hasValidInitial) return initialTime;
346
+ if (hasValidSaved) return saved;
347
+ return void 0;
348
+ }, [autoSave, storageKey, src, initialTime]);
349
+ (0, import_react2.useEffect)(() => {
350
+ const start = getInitialTime();
196
351
  if (start !== void 0 && videoRef.current) {
197
352
  videoRef.current.currentTime = start;
198
353
  setCurrentTime(start);
199
354
  }
200
- }, [src, storageKey, autoSave, initialTime]);
201
- const saveProgress = (0, import_react2.useCallback)(() => {
202
- if (!autoSave || !storageKey) return;
203
- const now = Date.now();
204
- if (now - lastSaveTimeRef.current > 5e3) {
205
- localStorage.setItem(`${storageKey}-${src}`, currentTime.toString());
206
- lastSaveTimeRef.current = now;
207
- }
208
- }, [autoSave, storageKey, src, currentTime]);
209
- const togglePlayPause = (0, import_react2.useCallback)(() => {
210
- if (videoRef.current) {
211
- if (isPlaying) {
212
- videoRef.current.pause();
213
- } else {
214
- videoRef.current.play();
355
+ }, [getInitialTime]);
356
+ const saveProgress = (0, import_react2.useCallback)(
357
+ (time) => {
358
+ if (!autoSave || !storageKey) return;
359
+ const now = Date.now();
360
+ if (now - lastSaveTimeRef.current > 5e3) {
361
+ localStorage.setItem(`${storageKey}-${src}`, time.toString());
362
+ lastSaveTimeRef.current = now;
215
363
  }
216
- setIsPlaying(!isPlaying);
364
+ },
365
+ [autoSave, storageKey, src]
366
+ );
367
+ const togglePlayPause = (0, import_react2.useCallback)(async () => {
368
+ const video = videoRef.current;
369
+ if (!video) return;
370
+ if (!video.paused) {
371
+ video.pause();
372
+ return;
217
373
  }
218
- }, [isPlaying]);
374
+ try {
375
+ await video.play();
376
+ } catch {
377
+ }
378
+ }, []);
219
379
  const handleVolumeChange = (0, import_react2.useCallback)(
220
380
  (newVolume) => {
221
- if (videoRef.current) {
222
- const volumeValue = newVolume / 100;
223
- videoRef.current.volume = volumeValue;
224
- setVolume(volumeValue);
225
- if (volumeValue === 0) {
226
- videoRef.current.muted = true;
227
- setIsMuted(true);
228
- } else if (isMuted) {
229
- videoRef.current.muted = false;
230
- setIsMuted(false);
231
- }
381
+ const video = videoRef.current;
382
+ if (!video) return;
383
+ const volumeValue = newVolume / 100;
384
+ video.volume = volumeValue;
385
+ setVolume(volumeValue);
386
+ const shouldMute = volumeValue === 0;
387
+ const shouldUnmute = volumeValue > 0 && isMuted;
388
+ if (shouldMute) {
389
+ video.muted = true;
390
+ setIsMuted(true);
391
+ } else if (shouldUnmute) {
392
+ video.muted = false;
393
+ setIsMuted(false);
232
394
  }
233
395
  },
234
396
  [isMuted]
235
397
  );
236
398
  const toggleMute = (0, import_react2.useCallback)(() => {
237
- if (videoRef.current) {
238
- if (isMuted) {
239
- const restoreVolume = volume > 0 ? volume : 0.5;
240
- videoRef.current.volume = restoreVolume;
241
- videoRef.current.muted = false;
242
- setVolume(restoreVolume);
243
- setIsMuted(false);
244
- } else {
245
- videoRef.current.muted = true;
246
- setIsMuted(true);
247
- }
399
+ const video = videoRef.current;
400
+ if (!video) return;
401
+ if (isMuted) {
402
+ const restoreVolume = volume > 0 ? volume : 0.5;
403
+ video.volume = restoreVolume;
404
+ video.muted = false;
405
+ setVolume(restoreVolume);
406
+ setIsMuted(false);
407
+ } else {
408
+ video.muted = true;
409
+ setIsMuted(true);
248
410
  }
249
411
  }, [isMuted, volume]);
412
+ const handleSeek = (0, import_react2.useCallback)((newTime) => {
413
+ const video = videoRef.current;
414
+ if (video) {
415
+ video.currentTime = newTime;
416
+ }
417
+ }, []);
250
418
  const toggleFullscreen = (0, import_react2.useCallback)(() => {
251
419
  const container = videoRef.current?.parentElement;
252
420
  if (!container) return;
253
- if (!isFullscreen) {
254
- if (container.requestFullscreen) {
255
- container.requestFullscreen();
256
- }
257
- } else if (document.exitFullscreen) {
421
+ if (!isFullscreen && container.requestFullscreen) {
422
+ container.requestFullscreen();
423
+ } else if (isFullscreen && document.exitFullscreen) {
258
424
  document.exitFullscreen();
259
425
  }
260
- setIsFullscreen(!isFullscreen);
261
426
  }, [isFullscreen]);
262
427
  const handleSpeedChange = (0, import_react2.useCallback)((speed) => {
263
428
  if (videoRef.current) {
@@ -270,39 +435,43 @@ var VideoPlayer = ({
270
435
  setShowSpeedMenu(!showSpeedMenu);
271
436
  }, [showSpeedMenu]);
272
437
  const toggleCaptions = (0, import_react2.useCallback)(() => {
273
- if (!trackRef.current?.track) return;
438
+ if (!trackRef.current?.track || !subtitles) return;
274
439
  const newShowCaptions = !showCaptions;
275
440
  setShowCaptions(newShowCaptions);
276
- trackRef.current.track.mode = newShowCaptions ? "showing" : "hidden";
277
- }, [showCaptions]);
278
- const handleTimeUpdate = (0, import_react2.useCallback)(() => {
279
- if (videoRef.current) {
280
- const current = videoRef.current.currentTime;
281
- setCurrentTime(current);
282
- saveProgress();
283
- onTimeUpdate?.(current);
284
- if (duration > 0) {
285
- const progressPercent = current / duration * 100;
286
- onProgress?.(progressPercent);
287
- if (progressPercent >= 95 && !hasCompleted) {
288
- setHasCompleted(true);
289
- onVideoComplete?.();
290
- }
441
+ trackRef.current.track.mode = newShowCaptions && subtitles ? "showing" : "hidden";
442
+ }, [showCaptions, subtitles]);
443
+ const checkVideoCompletion = (0, import_react2.useCallback)(
444
+ (progressPercent) => {
445
+ if (progressPercent >= 95 && !hasCompleted) {
446
+ setHasCompleted(true);
447
+ onVideoComplete?.();
291
448
  }
449
+ },
450
+ [hasCompleted, onVideoComplete]
451
+ );
452
+ const handleTimeUpdate = (0, import_react2.useCallback)(() => {
453
+ const video = videoRef.current;
454
+ if (!video) return;
455
+ const current = video.currentTime;
456
+ setCurrentTime(current);
457
+ saveProgress(current);
458
+ onTimeUpdate?.(current);
459
+ if (duration > 0) {
460
+ const progressPercent = current / duration * 100;
461
+ onProgress?.(progressPercent);
462
+ checkVideoCompletion(progressPercent);
292
463
  }
293
- }, [
294
- duration,
295
- saveProgress,
296
- onTimeUpdate,
297
- onProgress,
298
- onVideoComplete,
299
- hasCompleted
300
- ]);
464
+ }, [duration, saveProgress, onTimeUpdate, onProgress, checkVideoCompletion]);
301
465
  const handleLoadedMetadata = (0, import_react2.useCallback)(() => {
302
466
  if (videoRef.current) {
303
467
  setDuration(videoRef.current.duration);
304
468
  }
305
469
  }, []);
470
+ (0, import_react2.useEffect)(() => {
471
+ if (trackRef.current?.track) {
472
+ trackRef.current.track.mode = showCaptions && subtitles ? "showing" : "hidden";
473
+ }
474
+ }, [subtitles, showCaptions]);
306
475
  (0, import_react2.useEffect)(() => {
307
476
  const handleVisibilityChange = () => {
308
477
  if (document.hidden && isPlaying && videoRef.current) {
@@ -321,9 +490,78 @@ var VideoPlayer = ({
321
490
  return () => {
322
491
  document.removeEventListener("visibilitychange", handleVisibilityChange);
323
492
  window.removeEventListener("blur", handleBlur);
493
+ clearControlsTimeout();
494
+ clearMouseMoveTimeout();
324
495
  };
325
- }, [isPlaying]);
496
+ }, [isPlaying, clearControlsTimeout, clearMouseMoveTimeout]);
326
497
  const progressPercentage = duration > 0 ? currentTime / duration * 100 : 0;
498
+ const getTopControlsOpacity = (0, import_react2.useCallback)(() => {
499
+ if (isFullscreen) {
500
+ return showControls ? "opacity-100" : "opacity-0";
501
+ }
502
+ return !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100";
503
+ }, [isFullscreen, showControls, isPlaying]);
504
+ const getBottomControlsOpacity = (0, import_react2.useCallback)(() => {
505
+ if (isFullscreen) {
506
+ return showControls ? "opacity-100" : "opacity-0";
507
+ }
508
+ return !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100";
509
+ }, [isFullscreen, showControls, isPlaying]);
510
+ const handleVideoKeyDown = (0, import_react2.useCallback)(
511
+ (e) => {
512
+ if (e.key) {
513
+ e.stopPropagation();
514
+ showControlsWithTimer();
515
+ }
516
+ switch (e.key) {
517
+ case " ":
518
+ case "Enter":
519
+ e.preventDefault();
520
+ togglePlayPause();
521
+ break;
522
+ case "ArrowLeft":
523
+ e.preventDefault();
524
+ if (videoRef.current) {
525
+ videoRef.current.currentTime -= 10;
526
+ }
527
+ break;
528
+ case "ArrowRight":
529
+ e.preventDefault();
530
+ if (videoRef.current) {
531
+ videoRef.current.currentTime += 10;
532
+ }
533
+ break;
534
+ case "ArrowUp":
535
+ e.preventDefault();
536
+ handleVolumeChange(Math.min(100, volume * 100 + 10));
537
+ break;
538
+ case "ArrowDown":
539
+ e.preventDefault();
540
+ handleVolumeChange(Math.max(0, volume * 100 - 10));
541
+ break;
542
+ case "m":
543
+ case "M":
544
+ e.preventDefault();
545
+ toggleMute();
546
+ break;
547
+ case "f":
548
+ case "F":
549
+ e.preventDefault();
550
+ toggleFullscreen();
551
+ break;
552
+ default:
553
+ break;
554
+ }
555
+ },
556
+ [
557
+ showControlsWithTimer,
558
+ togglePlayPause,
559
+ handleVolumeChange,
560
+ volume,
561
+ toggleMute,
562
+ toggleFullscreen
563
+ ]
564
+ );
327
565
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: cn("flex flex-col", className), children: [
328
566
  (title || subtitleText) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "bg-subject-1 rounded-t-xl px-8 py-4 flex items-end justify-between min-h-20", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-1", children: [
329
567
  title && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -350,12 +588,17 @@ var VideoPlayer = ({
350
588
  )
351
589
  ] }) }),
352
590
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
353
- "div",
591
+ "section",
354
592
  {
355
593
  className: cn(
356
594
  "relative w-full bg-background overflow-hidden group",
357
- title || subtitleText ? "rounded-b-xl" : "rounded-xl"
595
+ title || subtitleText ? "rounded-b-xl" : "rounded-xl",
596
+ // Hide cursor when controls are hidden and video is playing in fullscreen
597
+ isFullscreen && isPlaying && !showControls ? "cursor-none" : "cursor-default"
358
598
  ),
599
+ "aria-label": title ? `Video player: ${title}` : "Video player",
600
+ onMouseMove: isFullscreen ? handleMouseMove : showControlsWithTimer,
601
+ onMouseEnter: showControlsWithTimer,
359
602
  children: [
360
603
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
361
604
  "video",
@@ -368,39 +611,7 @@ var VideoPlayer = ({
368
611
  onTimeUpdate: handleTimeUpdate,
369
612
  onLoadedMetadata: handleLoadedMetadata,
370
613
  onClick: togglePlayPause,
371
- onKeyDown: (e) => {
372
- if (e.key) {
373
- setShowControls(true);
374
- }
375
- if (e.key === " " || e.key === "Enter") {
376
- e.preventDefault();
377
- togglePlayPause();
378
- }
379
- if (e.key === "ArrowLeft" && videoRef.current) {
380
- e.preventDefault();
381
- videoRef.current.currentTime -= 10;
382
- }
383
- if (e.key === "ArrowRight" && videoRef.current) {
384
- e.preventDefault();
385
- videoRef.current.currentTime += 10;
386
- }
387
- if (e.key === "ArrowUp") {
388
- e.preventDefault();
389
- handleVolumeChange(Math.min(100, volume * 100 + 10));
390
- }
391
- if (e.key === "ArrowDown") {
392
- e.preventDefault();
393
- handleVolumeChange(Math.max(0, volume * 100 - 10));
394
- }
395
- if (e.key === "m" || e.key === "M") {
396
- e.preventDefault();
397
- toggleMute();
398
- }
399
- if (e.key === "f" || e.key === "F") {
400
- e.preventDefault();
401
- toggleFullscreen();
402
- }
403
- },
614
+ onKeyDown: handleVideoKeyDown,
404
615
  tabIndex: 0,
405
616
  "aria-label": title ? `Video: ${title}` : "Video player",
406
617
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -408,9 +619,9 @@ var VideoPlayer = ({
408
619
  {
409
620
  ref: trackRef,
410
621
  kind: "captions",
411
- src: subtitles || "data:text/vtt;charset=utf-8,WEBVTT%0A%0ANOTE%20No%20captions%20available",
412
- srcLang: "en",
413
- label: subtitles ? "Subtitles" : "No captions available",
622
+ src: subtitles || "data:text/vtt;charset=utf-8,WEBVTT",
623
+ srcLang: "pt-br",
624
+ label: subtitles ? "Legendas em Portugu\xEAs" : "Sem legendas dispon\xEDveis",
414
625
  default: false
415
626
  }
416
627
  )
@@ -430,9 +641,9 @@ var VideoPlayer = ({
430
641
  {
431
642
  className: cn(
432
643
  "absolute top-0 left-0 right-0 p-4 bg-gradient-to-b from-black/70 to-transparent transition-opacity",
433
- !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100"
644
+ getTopControlsOpacity()
434
645
  ),
435
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ml-auto block", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
646
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex justify-start", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
436
647
  IconButton_default,
437
648
  {
438
649
  icon: isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_phosphor_react.ArrowsInSimple, { size: 24 }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_phosphor_react.ArrowsOutSimple, { size: 24 }),
@@ -448,29 +659,18 @@ var VideoPlayer = ({
448
659
  {
449
660
  className: cn(
450
661
  "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent transition-opacity",
451
- !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100"
662
+ getBottomControlsOpacity()
452
663
  ),
453
664
  children: [
454
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "px-4 pb-2", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
455
- "input",
665
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
666
+ ProgressBar,
456
667
  {
457
- type: "range",
458
- min: 0,
459
- max: duration || 100,
460
- value: currentTime,
461
- onChange: (e) => {
462
- const newTime = parseFloat(e.target.value);
463
- if (videoRef.current) {
464
- videoRef.current.currentTime = newTime;
465
- }
466
- },
467
- className: "w-full h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer slider:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary-500",
468
- "aria-label": "Video progress",
469
- style: {
470
- background: `linear-gradient(to right, #2271C4 ${progressPercentage}%, #D5D4D4 ${progressPercentage}%)`
471
- }
668
+ currentTime,
669
+ duration,
670
+ progressPercentage,
671
+ onSeek: handleSeek
472
672
  }
473
- ) }),
673
+ ),
474
674
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between px-4 pb-4", children: [
475
675
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-4", children: [
476
676
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -482,32 +682,15 @@ var VideoPlayer = ({
482
682
  className: "!bg-transparent !text-white hover:!bg-white/20"
483
683
  }
484
684
  ),
485
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", children: [
486
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
487
- IconButton_default,
488
- {
489
- icon: isMuted ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_phosphor_react.SpeakerSlash, { size: 24 }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_phosphor_react.SpeakerHigh, { size: 24 }),
490
- onClick: toggleMute,
491
- "aria-label": isMuted ? "Unmute" : "Mute",
492
- className: "!bg-transparent !text-white hover:!bg-white/20"
493
- }
494
- ),
495
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
496
- "input",
497
- {
498
- type: "range",
499
- min: 0,
500
- max: 100,
501
- value: Math.round(volume * 100),
502
- onChange: (e) => handleVolumeChange(parseInt(e.target.value)),
503
- className: "w-20 h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500",
504
- "aria-label": "Volume control",
505
- style: {
506
- background: `linear-gradient(to right, #2271C4 ${volume * 100}%, #D5D4D4 ${volume * 100}%)`
507
- }
508
- }
509
- )
510
- ] }),
685
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
686
+ VolumeControls,
687
+ {
688
+ volume,
689
+ isMuted,
690
+ onVolumeChange: handleVolumeChange,
691
+ onToggleMute: toggleMute
692
+ }
693
+ ),
511
694
  subtitles && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
512
695
  IconButton_default,
513
696
  {
@@ -526,29 +709,15 @@ var VideoPlayer = ({
526
709
  formatTime(duration)
527
710
  ] })
528
711
  ] }),
529
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative", children: [
530
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
531
- IconButton_default,
532
- {
533
- icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_phosphor_react.DotsThreeVertical, { size: 24 }),
534
- onClick: toggleSpeedMenu,
535
- "aria-label": "Playback speed",
536
- className: "!bg-transparent !text-white hover:!bg-white/20"
537
- }
538
- ),
539
- showSpeedMenu && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute bottom-12 right-0 bg-black/90 rounded-lg p-2 min-w-20", children: [0.5, 0.75, 1, 1.25, 1.5, 2].map((speed) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
540
- "button",
541
- {
542
- onClick: () => handleSpeedChange(speed),
543
- className: `block w-full text-left px-3 py-1 text-sm rounded hover:bg-white/20 transition-colors ${playbackRate === speed ? "text-primary-400" : "text-white"}`,
544
- children: [
545
- speed,
546
- "x"
547
- ]
548
- },
549
- speed
550
- )) })
551
- ] }) })
712
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
713
+ SpeedMenu,
714
+ {
715
+ showSpeedMenu,
716
+ playbackRate,
717
+ onToggleMenu: toggleSpeedMenu,
718
+ onSpeedChange: handleSpeedChange
719
+ }
720
+ ) })
552
721
  ] })
553
722
  ]
554
723
  }