UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

571 lines (568 loc) 22.8 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { useRef, useState, useEffect, useCallback } from 'react'; import { GlassCore } from '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import '../../primitives/motion/MotionFramer.js'; import { cn } from '../../lib/utilsComprehensive.js'; import { useMedia } from './GlassMediaProvider.js'; const formatTime = seconds => { if (isNaN(seconds)) return "0:00"; const hours = Math.floor(seconds / 3600); const minutes = Math.floor(seconds % 3600 / 60); const secs = Math.floor(seconds % 60); if (hours > 0) { return `${hours}:${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`; } else { return `${minutes}:${secs.toString().padStart(2, "0")}`; } }; const VideoControls = ({ isPlaying, currentTime, duration, volume, playbackRate, isFullscreen, isMuted, quality, buffered, onPlayPause, onSeek, onVolumeChange, onPlaybackRateChange, onQualityChange, onFullscreenToggle, onMuteToggle }) => { const [showVolumeSlider, setShowVolumeSlider] = useState(false); const [showSpeedMenu, setShowSpeedMenu] = useState(false); const [showQualityMenu, setShowQualityMenu] = useState(false); const [seeking, setSeeking] = useState(false); const handleProgressClick = e => { const rect = e.currentTarget.getBoundingClientRect(); const percent = (e.clientX - rect.left) / rect.width; const time = percent * duration; onSeek(time); }; const handleProgressDrag = e => { if (seeking) { const rect = e.currentTarget.getBoundingClientRect(); const percent = (e.clientX - rect.left) / rect.width; const time = Math.max(0, Math.min(duration, percent * duration)); onSeek(time); } }; const playbackSpeeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]; const qualities = ["auto", "144p", "240p", "360p", "480p", "720p", "1080p"]; return jsxs("div", { "data-glass-component": true, className: 'absolute bottom-0 left-0 right-0 glass-gradient-primary glass-gradient-primary via-black/40 glass-gradient-primary glass-p-4', children: [jsxs("div", { className: 'glass-w-full h-2 glass-surface-subtle/20 glass-radius-full cursor-pointer mb-4 relative', onClick: handleProgressClick, onMouseMove: handleProgressDrag, onMouseDown: () => setSeeking(true), onMouseUp: () => setSeeking(false), onMouseLeave: () => setSeeking(false), children: [jsx("div", { className: 'absolute top-0 left-0 glass-h-full glass-surface-subtle/30 glass-radius-full', style: { width: `${buffered / duration * 100}%` } }), jsx("div", { className: 'absolute top-0 left-0 glass-h-full glass-surface-blue glass-radius-full', style: { width: `${currentTime / duration * 100}%` } }), jsx("div", { className: 'absolute glass-top-1/2 transform -translate-y-1/2 w-4 h-4 glass-surface-blue glass-radius-full glass-border-2 glass-border-white glass-shadow-md', style: { left: `calc(${currentTime / duration * 100}% - 8px)` } })] }), jsxs("div", { className: 'glass-flex glass-items-center glass-justify-between text-primary', children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-3", children: [jsx("button", { onClick: onPlayPause, className: 'w-12 h-12 glass-flex glass-items-center glass-justify-center glass-surface-subtle/20 hover:glass-surface-subtle/30 glass-radius-full transition-colors glass-focus glass-touch-target glass-contrast-guard', children: isPlaying ? jsxs("div", { className: "glass-flex glass-gap-1", children: [jsx("div", { className: 'w-1 h-4 glass-surface-subtle glass-radius-full' }), jsx("div", { className: 'w-1 h-4 glass-surface-subtle glass-radius-full' })] }) : jsx("div", { className: 'w-0 h-0 glass-border-l-4 glass-border-l-white glass-border-y-4 glass-border-y-transparent ml-1' }) }), jsxs("div", { className: 'relative', onMouseEnter: () => setShowVolumeSlider(true), onMouseLeave: () => setShowVolumeSlider(false), children: [jsx("button", { onClick: onMuteToggle, className: 'w-10 h-10 glass-flex glass-items-center glass-justify-center hover:glass-surface-subtle/20 glass-radius-full transition-colors glass-focus glass-touch-target glass-contrast-guard', children: isMuted || volume === 0 ? "🔇" : volume < 0.5 ? "🔉" : "🔊" }), showVolumeSlider && jsx("div", { className: 'absolute bottom-12 left-1/2 transform -translate-x-1/2 glass-surface-dark/80 glass-radius-lg glass-p-2', children: jsx("input", { type: "range", min: "0", max: "1", step: "0.01", value: isMuted ? 0 : volume, onChange: e => onVolumeChange(Number(e.target.value)), className: 'w-16 h-1 glass-surface-subtle/20 glass-radius-full appearance-none slider-thumb-white glass-focus glass-touch-target glass-contrast-guard' }) })] }), jsxs("div", { className: 'glass-text-sm font-mono', children: [formatTime(currentTime), " / ", formatTime(duration)] })] }), jsxs("div", { className: "glass-flex glass-items-center glass-gap-3", children: [jsxs("div", { className: 'relative', children: [jsxs("button", { onClick: () => setShowSpeedMenu(!showSpeedMenu), className: 'glass-px-3 glass-py-2 glass-text-sm hover:glass-surface-subtle/20 glass-radius-md transition-colors glass-focus glass-touch-target glass-contrast-guard', children: [playbackRate, "x"] }), showSpeedMenu && jsx("div", { className: 'absolute bottom-12 right-0 glass-surface-dark/90 glass-radius-lg glass-p-2 glass-min-w-20', children: playbackSpeeds.map(speed => jsxs("button", { onClick: () => { onPlaybackRateChange(speed); setShowSpeedMenu(false); }, className: cn("block w-full text-left px-3 py-2 text-sm rounded hover:bg-white/20 transition-colors glass-focus glass-touch-target glass-contrast-guard", speed === playbackRate && "bg-blue-500"), children: [speed, "x"] }, speed)) })] }), jsxs("div", { className: 'relative', children: [jsx("button", { onClick: () => setShowQualityMenu(!showQualityMenu), className: 'glass-px-3 glass-py-2 glass-text-sm hover:glass-surface-subtle/20 glass-radius-md transition-colors glass-focus glass-touch-target glass-contrast-guard', children: quality }), showQualityMenu && jsx("div", { className: 'absolute bottom-12 right-0 glass-surface-dark/90 glass-radius-lg glass-p-2 glass-min-w-20', children: qualities.map(q => jsx("button", { onClick: () => { onQualityChange(q); setShowQualityMenu(false); }, className: cn("block w-full text-left px-3 py-2 text-sm rounded hover:bg-white/20 transition-colors glass-focus glass-touch-target glass-contrast-guard", q === quality && "bg-blue-500"), children: q }, q)) })] }), jsx("button", { onClick: onFullscreenToggle, className: 'w-10 h-10 glass-flex glass-items-center glass-justify-center hover:glass-surface-subtle/20 glass-radius-full transition-colors glass-focus glass-touch-target glass-contrast-guard', children: isFullscreen ? "⊟" : "⊞" })] })] })] }); }; const ChapterList = ({ chapters, currentTime, onChapterClick }) => { return jsxs("div", { className: 'glass-surface-subtle text-primary glass-p-4 glass-radius-lg', children: [jsx("h3", { className: 'glass-text-lg font-semibold mb-4', children: "Chapters" }), jsx("div", { className: 'space-y-2 max-h-96 overflow-y-auto', children: chapters.map(chapter => { const isActive = currentTime >= chapter.startTime && currentTime <= chapter.endTime; return jsxs("button", { onClick: () => onChapterClick(chapter), className: cn("flex items-center gap-3 w-full p-3 rounded-lg text-left transition-colors glass-focus glass-touch-target glass-contrast-guard", isActive ? "bg-blue-600" : "bg-gray-800 hover:bg-gray-700"), children: [chapter.thumbnail && jsx("img", { src: chapter.thumbnail, alt: chapter.title, className: 'w-16 h-9 object-cover glass-radius' }), jsxs("div", { className: "glass-flex-1", children: [jsx("div", { className: 'font-medium', children: chapter.title }), jsxs("div", { className: "glass-text-sm glass-text-secondary", children: [formatTime(chapter.startTime), " -", " ", formatTime(chapter.endTime)] }), chapter.description && jsx("div", { className: 'glass-text-xs glass-text-secondary mt-1', children: chapter.description })] })] }, chapter.id); }) })] }); }; const TranscriptPanel = ({ transcript, currentTime, searchQuery, onSearchChange, onTranscriptClick }) => { const [highlightedResults, setHighlightedResults] = useState([]); useEffect(() => { if (searchQuery) { const results = transcript.filter(entry => entry.text.toLowerCase().includes(searchQuery.toLowerCase())); setHighlightedResults(results); } else { setHighlightedResults([]); } }, [searchQuery, transcript]); const highlightText = (text, query) => { if (!query) return text; const regex = new RegExp(`(${query})`, "gi"); return text.split(regex).map((part, index) => part.toLowerCase() === query.toLowerCase() ? jsx("mark", { className: "glass-surface-subtle glass-text-inverse glass-radius glass-px-1", children: part }, index) : part); }; return jsxs("div", { className: 'glass-surface-subtle text-primary glass-p-4 glass-radius-lg glass-h-full glass-flex glass-flex-col', children: [jsxs("div", { className: 'glass-flex glass-items-center glass-justify-between mb-4', children: [jsx("h3", { className: 'glass-text-lg font-semibold', children: "Transcript" }), jsxs("div", { className: "glass-text-sm glass-text-secondary", children: [transcript.length, " entries"] })] }), jsxs("div", { className: 'mb-4', children: [jsx("input", { type: "text", placeholder: "Search transcript...", value: searchQuery, onChange: e => onSearchChange(e.target.value), className: 'glass-w-full glass-px-3 glass-py-2 glass-surface-primary glass-border glass-border-gray-700 glass-radius-lg text-primary placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 glass-focus glass-touch-target glass-contrast-guard' }), searchQuery && jsxs("div", { className: 'glass-text-sm glass-text-secondary mt-2', children: [highlightedResults.length, " results found"] })] }), jsx("div", { className: 'glass-flex-1 overflow-y-auto space-y-3', children: (searchQuery ? highlightedResults : transcript).map(entry => { const isActive = currentTime >= entry.startTime && currentTime <= entry.endTime; const confidence = entry.confidence || 0; return jsxs("button", { onClick: () => onTranscriptClick(entry), className: cn("flex flex-col items-start gap-2 w-full p-3 rounded-lg text-left transition-colors glass-focus glass-touch-target glass-contrast-guard", isActive ? "bg-blue-600" : "bg-gray-800 hover:bg-gray-700"), children: [jsxs("div", { className: "glass-flex glass-items-center glass-justify-between glass-w-full", children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx("span", { className: 'glass-text-xs glass-text-secondary font-mono', children: formatTime(entry.startTime) }), entry.speaker && jsx("span", { className: 'glass-text-xs glass-surface-subtle text-gray-300 glass-px-2 glass-py-1 glass-radius', children: entry.speaker })] }), jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [confidence > 0 && jsxs("div", { className: cn("text-xs px-2 py-1 rounded", confidence > 0.9 ? "bg-green-600" : confidence > 0.7 ? "bg-yellow-600" : "bg-red-600"), children: [Math.round(confidence * 100), "%"] }), entry.sentiment && jsx("div", { className: "glass-text-xs", children: entry.sentiment === "positive" ? "😊" : entry.sentiment === "negative" ? "😔" : "😐" })] })] }), jsx("div", { className: 'glass-text-sm leading-relaxed', children: searchQuery ? highlightText(entry.text, searchQuery) : entry.text }), entry.keywords && entry.keywords.length > 0 && jsx("div", { className: 'glass-flex glass-flex-wrap glass-gap-1 mt-2', children: entry.keywords.map(keyword => jsx("span", { className: 'glass-text-xs glass-surface-subtle text-gray-300 glass-px-2 glass-py-1 glass-radius', children: keyword }, keyword)) })] }, entry.id); }) })] }); }; const GlassAdvancedVideoPlayer = ({ mediaFile, className, showControls = true, showTranscript = false, showChapters = false, showAnalytics = false, autoplay = false, muted = false, loop = false, preload = "metadata", poster, onTimeUpdate, onEnded, onError }) => { const { playbackState, setPlaybackState, play, pause, seekTo, setVolume, setPlaybackRate, toggleMute, toggleFullscreen, setQuality, transcripts, generateTranscript, searchTranscript, getTranscriptAtTime, setActiveChapter, getChapterAtTime, trackView, trackEngagement } = useMedia(); const videoRef = useRef(null); const containerRef = useRef(null); const [showControlsOverlay, setShowControlsOverlay] = useState(showControls); const [controlsTimeout, setControlsTimeout] = useState(null); const [transcriptSearch, setTranscriptSearch] = useState(""); const [hasTranscript, setHasTranscript] = useState(false); const isPlaying = playbackState?.mediaId === mediaFile.id && playbackState.isPlaying; const currentTime = playbackState?.mediaId === mediaFile.id ? playbackState.currentTime : 0; const duration = playbackState?.mediaId === mediaFile.id ? playbackState.duration : mediaFile.duration || 0; const volume = playbackState?.volume || 1; const playbackRate = playbackState?.playbackRate || 1; const isFullscreen = playbackState?.isFullscreen || false; const isMuted = playbackState?.isMuted || false; const quality = playbackState?.quality || "auto"; // Initialize transcript useEffect(() => { if (!transcripts[mediaFile.id] && showTranscript) { generateTranscript(mediaFile.id).then(() => { setHasTranscript(true); }); } else if (transcripts[mediaFile.id]) { setHasTranscript(true); } }, [mediaFile.id, showTranscript, generateTranscript, transcripts]); // Mouse movement handling for controls const handleMouseMove = useCallback(() => { setShowControlsOverlay(true); if (controlsTimeout) { clearTimeout(controlsTimeout); } const timeout = setTimeout(() => { if (isPlaying) { setShowControlsOverlay(false); } }, 3000); setControlsTimeout(timeout); }, [isPlaying, controlsTimeout]); // Video event handlers const handleTimeUpdate = () => { if (videoRef.current) { const currentTime = videoRef.current.currentTime; if (playbackState && playbackState.mediaId === mediaFile.id) { setPlaybackState({ ...playbackState, currentTime }); } onTimeUpdate?.(currentTime); // Update active chapter const activeChapter = getChapterAtTime(mediaFile.id, currentTime); if (activeChapter) { setActiveChapter(mediaFile.id, activeChapter.id); } } }; const handleLoadedMetadata = () => { if (videoRef.current) { const duration = videoRef.current.duration; if (playbackState && playbackState.mediaId === mediaFile.id) { setPlaybackState({ ...playbackState, duration, isLoading: false }); } } }; const handleEnded = () => { if (playbackState && playbackState.mediaId === mediaFile.id) { setPlaybackState({ ...playbackState, isPlaying: false, currentTime: 0 }); } trackView(mediaFile.id, duration); onEnded?.(); }; const handleError = () => { if (playbackState && playbackState.mediaId === mediaFile.id) { setPlaybackState({ ...playbackState, isError: true, isLoading: false }); } onError?.("Video playback error occurred"); }; const handlePlayPause = () => { if (isPlaying) { pause(); if (videoRef.current) { videoRef.current.pause(); } } else { play(mediaFile.id); if (videoRef.current) { videoRef.current.play(); } } }; const handleSeek = time => { seekTo(time); if (videoRef.current) { videoRef.current.currentTime = time; } }; const handleVolumeChange = newVolume => { setVolume(newVolume); if (videoRef.current) { videoRef.current.volume = newVolume; } }; const handlePlaybackRateChange = rate => { setPlaybackRate(rate); if (videoRef.current) { videoRef.current.playbackRate = rate; } }; const handleMuteToggle = () => { toggleMute(); if (videoRef.current) { videoRef.current.muted = !isMuted; } }; const handleFullscreenToggle = () => { toggleFullscreen(); if (!isFullscreen) { if (containerRef.current?.requestFullscreen) { containerRef.current.requestFullscreen(); } } else { if (document.exitFullscreen) { document.exitFullscreen(); } } }; const handleChapterClick = chapter => { handleSeek(chapter.startTime); setActiveChapter(mediaFile.id, chapter.id); }; const handleTranscriptClick = entry => { handleSeek(entry.startTime); }; return jsx("div", { className: cn("relative bg-black rounded-lg overflow-hidden", className), children: jsxs("div", { ref: containerRef, className: 'relative glass-w-full glass-h-full glass-flex', onMouseMove: handleMouseMove, onMouseLeave: () => setShowControlsOverlay(false), children: [jsxs("div", { className: 'glass-flex-1 relative', children: [jsx("video", { ref: videoRef, src: mediaFile.src, poster: poster || mediaFile.poster, autoPlay: autoplay, muted: muted, loop: loop, preload: preload, className: 'glass-w-full glass-h-full object-contain', onTimeUpdate: handleTimeUpdate, onLoadedMetadata: handleLoadedMetadata, onEnded: handleEnded, onError: handleError, onClick: handlePlayPause }), playbackState?.isLoading && jsx("div", { className: 'absolute inset-0 glass-flex glass-items-center glass-justify-center glass-surface-dark/50', children: jsx("div", { className: 'animate-spin glass-radius-full h-16 w-16 glass-border-4 glass-border-white glass-border-t-transparent' }) }), playbackState?.isError && jsx("div", { className: 'absolute inset-0 glass-flex glass-items-center glass-justify-center glass-surface-dark/80 text-primary', children: jsxs("div", { className: 'text-center', children: [jsx("div", { className: 'glass-text-4xl mb-4', children: "\u26A0\uFE0F" }), jsx("h3", { className: 'glass-text-xl font-semibold mb-2', children: "Playback Error" }), jsx("p", { className: 'text-gray-300', children: "Unable to load video content" })] }) }), showControlsOverlay && jsx(VideoControls, { isPlaying: isPlaying, currentTime: currentTime, duration: duration, volume: volume, playbackRate: playbackRate, isFullscreen: isFullscreen, isMuted: isMuted, quality: quality, buffered: videoRef.current?.buffered.length ? videoRef.current.buffered.end(videoRef.current.buffered.length - 1) : 0, onPlayPause: handlePlayPause, onSeek: handleSeek, onVolumeChange: handleVolumeChange, onPlaybackRateChange: handlePlaybackRateChange, onQualityChange: setQuality, onFullscreenToggle: handleFullscreenToggle, onMuteToggle: handleMuteToggle })] }), jsxs("div", { className: "glass-flex", children: [showChapters && mediaFile.chapters && mediaFile.chapters.length > 0 && jsx(GlassCore, { className: 'w-80 glass-h-full overflow-hidden', children: jsx(ChapterList, { chapters: mediaFile.chapters, currentTime: currentTime, onChapterClick: handleChapterClick }) }), showTranscript && hasTranscript && transcripts[mediaFile.id] && jsx(GlassCore, { className: 'w-96 glass-h-full overflow-hidden', children: jsx(TranscriptPanel, { transcript: transcripts[mediaFile.id], currentTime: currentTime, searchQuery: transcriptSearch, onSearchChange: setTranscriptSearch, onTranscriptClick: handleTranscriptClick }) })] })] }) }); }; export { GlassAdvancedVideoPlayer }; //# sourceMappingURL=GlassAdvancedVideoPlayer.js.map