analytica-frontend-lib
Version:
Repositório público dos componentes utilizados nas plataformas da Analytica Ensino
917 lines (899 loc) • 36.2 kB
JavaScript
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
var _chunk7CYRHMOFjs = require('./chunk-7CYRHMOF.js');
var _chunkZHWRUIVEjs = require('./chunk-ZHWRUIVE.js');
var _chunkBVUPM6EYjs = require('./chunk-BVUPM6EY.js');
var _chunkANT66KVKjs = require('./chunk-ANT66KVK.js');
var _chunkTN3AYOMVjs = require('./chunk-TN3AYOMV.js');
// src/components/VideoPlayer/VideoPlayer.tsx
var _react = require('react');
var _reactdom = require('react-dom');
var _Play = require('@phosphor-icons/react/dist/csr/Play');
var _Pause = require('@phosphor-icons/react/dist/csr/Pause');
var _SpeakerHigh = require('@phosphor-icons/react/dist/csr/SpeakerHigh');
var _SpeakerSlash = require('@phosphor-icons/react/dist/csr/SpeakerSlash');
var _ArrowsOutSimple = require('@phosphor-icons/react/dist/csr/ArrowsOutSimple');
var _ArrowsInSimple = require('@phosphor-icons/react/dist/csr/ArrowsInSimple');
var _ClosedCaptioning = require('@phosphor-icons/react/dist/csr/ClosedCaptioning');
var _DotsThreeVertical = require('@phosphor-icons/react/dist/csr/DotsThreeVertical');
var _jsxruntime = require('react/jsx-runtime');
var CONTROLS_HIDE_TIMEOUT = 3e3;
var LEAVE_HIDE_TIMEOUT = 1e3;
var INIT_DELAY = 100;
var formatTime = (seconds) => {
if (!seconds || Number.isNaN(seconds)) return "0:00";
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, "0")}`;
};
var ProgressBar = ({
currentTime,
duration,
progressPercentage,
onSeek,
className = "px-4 pb-2"
}) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"input",
{
type: "range",
min: 0,
max: duration || 100,
value: currentTime,
onChange: (e) => onSeek(Number.parseFloat(e.target.value)),
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",
"aria-label": "Video progress",
style: {
background: `linear-gradient(to right, var(--color-primary-700) ${progressPercentage}%, var(--color-secondary-300) ${progressPercentage}%)`
}
}
) });
var VolumeControls = ({
volume,
isMuted,
onVolumeChange,
onToggleMute,
iconSize = 24,
showSlider = true
}) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkBVUPM6EYjs.IconButton_default,
{
icon: isMuted ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _SpeakerSlash.SpeakerSlashIcon, { size: iconSize }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _SpeakerHigh.SpeakerHighIcon, { size: iconSize }),
onClick: onToggleMute,
"aria-label": isMuted ? "Unmute" : "Mute",
className: "!bg-transparent !text-white hover:!bg-white/20"
}
),
showSlider && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"input",
{
type: "range",
min: 0,
max: 100,
value: Math.round(volume * 100),
onChange: (e) => onVolumeChange(Number.parseInt(e.target.value)),
className: "w-20 h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500",
"aria-label": "Volume control",
style: {
background: `linear-gradient(to right, var(--color-primary-700) ${volume * 100}%, var(--color-secondary-300) ${volume * 100}%)`
}
}
)
] });
var SpeedMenu = ({
showSpeedMenu,
playbackRate,
onToggleMenu,
onSpeedChange,
isFullscreen,
iconSize = 24,
isTinyMobile = false
}) => {
const buttonRef = _react.useRef.call(void 0, null);
const speedMenuContainerRef = _react.useRef.call(void 0, null);
const speedMenuRef = _react.useRef.call(void 0, null);
const getMenuPosition = () => {
if (!buttonRef.current) return { top: 0, left: 0 };
const rect = buttonRef.current.getBoundingClientRect();
const menuHeight = isTinyMobile ? 150 : 180;
const menuWidth = isTinyMobile ? 60 : 80;
const padding = isTinyMobile ? 4 : 8;
return {
// Fixed coords are viewport-based — no scroll offsets.
top: Math.max(padding, rect.top - menuHeight),
left: Math.max(padding, rect.right - menuWidth)
};
};
const position = getMenuPosition();
_react.useEffect.call(void 0, () => {
const handleClickOutside = (event) => {
const target = event.target;
const isOutsideContainer = speedMenuContainerRef.current && !speedMenuContainerRef.current.contains(target);
const isOutsideMenu = speedMenuRef.current && !speedMenuRef.current.contains(target);
if (isOutsideContainer && isOutsideMenu) {
onToggleMenu();
}
};
if (showSpeedMenu) {
document.addEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [showSpeedMenu, onToggleMenu]);
const menuContent = /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"div",
{
ref: speedMenuRef,
role: "menu",
"aria-label": "Playback speed",
className: isFullscreen ? "absolute bottom-12 right-0 bg-background border border-border-100 rounded-lg shadow-lg p-2 min-w-24 z-[9999]" : "fixed bg-background border border-border-100 rounded-lg shadow-lg p-2 min-w-24 z-[9999]",
style: isFullscreen ? void 0 : {
top: `${position.top}px`,
left: `${position.left}px`
},
children: [0.5, 0.75, 1, 1.25, 1.5, 2].map((speed) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
"button",
{
role: "menuitemradio",
"aria-checked": playbackRate === speed,
onClick: () => onSpeedChange(speed),
className: `block w-full text-left px-3 py-1 text-sm rounded hover:bg-border-50 transition-colors ${playbackRate === speed ? "bg-primary-950 text-secondary-100 font-medium" : "text-text-950"}`,
children: [
speed,
"x"
]
},
speed
))
}
);
const portalContent = showSpeedMenu && globalThis.window !== void 0 && globalThis.document !== void 0 && !!_optionalChain([globalThis, 'access', _ => _.document, 'optionalAccess', _2 => _2.body]) ? _reactdom.createPortal.call(void 0, menuContent, globalThis.document.body) : null;
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "relative", ref: speedMenuContainerRef, children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkBVUPM6EYjs.IconButton_default,
{
ref: buttonRef,
icon: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _DotsThreeVertical.DotsThreeVerticalIcon, { size: iconSize }),
onClick: onToggleMenu,
"aria-label": "Playback speed",
"aria-haspopup": "menu",
"aria-expanded": showSpeedMenu,
className: "!bg-transparent !text-white hover:!bg-white/20"
}
),
showSpeedMenu && (isFullscreen ? menuContent : portalContent)
] });
};
var VideoPlayer = ({
src,
poster,
subtitles,
title,
subtitle: subtitleText,
initialTime = 0,
onTimeUpdate,
onProgress,
onVideoComplete,
className,
autoSave = true,
storageKey = "video-progress",
userId,
downloadContent,
showDownloadButton = false,
onDownloadStart,
onDownloadComplete,
onDownloadError
}) => {
const videoRef = _react.useRef.call(void 0, null);
const { isUltraSmallMobile, isTinyMobile } = _chunkZHWRUIVEjs.useMobile.call(void 0, );
const [isPlaying, setIsPlaying] = _react.useState.call(void 0, false);
const [hasStarted, setHasStarted] = _react.useState.call(void 0, false);
const [currentTime, setCurrentTime] = _react.useState.call(void 0, 0);
const [duration, setDuration] = _react.useState.call(void 0, 0);
const [isMuted, setIsMuted] = _react.useState.call(void 0, false);
const [volume, setVolume] = _react.useState.call(void 0, 1);
const [isFullscreen, setIsFullscreen] = _react.useState.call(void 0, false);
const [showControls, setShowControls] = _react.useState.call(void 0, true);
const [hasCompleted, setHasCompleted] = _react.useState.call(void 0, false);
const [showCaptions, setShowCaptions] = _react.useState.call(void 0, false);
const [subtitlesValidation, setSubtitlesValidation] = _react.useState.call(void 0, "idle");
_react.useEffect.call(void 0, () => {
setHasCompleted(false);
setHasStarted(false);
}, [src]);
const [playbackRate, setPlaybackRate] = _react.useState.call(void 0, 1);
const [showSpeedMenu, setShowSpeedMenu] = _react.useState.call(void 0, false);
const lastSaveTimeRef = _react.useRef.call(void 0, 0);
const trackRef = _react.useRef.call(void 0, null);
const controlsTimeoutRef = _react.useRef.call(void 0, null);
const lastMousePositionRef = _react.useRef.call(void 0, { x: 0, y: 0 });
const isUserInteracting = _react.useCallback.call(void 0, () => {
if (showSpeedMenu) {
return true;
}
const activeElement = document.activeElement;
const videoContainer = _optionalChain([videoRef, 'access', _3 => _3.current, 'optionalAccess', _4 => _4.parentElement]);
if (activeElement && _optionalChain([videoContainer, 'optionalAccess', _5 => _5.contains, 'call', _6 => _6(activeElement)])) {
if (activeElement === videoRef.current) {
return false;
}
const isControl = activeElement.matches("button, input, [tabindex]");
if (isControl) {
return true;
}
}
return false;
}, [showSpeedMenu]);
const clearControlsTimeout = _react.useCallback.call(void 0, () => {
if (controlsTimeoutRef.current) {
clearTimeout(controlsTimeoutRef.current);
controlsTimeoutRef.current = null;
}
}, []);
const showControlsWithTimer = _react.useCallback.call(void 0, () => {
setShowControls(true);
clearControlsTimeout();
if (isFullscreen) {
if (isPlaying) {
controlsTimeoutRef.current = globalThis.setTimeout(() => {
setShowControls(false);
}, CONTROLS_HIDE_TIMEOUT);
}
} else {
controlsTimeoutRef.current = globalThis.setTimeout(() => {
setShowControls(false);
}, CONTROLS_HIDE_TIMEOUT);
}
}, [isFullscreen, isPlaying, clearControlsTimeout]);
const handleMouseMove = _react.useCallback.call(void 0,
(event) => {
const currentX = event.clientX;
const currentY = event.clientY;
const lastPos = lastMousePositionRef.current;
const hasMoved = Math.abs(currentX - lastPos.x) > 5 || Math.abs(currentY - lastPos.y) > 5;
if (hasMoved) {
lastMousePositionRef.current = { x: currentX, y: currentY };
showControlsWithTimer();
}
},
[showControlsWithTimer]
);
const handleMouseEnter = _react.useCallback.call(void 0, () => {
showControlsWithTimer();
}, [showControlsWithTimer]);
const handleMouseLeave = _react.useCallback.call(void 0, () => {
const userInteracting = isUserInteracting();
clearControlsTimeout();
if (!isFullscreen && !userInteracting) {
controlsTimeoutRef.current = globalThis.setTimeout(() => {
setShowControls(false);
}, LEAVE_HIDE_TIMEOUT);
}
}, [isFullscreen, clearControlsTimeout, isUserInteracting]);
_react.useEffect.call(void 0, () => {
if (videoRef.current) {
videoRef.current.volume = volume;
videoRef.current.muted = isMuted;
}
}, [volume, isMuted]);
_react.useEffect.call(void 0, () => {
const video = videoRef.current;
if (!video) return;
const onPlay = () => {
setIsPlaying(true);
setHasStarted(true);
};
const onPause = () => setIsPlaying(false);
const onEnded = () => setIsPlaying(false);
video.addEventListener("play", onPlay);
video.addEventListener("pause", onPause);
video.addEventListener("ended", onEnded);
return () => {
video.removeEventListener("play", onPlay);
video.removeEventListener("pause", onPause);
video.removeEventListener("ended", onEnded);
};
}, []);
_react.useEffect.call(void 0, () => {
const video = videoRef.current;
if (!video) return;
video.setAttribute("playsinline", "");
video.setAttribute("webkit-playsinline", "");
}, []);
_react.useEffect.call(void 0, () => {
if (isPlaying) {
showControlsWithTimer();
} else {
clearControlsTimeout();
if (isFullscreen) {
setShowControls(true);
} else {
showControlsWithTimer();
}
}
}, [isPlaying, isFullscreen, showControlsWithTimer, clearControlsTimeout]);
_react.useEffect.call(void 0, () => {
const video = videoRef.current;
if (!video) return;
const handleFullscreenChange = () => {
const isCurrentlyFullscreen = !!document.fullscreenElement;
setIsFullscreen(isCurrentlyFullscreen);
if (isCurrentlyFullscreen) {
showControlsWithTimer();
}
};
const handleWebkitBeginFullscreen = () => {
setIsFullscreen(true);
showControlsWithTimer();
};
const handleWebkitEndFullscreen = () => {
setIsFullscreen(false);
};
document.addEventListener("fullscreenchange", handleFullscreenChange);
video.addEventListener(
"webkitbeginfullscreen",
handleWebkitBeginFullscreen
);
video.addEventListener("webkitendfullscreen", handleWebkitEndFullscreen);
return () => {
document.removeEventListener("fullscreenchange", handleFullscreenChange);
video.removeEventListener(
"webkitbeginfullscreen",
handleWebkitBeginFullscreen
);
video.removeEventListener(
"webkitendfullscreen",
handleWebkitEndFullscreen
);
};
}, [showControlsWithTimer]);
_react.useEffect.call(void 0, () => {
const init = () => {
if (!isFullscreen) {
showControlsWithTimer();
}
};
let raf1 = 0, raf2 = 0, tid;
if (globalThis.requestAnimationFrame === void 0) {
tid = globalThis.setTimeout(init, INIT_DELAY);
return () => {
if (tid) clearTimeout(tid);
};
} else {
raf1 = requestAnimationFrame(() => {
raf2 = requestAnimationFrame(init);
});
return () => {
cancelAnimationFrame(raf1);
cancelAnimationFrame(raf2);
};
}
}, []);
const getInitialTime = _react.useCallback.call(void 0, () => {
if (!autoSave || !storageKey) {
return Number.isFinite(initialTime) && initialTime >= 0 ? initialTime : void 0;
}
const scopedKey = userId ? `${storageKey}-${userId}` : storageKey;
const saved = Number(
localStorage.getItem(`${scopedKey}-${src}`) || Number.NaN
);
const hasValidInitial = Number.isFinite(initialTime) && initialTime >= 0;
const hasValidSaved = Number.isFinite(saved) && saved >= 0;
if (hasValidInitial) return initialTime;
if (hasValidSaved) return saved;
return void 0;
}, [autoSave, storageKey, userId, src, initialTime]);
_react.useEffect.call(void 0, () => {
const start = getInitialTime();
if (start !== void 0 && Number.isFinite(start) && videoRef.current) {
videoRef.current.currentTime = start;
setCurrentTime(start);
}
}, [getInitialTime]);
const saveProgress = _react.useCallback.call(void 0,
(time) => {
if (!autoSave || !storageKey) return;
const now = Date.now();
if (now - lastSaveTimeRef.current > 5e3) {
const scopedKey = userId ? `${storageKey}-${userId}` : storageKey;
localStorage.setItem(`${scopedKey}-${src}`, time.toString());
lastSaveTimeRef.current = now;
}
},
[autoSave, storageKey, userId, src]
);
const togglePlayPause = _react.useCallback.call(void 0, async () => {
const video = videoRef.current;
if (!video) return;
if (!video.paused) {
video.pause();
return;
}
try {
await video.play();
} catch (e2) {
}
}, []);
const handleVolumeChange = _react.useCallback.call(void 0,
(newVolume) => {
const video = videoRef.current;
if (!video) return;
const volumeValue = newVolume / 100;
video.volume = volumeValue;
setVolume(volumeValue);
const shouldMute = volumeValue === 0;
const shouldUnmute = volumeValue > 0 && isMuted;
if (shouldMute) {
video.muted = true;
setIsMuted(true);
} else if (shouldUnmute) {
video.muted = false;
setIsMuted(false);
}
},
[isMuted]
);
const toggleMute = _react.useCallback.call(void 0, () => {
const video = videoRef.current;
if (!video) return;
if (isMuted) {
const restoreVolume = volume > 0 ? volume : 0.5;
video.volume = restoreVolume;
video.muted = false;
setVolume(restoreVolume);
setIsMuted(false);
} else {
video.muted = true;
setIsMuted(true);
}
}, [isMuted, volume]);
const handleSeek = _react.useCallback.call(void 0, (newTime) => {
const video = videoRef.current;
if (video && Number.isFinite(newTime)) {
video.currentTime = newTime;
}
}, []);
const isSafariIOS = _react.useCallback.call(void 0, () => {
const ua = navigator.userAgent;
const isIOS = /iPad|iPhone|iPod/.test(ua);
const isWebKit = /WebKit/.test(ua);
const isNotChrome = !/CriOS|Chrome/.test(ua);
return isIOS && isWebKit && isNotChrome;
}, []);
const toggleFullscreen = _react.useCallback.call(void 0, () => {
const video = videoRef.current;
const container = _optionalChain([video, 'optionalAccess', _7 => _7.parentElement]);
if (!video || !container) return;
if (isSafariIOS()) {
const videoElement = video;
if (!isFullscreen && videoElement.webkitEnterFullscreen) {
videoElement.webkitEnterFullscreen();
} else if (isFullscreen && videoElement.webkitExitFullscreen) {
videoElement.webkitExitFullscreen();
}
} else if (!isFullscreen && container.requestFullscreen) {
container.requestFullscreen().catch(() => {
});
} else if (isFullscreen && document.exitFullscreen) {
document.exitFullscreen().catch(() => {
});
}
}, [isFullscreen, isSafariIOS]);
const handleSpeedChange = _react.useCallback.call(void 0, (speed) => {
if (videoRef.current) {
videoRef.current.playbackRate = speed;
setPlaybackRate(speed);
setShowSpeedMenu(false);
}
}, []);
const toggleSpeedMenu = _react.useCallback.call(void 0, () => {
setShowSpeedMenu(!showSpeedMenu);
}, [showSpeedMenu]);
const toggleCaptions = _react.useCallback.call(void 0, () => {
if (!_optionalChain([trackRef, 'access', _8 => _8.current, 'optionalAccess', _9 => _9.track]) || !subtitles || subtitlesValidation !== "valid")
return;
const newShowCaptions = !showCaptions;
setShowCaptions(newShowCaptions);
trackRef.current.track.mode = newShowCaptions ? "showing" : "hidden";
}, [showCaptions, subtitles, subtitlesValidation]);
const checkVideoCompletion = _react.useCallback.call(void 0,
(progressPercent) => {
if (progressPercent >= 95 && !hasCompleted) {
setHasCompleted(true);
_optionalChain([onVideoComplete, 'optionalCall', _10 => _10()]);
}
},
[hasCompleted, onVideoComplete]
);
const handleTimeUpdate = _react.useCallback.call(void 0, () => {
const video = videoRef.current;
if (!video) return;
const current = video.currentTime;
setCurrentTime(current);
saveProgress(current);
_optionalChain([onTimeUpdate, 'optionalCall', _11 => _11(current)]);
if (duration > 0) {
const progressPercent = current / duration * 100;
_optionalChain([onProgress, 'optionalCall', _12 => _12(progressPercent)]);
checkVideoCompletion(progressPercent);
}
}, [duration, saveProgress, onTimeUpdate, onProgress, checkVideoCompletion]);
const handleLoadedMetadata = _react.useCallback.call(void 0, () => {
if (videoRef.current) {
setDuration(videoRef.current.duration);
}
}, []);
_react.useEffect.call(void 0, () => {
const controller = new AbortController();
const validateSubtitles = async () => {
if (!subtitles) {
setSubtitlesValidation("idle");
return;
}
setSubtitlesValidation("validating");
try {
if (subtitles.startsWith("data:")) {
setSubtitlesValidation("valid");
return;
}
const response = await fetch(subtitles, {
method: "HEAD",
signal: controller.signal
});
if (response.ok) {
const contentType = response.headers.get("content-type");
const isValidType = !contentType || contentType.includes("text/vtt") || contentType.includes("text/plain") || contentType.includes("application/octet-stream");
if (isValidType) {
setSubtitlesValidation("valid");
} else {
setSubtitlesValidation("invalid");
console.warn(
`Subtitles URL has invalid content type: ${contentType}`
);
}
} else {
setSubtitlesValidation("invalid");
console.warn(
`Subtitles URL returned status: ${response.status} ${response.statusText}`
);
}
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
return;
}
console.warn("Subtitles URL validation failed:", error);
setSubtitlesValidation("invalid");
}
};
validateSubtitles();
return () => {
controller.abort();
};
}, [subtitles]);
_react.useEffect.call(void 0, () => {
if (_optionalChain([trackRef, 'access', _13 => _13.current, 'optionalAccess', _14 => _14.track])) {
trackRef.current.track.mode = showCaptions && subtitles && subtitlesValidation === "valid" ? "showing" : "hidden";
}
}, [subtitles, showCaptions, subtitlesValidation]);
_react.useEffect.call(void 0, () => {
const handleVisibilityChange = () => {
if (document.hidden && isPlaying && videoRef.current) {
videoRef.current.pause();
setIsPlaying(false);
}
};
const handleBlur = () => {
if (isPlaying && videoRef.current) {
videoRef.current.pause();
setIsPlaying(false);
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
globalThis.addEventListener("blur", handleBlur);
return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
globalThis.removeEventListener("blur", handleBlur);
clearControlsTimeout();
};
}, [isPlaying, clearControlsTimeout]);
const progressPercentage = duration > 0 ? currentTime / duration * 100 : 0;
const getIconSize = _react.useCallback.call(void 0, () => {
if (isTinyMobile) return 18;
if (isUltraSmallMobile) return 20;
return 24;
}, [isTinyMobile, isUltraSmallMobile]);
const getControlsPadding = _react.useCallback.call(void 0, () => {
if (isTinyMobile) return "px-2 pb-2 pt-1";
if (isUltraSmallMobile) return "px-3 pb-3 pt-1";
return "px-4 pb-4";
}, [isTinyMobile, isUltraSmallMobile]);
const getControlsGap = _react.useCallback.call(void 0, () => {
if (isTinyMobile) return "gap-1";
if (isUltraSmallMobile) return "gap-2";
return "gap-4";
}, [isTinyMobile, isUltraSmallMobile]);
const getProgressBarPadding = _react.useCallback.call(void 0, () => {
if (isTinyMobile) return "px-2 pb-1";
if (isUltraSmallMobile) return "px-3 pb-1";
return "px-4 pb-2";
}, [isTinyMobile, isUltraSmallMobile]);
const getCenterPlayButtonPosition = _react.useCallback.call(void 0, () => {
if (isTinyMobile) return "items-center justify-center -translate-y-12";
if (isUltraSmallMobile) return "items-center justify-center -translate-y-8";
return "items-center justify-center";
}, [isTinyMobile, isUltraSmallMobile]);
const getTopControlsOpacity = _react.useCallback.call(void 0, () => {
return showControls ? "opacity-100" : "opacity-0";
}, [showControls]);
const getBottomControlsOpacity = _react.useCallback.call(void 0, () => {
return showControls ? "opacity-100" : "opacity-0";
}, [showControls]);
const seekBackward = _react.useCallback.call(void 0, () => {
if (videoRef.current) {
videoRef.current.currentTime -= 10;
}
}, []);
const seekForward = _react.useCallback.call(void 0, () => {
if (videoRef.current) {
videoRef.current.currentTime += 10;
}
}, []);
const increaseVolume = _react.useCallback.call(void 0, () => {
handleVolumeChange(Math.min(100, volume * 100 + 10));
}, [handleVolumeChange, volume]);
const decreaseVolume = _react.useCallback.call(void 0, () => {
handleVolumeChange(Math.max(0, volume * 100 - 10));
}, [handleVolumeChange, volume]);
const handleVideoKeyDown = _react.useCallback.call(void 0,
(e) => {
if (!e.key) return;
e.stopPropagation();
showControlsWithTimer();
const keyHandlers = {
" ": togglePlayPause,
Enter: togglePlayPause,
ArrowLeft: seekBackward,
ArrowRight: seekForward,
ArrowUp: increaseVolume,
ArrowDown: decreaseVolume,
m: toggleMute,
M: toggleMute,
f: toggleFullscreen,
F: toggleFullscreen
};
const handler = keyHandlers[e.key];
if (handler) {
e.preventDefault();
handler();
}
},
[
showControlsWithTimer,
togglePlayPause,
seekBackward,
seekForward,
increaseVolume,
decreaseVolume,
toggleMute,
toggleFullscreen
]
);
const groupedSubTitleValid = subtitles && subtitlesValidation === "valid";
const showPoster = Boolean(poster) && !hasStarted;
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkTN3AYOMVjs.cn.call(void 0, "flex flex-col", className), children: [
(title || subtitleText) && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "bg-subject-1 px-8 py-4 flex items-end justify-between min-h-20", children: [
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-col gap-1", children: [
title && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkANT66KVKjs.Text_default,
{
as: "h2",
size: "lg",
weight: "bold",
color: "text-text-900",
className: "leading-5 tracking-wide",
children: title
}
),
subtitleText && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkANT66KVKjs.Text_default,
{
as: "p",
size: "sm",
weight: "normal",
color: "text-text-600",
className: "leading-5",
children: subtitleText
}
)
] }),
showDownloadButton && downloadContent && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunk7CYRHMOFjs.DownloadButton_default,
{
content: downloadContent,
lessonTitle: title,
onDownloadStart,
onDownloadComplete,
onDownloadError,
className: "flex-shrink-0"
}
)
] }),
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
"section",
{
className: _chunkTN3AYOMVjs.cn.call(void 0,
"relative w-full bg-background overflow-hidden group",
"rounded-b-xl",
// Hide cursor when controls are hidden and video is playing
isPlaying && !showControls ? "cursor-none group-hover:cursor-default" : "cursor-default"
),
"aria-label": title ? `Video player: ${title}` : "Video player",
onMouseMove: handleMouseMove,
onMouseEnter: handleMouseEnter,
onTouchStart: handleMouseEnter,
onMouseLeave: handleMouseLeave,
children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"video",
{
ref: videoRef,
src,
crossOrigin: "anonymous",
className: "w-full h-full object-contain analytica-video",
controlsList: "nodownload",
playsInline: true,
onTimeUpdate: handleTimeUpdate,
onLoadedMetadata: handleLoadedMetadata,
onClick: togglePlayPause,
onKeyDown: handleVideoKeyDown,
tabIndex: 0,
"aria-label": title ? `Video: ${title}` : "Video player",
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"track",
{
ref: trackRef,
kind: "captions",
src: groupedSubTitleValid ? subtitles : "data:text/vtt;charset=utf-8,WEBVTT",
srcLang: "pt-br",
label: groupedSubTitleValid ? "Legendas em Portugu\xEAs" : "Sem legendas dispon\xEDveis",
default: false
}
)
}
),
showPoster && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"img",
{
src: poster,
alt: title ? `Capa: ${title}` : "Capa do v\xEDdeo",
className: "absolute inset-0 w-full h-full object-cover",
"aria-hidden": "true"
}
),
!isPlaying && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"div",
{
className: _chunkTN3AYOMVjs.cn.call(void 0,
"absolute inset-0 flex bg-black/30 transition-opacity",
getCenterPlayButtonPosition()
),
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkBVUPM6EYjs.IconButton_default,
{
icon: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _Play.PlayIcon, { size: 32, weight: "regular", className: "ml-1" }),
onClick: togglePlayPause,
"aria-label": "Play video",
className: "!bg-transparent !text-white !w-auto !h-auto hover:!bg-transparent hover:!text-gray-200"
}
)
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
"div",
{
className: _chunkTN3AYOMVjs.cn.call(void 0,
"absolute top-0 left-0 right-0 p-4 bg-gradient-to-b from-black/70 to-transparent transition-opacity",
getTopControlsOpacity()
),
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex justify-start", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkBVUPM6EYjs.IconButton_default,
{
icon: isFullscreen ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _ArrowsInSimple.ArrowsInSimpleIcon, { size: 24 }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _ArrowsOutSimple.ArrowsOutSimpleIcon, { size: 24 }),
onClick: toggleFullscreen,
"aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
className: "!bg-transparent !text-white hover:!bg-white/20"
}
) })
}
),
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
"div",
{
className: _chunkTN3AYOMVjs.cn.call(void 0,
"absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent transition-opacity",
getBottomControlsOpacity()
),
children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
ProgressBar,
{
currentTime,
duration,
progressPercentage,
onSeek: handleSeek,
className: getProgressBarPadding()
}
),
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
"div",
{
className: _chunkTN3AYOMVjs.cn.call(void 0,
"flex items-center justify-between",
getControlsPadding()
),
children: [
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkTN3AYOMVjs.cn.call(void 0, "flex items-center", getControlsGap()), children: [
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkBVUPM6EYjs.IconButton_default,
{
icon: isPlaying ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _Pause.PauseIcon, { size: getIconSize() }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _Play.PlayIcon, { size: getIconSize() }),
onClick: togglePlayPause,
"aria-label": isPlaying ? "Pause" : "Play",
className: "!bg-transparent !text-white hover:!bg-white/20"
}
),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
VolumeControls,
{
volume,
isMuted,
onVolumeChange: handleVolumeChange,
onToggleMute: toggleMute,
iconSize: getIconSize(),
showSlider: !isUltraSmallMobile
}
),
groupedSubTitleValid && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
_chunkBVUPM6EYjs.IconButton_default,
{
icon: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _ClosedCaptioning.ClosedCaptioningIcon, { size: getIconSize() }),
onClick: toggleCaptions,
"aria-label": showCaptions ? "Hide captions" : "Show captions",
className: _chunkTN3AYOMVjs.cn.call(void 0,
"!bg-transparent hover:!bg-white/20",
showCaptions ? "!text-primary-400" : "!text-white"
)
}
),
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkANT66KVKjs.Text_default, { size: "sm", weight: "medium", color: "text-white", children: [
formatTime(currentTime),
" / ",
formatTime(duration)
] })
] }),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "flex items-center gap-4", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
SpeedMenu,
{
showSpeedMenu,
playbackRate,
onToggleMenu: toggleSpeedMenu,
onSpeedChange: handleSpeedChange,
iconSize: getIconSize(),
isTinyMobile,
isFullscreen
}
) })
]
}
)
]
}
)
]
}
)
] });
};
var VideoPlayer_default = VideoPlayer;
exports.VideoPlayer_default = VideoPlayer_default;
//# sourceMappingURL=chunk-52VZ6TOF.js.map