@frameset/plex-player 1.0.6 → 2.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/utils/helpers.ts","../src/components/ProgressBar.tsx","../src/components/Icons.tsx","../src/components/VolumeControl.tsx","../src/components/SettingsMenu.tsx","../src/components/AdOverlay.tsx","../src/components/ErrorDisplay.tsx","../src/components/Loader.tsx","../src/hooks/useKeyboard.ts","../src/utils/vast.ts","../src/hooks/useVast.ts","../src/components/PlexVideoPlayer.tsx","../src/hooks/usePlayer.ts"],"sourcesContent":["// Utility functions for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\n/**\r\n * Format time in seconds to MM:SS or HH:MM:SS format\r\n */\r\nexport const formatTime = (seconds: number): string => {\r\n if (!isFinite(seconds) || isNaN(seconds)) return '0:00';\r\n\r\n const hrs = Math.floor(seconds / 3600);\r\n const mins = Math.floor((seconds % 3600) / 60);\r\n const secs = Math.floor(seconds % 60);\r\n\r\n if (hrs > 0) {\r\n return `${hrs}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\r\n }\r\n\r\n return `${mins}:${secs.toString().padStart(2, '0')}`;\r\n};\r\n\r\n/**\r\n * Parse time string to seconds\r\n */\r\nexport const parseTime = (time: string): number => {\r\n const parts = time.split(':').map(Number);\r\n if (parts.length === 3) {\r\n return parts[0] * 3600 + parts[1] * 60 + parts[2];\r\n }\r\n if (parts.length === 2) {\r\n return parts[0] * 60 + parts[1];\r\n }\r\n return parts[0] || 0;\r\n};\r\n\r\n/**\r\n * Calculate percentage\r\n */\r\nexport const percentage = (value: number, total: number): number => {\r\n if (total === 0) return 0;\r\n return Math.min(100, Math.max(0, (value / total) * 100));\r\n};\r\n\r\n/**\r\n * Clamp a value between min and max\r\n */\r\nexport const clamp = (value: number, min: number, max: number): number => {\r\n return Math.min(max, Math.max(min, value));\r\n};\r\n\r\n/**\r\n * Throttle function execution\r\n */\r\nexport const throttle = <T extends (...args: unknown[]) => unknown>(\r\n func: T,\r\n limit: number\r\n): ((...args: Parameters<T>) => void) => {\r\n let inThrottle = false;\r\n return (...args: Parameters<T>) => {\r\n if (!inThrottle) {\r\n func(...args);\r\n inThrottle = true;\r\n setTimeout(() => (inThrottle = false), limit);\r\n }\r\n };\r\n};\r\n\r\n/**\r\n * Debounce function execution\r\n */\r\nexport const debounce = <T extends (...args: unknown[]) => unknown>(\r\n func: T,\r\n wait: number\r\n): ((...args: Parameters<T>) => void) => {\r\n let timeout: ReturnType<typeof setTimeout> | null = null;\r\n return (...args: Parameters<T>) => {\r\n if (timeout) clearTimeout(timeout);\r\n timeout = setTimeout(() => func(...args), wait);\r\n };\r\n};\r\n\r\n/**\r\n * Check if fullscreen is supported\r\n */\r\nexport const isFullscreenSupported = (): boolean => {\r\n return !!(\r\n document.fullscreenEnabled ||\r\n (document as Document & { webkitFullscreenEnabled?: boolean }).webkitFullscreenEnabled ||\r\n (document as Document & { mozFullScreenEnabled?: boolean }).mozFullScreenEnabled ||\r\n (document as Document & { msFullscreenEnabled?: boolean }).msFullscreenEnabled\r\n );\r\n};\r\n\r\n/**\r\n * Check if Picture-in-Picture is supported\r\n */\r\nexport const isPipSupported = (): boolean => {\r\n return 'pictureInPictureEnabled' in document && document.pictureInPictureEnabled;\r\n};\r\n\r\n/**\r\n * Get current fullscreen element\r\n */\r\nexport const getFullscreenElement = (): Element | null => {\r\n return (\r\n document.fullscreenElement ||\r\n (document as Document & { webkitFullscreenElement?: Element }).webkitFullscreenElement ||\r\n (document as Document & { mozFullScreenElement?: Element }).mozFullScreenElement ||\r\n (document as Document & { msFullscreenElement?: Element }).msFullscreenElement ||\r\n null\r\n );\r\n};\r\n\r\n/**\r\n * Request fullscreen on element\r\n */\r\nexport const requestFullscreen = async (element: HTMLElement): Promise<void> => {\r\n if (element.requestFullscreen) {\r\n await element.requestFullscreen();\r\n } else if ((element as HTMLElement & { webkitRequestFullscreen?: () => Promise<void> }).webkitRequestFullscreen) {\r\n await (element as HTMLElement & { webkitRequestFullscreen: () => Promise<void> }).webkitRequestFullscreen();\r\n } else if ((element as HTMLElement & { mozRequestFullScreen?: () => Promise<void> }).mozRequestFullScreen) {\r\n await (element as HTMLElement & { mozRequestFullScreen: () => Promise<void> }).mozRequestFullScreen();\r\n } else if ((element as HTMLElement & { msRequestFullscreen?: () => Promise<void> }).msRequestFullscreen) {\r\n await (element as HTMLElement & { msRequestFullscreen: () => Promise<void> }).msRequestFullscreen();\r\n }\r\n};\r\n\r\n/**\r\n * Exit fullscreen\r\n */\r\nexport const exitFullscreen = async (): Promise<void> => {\r\n if (document.exitFullscreen) {\r\n await document.exitFullscreen();\r\n } else if ((document as Document & { webkitExitFullscreen?: () => Promise<void> }).webkitExitFullscreen) {\r\n await (document as Document & { webkitExitFullscreen: () => Promise<void> }).webkitExitFullscreen();\r\n } else if ((document as Document & { mozCancelFullScreen?: () => Promise<void> }).mozCancelFullScreen) {\r\n await (document as Document & { mozCancelFullScreen: () => Promise<void> }).mozCancelFullScreen();\r\n } else if ((document as Document & { msExitFullscreen?: () => Promise<void> }).msExitFullscreen) {\r\n await (document as Document & { msExitFullscreen: () => Promise<void> }).msExitFullscreen();\r\n }\r\n};\r\n\r\n/**\r\n * Detect video type from URL\r\n */\r\nexport const detectVideoType = (url: string): string => {\r\n const extension = url.split('?')[0].split('.').pop()?.toLowerCase();\r\n \r\n const mimeTypes: Record<string, string> = {\r\n mp4: 'video/mp4',\r\n webm: 'video/webm',\r\n ogg: 'video/ogg',\r\n ogv: 'video/ogg',\r\n m3u8: 'application/x-mpegURL',\r\n mpd: 'application/dash+xml',\r\n mov: 'video/quicktime',\r\n avi: 'video/x-msvideo',\r\n mkv: 'video/x-matroska',\r\n };\r\n\r\n return mimeTypes[extension || ''] || 'video/mp4';\r\n};\r\n\r\n/**\r\n * Check if HLS is natively supported\r\n */\r\nexport const isHlsNativelySupported = (): boolean => {\r\n const video = document.createElement('video');\r\n return video.canPlayType('application/vnd.apple.mpegurl') !== '';\r\n};\r\n\r\n/**\r\n * Generate unique ID\r\n */\r\nexport const generateId = (): string => {\r\n return `plex-${Math.random().toString(36).substring(2, 11)}`;\r\n};\r\n\r\n/**\r\n * Check if device is mobile\r\n */\r\nexport const isMobile = (): boolean => {\r\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(\r\n navigator.userAgent\r\n );\r\n};\r\n\r\n/**\r\n * Check if device is touch-enabled\r\n */\r\nexport const isTouchDevice = (): boolean => {\r\n return 'ontouchstart' in window || navigator.maxTouchPoints > 0;\r\n};\r\n\r\n/**\r\n * Parse buffered time ranges\r\n */\r\nexport const getBufferedEnd = (video: HTMLVideoElement): number => {\r\n if (video.buffered.length === 0) return 0;\r\n \r\n const currentTime = video.currentTime;\r\n for (let i = 0; i < video.buffered.length; i++) {\r\n if (video.buffered.start(i) <= currentTime && video.buffered.end(i) >= currentTime) {\r\n return video.buffered.end(i);\r\n }\r\n }\r\n \r\n return video.buffered.end(video.buffered.length - 1);\r\n};\r\n\r\n/**\r\n * Check browser support for specific video format\r\n */\r\nexport const canPlayType = (type: string): boolean => {\r\n const video = document.createElement('video');\r\n const result = video.canPlayType(type);\r\n return result === 'probably' || result === 'maybe';\r\n};\r\n","// Progress Bar Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React, { useRef, useState, useCallback, useEffect } from 'react';\r\nimport { formatTime, percentage, clamp } from '../utils/helpers';\r\n\r\ninterface ProgressBarProps {\r\n currentTime: number;\r\n duration: number;\r\n buffered: number;\r\n onSeek: (time: number) => void;\r\n thumbnailPreview?: {\r\n enabled: boolean;\r\n sprites?: string;\r\n interval?: number;\r\n width?: number;\r\n height?: number;\r\n };\r\n disabled?: boolean;\r\n}\r\n\r\nexport const ProgressBar: React.FC<ProgressBarProps> = ({\r\n currentTime,\r\n duration,\r\n buffered,\r\n onSeek,\r\n thumbnailPreview,\r\n disabled = false,\r\n}) => {\r\n const progressRef = useRef<HTMLDivElement>(null);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const [hoverTime, setHoverTime] = useState<number | null>(null);\r\n const [hoverPosition, setHoverPosition] = useState(0);\r\n\r\n const playedPercent = percentage(currentTime, duration);\r\n const bufferedPercent = percentage(buffered, duration);\r\n\r\n const getTimeFromPosition = useCallback(\r\n (clientX: number): number => {\r\n if (!progressRef.current) return 0;\r\n const rect = progressRef.current.getBoundingClientRect();\r\n const percent = clamp((clientX - rect.left) / rect.width, 0, 1);\r\n return percent * duration;\r\n },\r\n [duration]\r\n );\r\n\r\n const handleMouseMove = useCallback(\r\n (e: MouseEvent | React.MouseEvent) => {\r\n if (!progressRef.current) return;\r\n const rect = progressRef.current.getBoundingClientRect();\r\n const clientX = 'clientX' in e ? e.clientX : 0;\r\n const position = clamp(clientX - rect.left, 0, rect.width);\r\n const time = getTimeFromPosition(clientX);\r\n\r\n setHoverPosition(position);\r\n setHoverTime(time);\r\n\r\n if (isDragging) {\r\n onSeek(time);\r\n }\r\n },\r\n [isDragging, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleMouseDown = useCallback(\r\n (e: React.MouseEvent) => {\r\n if (disabled) return;\r\n e.preventDefault();\r\n setIsDragging(true);\r\n const time = getTimeFromPosition(e.clientX);\r\n onSeek(time);\r\n },\r\n [disabled, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleMouseUp = useCallback(() => {\r\n setIsDragging(false);\r\n }, []);\r\n\r\n const handleMouseEnter = useCallback(\r\n (e: React.MouseEvent) => {\r\n const time = getTimeFromPosition(e.clientX);\r\n setHoverTime(time);\r\n },\r\n [getTimeFromPosition]\r\n );\r\n\r\n const handleMouseLeave = useCallback(() => {\r\n setHoverTime(null);\r\n }, []);\r\n\r\n // Global mouse events for dragging\r\n useEffect(() => {\r\n if (isDragging) {\r\n const mouseMoveHandler = (e: MouseEvent) => handleMouseMove(e);\r\n window.addEventListener('mousemove', mouseMoveHandler);\r\n window.addEventListener('mouseup', handleMouseUp);\r\n\r\n return () => {\r\n window.removeEventListener('mousemove', mouseMoveHandler);\r\n window.removeEventListener('mouseup', handleMouseUp);\r\n };\r\n }\r\n return undefined;\r\n }, [isDragging, handleMouseMove, handleMouseUp]);\r\n\r\n // Touch support\r\n const handleTouchStart = useCallback(\r\n (e: React.TouchEvent) => {\r\n if (disabled) return;\r\n e.preventDefault();\r\n const touch = e.touches[0];\r\n const time = getTimeFromPosition(touch.clientX);\r\n setIsDragging(true);\r\n onSeek(time);\r\n },\r\n [disabled, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleTouchMove = useCallback(\r\n (e: React.TouchEvent) => {\r\n if (!isDragging) return;\r\n const touch = e.touches[0];\r\n const time = getTimeFromPosition(touch.clientX);\r\n onSeek(time);\r\n },\r\n [isDragging, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleTouchEnd = useCallback(() => {\r\n setIsDragging(false);\r\n }, []);\r\n\r\n return (\r\n <div\r\n ref={progressRef}\r\n className=\"plex-video-player__progress-container\"\r\n onMouseDown={handleMouseDown}\r\n onMouseEnter={handleMouseEnter}\r\n onMouseLeave={handleMouseLeave}\r\n onMouseMove={(e) => handleMouseMove(e.nativeEvent)}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n role=\"slider\"\r\n aria-label=\"Video progress\"\r\n aria-valuemin={0}\r\n aria-valuemax={duration}\r\n aria-valuenow={currentTime}\r\n aria-valuetext={formatTime(currentTime)}\r\n tabIndex={0}\r\n >\r\n <div className=\"plex-video-player__progress\">\r\n <div\r\n className=\"plex-video-player__progress-buffered\"\r\n style={{ width: `${bufferedPercent}%` }}\r\n />\r\n <div\r\n className=\"plex-video-player__progress-played\"\r\n style={{ width: `${playedPercent}%` }}\r\n />\r\n <div\r\n className=\"plex-video-player__progress-handle\"\r\n style={{ left: `${playedPercent}%` }}\r\n />\r\n </div>\r\n\r\n {/* Thumbnail Preview */}\r\n {thumbnailPreview?.enabled && hoverTime !== null && (\r\n <div\r\n className=\"plex-video-player__thumbnail-preview\"\r\n style={{\r\n left: `${hoverPosition}px`,\r\n width: thumbnailPreview.width || 160,\r\n height: thumbnailPreview.height || 90,\r\n }}\r\n >\r\n {thumbnailPreview.sprites && (\r\n <div\r\n style={{\r\n width: '100%',\r\n height: '100%',\r\n backgroundImage: `url(${thumbnailPreview.sprites})`,\r\n backgroundPosition: calculateSpritePosition(\r\n hoverTime,\r\n duration,\r\n thumbnailPreview.interval || 10,\r\n thumbnailPreview.width || 160,\r\n thumbnailPreview.height || 90\r\n ),\r\n backgroundSize: 'cover',\r\n }}\r\n />\r\n )}\r\n <div className=\"plex-video-player__thumbnail-time\">\r\n {formatTime(hoverTime)}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Time tooltip */}\r\n {hoverTime !== null && !thumbnailPreview?.enabled && (\r\n <div\r\n className=\"plex-video-player__thumbnail-preview\"\r\n style={{\r\n left: `${hoverPosition}px`,\r\n width: 'auto',\r\n height: 'auto',\r\n padding: '4px 8px',\r\n }}\r\n >\r\n <span style={{ color: 'white', fontSize: '12px' }}>\r\n {formatTime(hoverTime)}\r\n </span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\n// Calculate sprite background position for thumbnail preview\r\nconst calculateSpritePosition = (\r\n time: number,\r\n _duration: number,\r\n interval: number,\r\n width: number,\r\n height: number\r\n): string => {\r\n const index = Math.floor(time / interval);\r\n const columns = 10; // Assuming 10 columns per row in sprite\r\n const row = Math.floor(index / columns);\r\n const col = index % columns;\r\n\r\n return `-${col * width}px -${row * height}px`;\r\n};\r\n\r\nexport default ProgressBar;\r\n","// Icon components for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\n\r\ninterface IconProps {\r\n className?: string;\r\n size?: number;\r\n}\r\n\r\nexport const PlayIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n);\r\n\r\nexport const PauseIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M6 19h4V5H6v14zm8-14v14h4V5h-4z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeHighIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeMediumIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeLowIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M7 9v6h4l5 5V4l-5 5H7z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeMuteIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\" />\r\n </svg>\r\n);\r\n\r\nexport const FullscreenIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\" />\r\n </svg>\r\n);\r\n\r\nexport const FullscreenExitIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\" />\r\n </svg>\r\n);\r\n\r\nexport const PipIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z\" />\r\n </svg>\r\n);\r\n\r\nexport const PipExitIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zM5 7h8v6H5z\" />\r\n </svg>\r\n);\r\n\r\nexport const SettingsIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z\" />\r\n </svg>\r\n);\r\n\r\nexport const CaptionsIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z\" />\r\n </svg>\r\n);\r\n\r\nexport const SpeedIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M20.38 8.57l-1.23 1.85a8 8 0 0 1-.22 7.58H5.07A8 8 0 0 1 15.58 6.85l1.85-1.23A10 10 0 0 0 3.35 19a2 2 0 0 0 1.72 1h13.85a2 2 0 0 0 1.74-1 10 10 0 0 0-.27-10.44zm-9.79 6.84a2 2 0 0 0 2.83 0l5.66-8.49-8.49 5.66a2 2 0 0 0 0 2.83z\" />\r\n </svg>\r\n);\r\n\r\nexport const QualityIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zM7.5 13h2v2H7.5zm4.5 0h2v2h-2zm4.5 0h2v2h-2zM7.5 9h2v2H7.5zm4.5 0h2v2h-2zm4.5 0h2v2h-2z\" />\r\n </svg>\r\n);\r\n\r\nexport const ForwardIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z\" />\r\n </svg>\r\n);\r\n\r\nexport const RewindIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z\" />\r\n </svg>\r\n);\r\n\r\nexport const SkipNextIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z\" />\r\n </svg>\r\n);\r\n\r\nexport const SkipPrevIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M6 6h2v12H6zm3.5 6l8.5 6V6z\" />\r\n </svg>\r\n);\r\n\r\nexport const ErrorIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\" />\r\n </svg>\r\n);\r\n\r\nexport const ExternalLinkIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z\" />\r\n </svg>\r\n);\r\n\r\nexport const CheckIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\" />\r\n </svg>\r\n);\r\n","// Volume Control Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React, { useCallback } from 'react';\r\nimport {\r\n VolumeHighIcon,\r\n VolumeMediumIcon,\r\n VolumeLowIcon,\r\n VolumeMuteIcon,\r\n} from './Icons';\r\n\r\ninterface VolumeControlProps {\r\n volume: number;\r\n muted: boolean;\r\n onVolumeChange: (volume: number) => void;\r\n onToggleMute: () => void;\r\n}\r\n\r\nexport const VolumeControl: React.FC<VolumeControlProps> = ({\r\n volume,\r\n muted,\r\n onVolumeChange,\r\n onToggleMute,\r\n}) => {\r\n const handleSliderChange = useCallback(\r\n (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const newVolume = parseFloat(e.target.value);\r\n onVolumeChange(newVolume);\r\n },\r\n [onVolumeChange]\r\n );\r\n\r\n const getVolumeIcon = () => {\r\n if (muted || volume === 0) {\r\n return <VolumeMuteIcon />;\r\n }\r\n if (volume < 0.33) {\r\n return <VolumeLowIcon />;\r\n }\r\n if (volume < 0.66) {\r\n return <VolumeMediumIcon />;\r\n }\r\n return <VolumeHighIcon />;\r\n };\r\n\r\n return (\r\n <div className=\"plex-video-player__volume-container\">\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={onToggleMute}\r\n aria-label={muted ? 'Unmute' : 'Mute'}\r\n title={muted ? 'Unmute (M)' : 'Mute (M)'}\r\n type=\"button\"\r\n >\r\n {getVolumeIcon()}\r\n </button>\r\n <div className=\"plex-video-player__volume-slider-container\">\r\n <input\r\n type=\"range\"\r\n className=\"plex-video-player__volume-slider\"\r\n min={0}\r\n max={1}\r\n step={0.01}\r\n value={muted ? 0 : volume}\r\n onChange={handleSliderChange}\r\n aria-label=\"Volume\"\r\n style={{\r\n background: `linear-gradient(to right, var(--plex-primary) 0%, var(--plex-primary) ${\r\n (muted ? 0 : volume) * 100\r\n }%, var(--plex-progress-bg) ${(muted ? 0 : volume) * 100}%, var(--plex-progress-bg) 100%)`,\r\n }}\r\n />\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\nexport default VolumeControl;\r\n","// Settings Menu Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React, { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { SettingsIcon, CheckIcon } from './Icons';\r\nimport { VideoSource, TextTrack } from '../types';\r\n\r\ninterface SettingsMenuProps {\r\n playbackRate: number;\r\n playbackSpeeds: number[];\r\n onPlaybackRateChange: (rate: number) => void;\r\n qualityEnabled: boolean;\r\n sources?: VideoSource[];\r\n currentQuality?: string;\r\n onQualityChange?: (quality: string) => void;\r\n captionsEnabled?: boolean;\r\n textTracks?: TextTrack[];\r\n currentTrack?: string;\r\n onTrackChange?: (track: string | null) => void;\r\n}\r\n\r\ntype MenuView = 'main' | 'speed' | 'quality' | 'captions';\r\n\r\nexport const SettingsMenu: React.FC<SettingsMenuProps> = ({\r\n playbackRate,\r\n playbackSpeeds,\r\n onPlaybackRateChange,\r\n qualityEnabled,\r\n sources,\r\n currentQuality,\r\n onQualityChange,\r\n captionsEnabled,\r\n textTracks,\r\n currentTrack,\r\n onTrackChange,\r\n}) => {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [currentView, setCurrentView] = useState<MenuView>('main');\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n // Close menu when clicking outside\r\n useEffect(() => {\r\n const handleClickOutside = (e: MouseEvent) => {\r\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\r\n setIsOpen(false);\r\n setCurrentView('main');\r\n }\r\n };\r\n\r\n if (isOpen) {\r\n document.addEventListener('mousedown', handleClickOutside);\r\n return () => document.removeEventListener('mousedown', handleClickOutside);\r\n }\r\n return undefined;\r\n }, [isOpen]);\r\n\r\n const handleToggle = useCallback(() => {\r\n setIsOpen((prev) => !prev);\r\n setCurrentView('main');\r\n }, []);\r\n\r\n const handleSpeedSelect = useCallback(\r\n (speed: number) => {\r\n onPlaybackRateChange(speed);\r\n setCurrentView('main');\r\n },\r\n [onPlaybackRateChange]\r\n );\r\n\r\n const handleQualitySelect = useCallback(\r\n (quality: string) => {\r\n onQualityChange?.(quality);\r\n setCurrentView('main');\r\n },\r\n [onQualityChange]\r\n );\r\n\r\n const handleTrackSelect = useCallback(\r\n (track: string | null) => {\r\n onTrackChange?.(track);\r\n setCurrentView('main');\r\n },\r\n [onTrackChange]\r\n );\r\n\r\n const renderMainMenu = () => (\r\n <>\r\n <div className=\"plex-video-player__settings-title\">Settings</div>\r\n <button\r\n className=\"plex-video-player__settings-item\"\r\n onClick={() => setCurrentView('speed')}\r\n type=\"button\"\r\n >\r\n <span>Playback Speed</span>\r\n <span>{playbackRate === 1 ? 'Normal' : `${playbackRate}x`}</span>\r\n </button>\r\n {qualityEnabled && sources && sources.length > 1 && (\r\n <button\r\n className=\"plex-video-player__settings-item\"\r\n onClick={() => setCurrentView('quality')}\r\n type=\"button\"\r\n >\r\n <span>Quality</span>\r\n <span>{currentQuality || 'Auto'}</span>\r\n </button>\r\n )}\r\n {captionsEnabled && textTracks && textTracks.length > 0 && (\r\n <button\r\n className=\"plex-video-player__settings-item\"\r\n onClick={() => setCurrentView('captions')}\r\n type=\"button\"\r\n >\r\n <span>Captions</span>\r\n <span>{currentTrack || 'Off'}</span>\r\n </button>\r\n )}\r\n </>\r\n );\r\n\r\n const renderSpeedMenu = () => (\r\n <>\r\n <button\r\n className=\"plex-video-player__settings-title\"\r\n onClick={() => setCurrentView('main')}\r\n style={{ cursor: 'pointer', border: 'none', background: 'transparent', width: '100%', textAlign: 'left' }}\r\n type=\"button\"\r\n >\r\n ← Playback Speed\r\n </button>\r\n <div className=\"plex-video-player__speed-menu\">\r\n {playbackSpeeds.map((speed) => (\r\n <button\r\n key={speed}\r\n className={`plex-video-player__speed-btn ${\r\n playbackRate === speed ? 'plex-video-player__speed-btn--active' : ''\r\n }`}\r\n onClick={() => handleSpeedSelect(speed)}\r\n type=\"button\"\r\n >\r\n {speed === 1 ? 'Normal' : `${speed}x`}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n );\r\n\r\n const renderQualityMenu = () => (\r\n <>\r\n <button\r\n className=\"plex-video-player__settings-title\"\r\n onClick={() => setCurrentView('main')}\r\n style={{ cursor: 'pointer', border: 'none', background: 'transparent', width: '100%', textAlign: 'left' }}\r\n type=\"button\"\r\n >\r\n ← Quality\r\n </button>\r\n <div className=\"plex-video-player__quality-menu\">\r\n {sources?.map((source) => (\r\n <button\r\n key={source.quality || source.src}\r\n className={`plex-video-player__settings-item ${\r\n currentQuality === source.quality ? 'plex-video-player__settings-item--active' : ''\r\n }`}\r\n onClick={() => handleQualitySelect(source.quality || source.src)}\r\n type=\"button\"\r\n >\r\n <span>{source.label || source.quality || 'Unknown'}</span>\r\n {currentQuality === source.quality && <CheckIcon size={16} />}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n );\r\n\r\n const renderCaptionsMenu = () => (\r\n <>\r\n <button\r\n className=\"plex-video-player__settings-title\"\r\n onClick={() => setCurrentView('main')}\r\n style={{ cursor: 'pointer', border: 'none', background: 'transparent', width: '100%', textAlign: 'left' }}\r\n type=\"button\"\r\n >\r\n ← Captions\r\n </button>\r\n <div className=\"plex-video-player__quality-menu\">\r\n <button\r\n className={`plex-video-player__settings-item ${\r\n !currentTrack ? 'plex-video-player__settings-item--active' : ''\r\n }`}\r\n onClick={() => handleTrackSelect(null)}\r\n type=\"button\"\r\n >\r\n <span>Off</span>\r\n {!currentTrack && <CheckIcon size={16} />}\r\n </button>\r\n {textTracks?.map((track) => (\r\n <button\r\n key={track.srclang}\r\n className={`plex-video-player__settings-item ${\r\n currentTrack === track.srclang ? 'plex-video-player__settings-item--active' : ''\r\n }`}\r\n onClick={() => handleTrackSelect(track.srclang)}\r\n type=\"button\"\r\n >\r\n <span>{track.label}</span>\r\n {currentTrack === track.srclang && <CheckIcon size={16} />}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n );\r\n\r\n return (\r\n <div className=\"plex-video-player__settings-container\" ref={containerRef}>\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={handleToggle}\r\n aria-label=\"Settings\"\r\n aria-expanded={isOpen}\r\n title=\"Settings\"\r\n type=\"button\"\r\n >\r\n <SettingsIcon />\r\n </button>\r\n {isOpen && (\r\n <div\r\n className={`plex-video-player__settings-menu ${\r\n isOpen ? 'plex-video-player__settings-menu--open' : ''\r\n }`}\r\n >\r\n {currentView === 'main' && renderMainMenu()}\r\n {currentView === 'speed' && renderSpeedMenu()}\r\n {currentView === 'quality' && renderQualityMenu()}\r\n {currentView === 'captions' && renderCaptionsMenu()}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\nexport default SettingsMenu;\r\n","// Ad Overlay Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\nimport { VastAdInfo } from '../types';\r\nimport { formatTime } from '../utils/helpers';\r\nimport { ExternalLinkIcon } from './Icons';\r\n\r\ninterface AdOverlayProps {\r\n ad: VastAdInfo;\r\n timeRemaining: number;\r\n canSkip: boolean;\r\n onSkip: () => void;\r\n onClick: () => void;\r\n}\r\n\r\nexport const AdOverlay: React.FC<AdOverlayProps> = ({\r\n ad,\r\n timeRemaining,\r\n canSkip,\r\n onSkip,\r\n onClick,\r\n}) => {\r\n const skipText = canSkip\r\n ? 'Skip Ad'\r\n : `Skip in ${Math.ceil(timeRemaining)}s`;\r\n\r\n return (\r\n <div className=\"plex-video-player__ad-overlay\" onClick={onClick}>\r\n {/* Ad Info Badge */}\r\n <div className=\"plex-video-player__ad-info\">\r\n <span className=\"plex-video-player__ad-badge\">Ad</span>\r\n <span>{formatTime(timeRemaining)} remaining</span>\r\n </div>\r\n\r\n {/* Learn More Button */}\r\n {ad.clickThrough && (\r\n <button\r\n className=\"plex-video-player__ad-learn-more\"\r\n onClick={(e) => {\r\n e.stopPropagation();\r\n onClick();\r\n }}\r\n type=\"button\"\r\n >\r\n <ExternalLinkIcon size={16} />\r\n Learn More\r\n </button>\r\n )}\r\n\r\n {/* Skip Button */}\r\n <button\r\n className=\"plex-video-player__ad-skip\"\r\n onClick={(e) => {\r\n e.stopPropagation();\r\n if (canSkip) onSkip();\r\n }}\r\n disabled={!canSkip}\r\n type=\"button\"\r\n >\r\n {skipText}\r\n </button>\r\n </div>\r\n );\r\n};\r\n\r\nexport default AdOverlay;\r\n","// Error Display Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\nimport { ErrorIcon } from './Icons';\r\n\r\ninterface ErrorDisplayProps {\r\n error: MediaError | null;\r\n onRetry: () => void;\r\n}\r\n\r\nconst ERROR_MESSAGES: Record<number, string> = {\r\n 1: 'The video playback was aborted.',\r\n 2: 'A network error occurred while loading the video.',\r\n 3: 'The video format is not supported or cannot be decoded.',\r\n 4: 'The video source is not supported.',\r\n};\r\n\r\nexport const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ error, onRetry }) => {\r\n const errorCode = error?.code || 0;\r\n const message = ERROR_MESSAGES[errorCode] || 'An unknown error occurred.';\r\n\r\n return (\r\n <div className=\"plex-video-player__error\">\r\n <ErrorIcon className=\"plex-video-player__error-icon\" size={60} />\r\n <div className=\"plex-video-player__error-message\">{message}</div>\r\n {errorCode > 0 && (\r\n <div className=\"plex-video-player__error-code\">Error Code: {errorCode}</div>\r\n )}\r\n <button\r\n className=\"plex-video-player__error-retry\"\r\n onClick={onRetry}\r\n type=\"button\"\r\n >\r\n Try Again\r\n </button>\r\n </div>\r\n );\r\n};\r\n\r\nexport default ErrorDisplay;\r\n","// Loading Spinner Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\n\r\ninterface LoaderProps {\r\n visible: boolean;\r\n}\r\n\r\nexport const Loader: React.FC<LoaderProps> = ({ visible }) => {\r\n if (!visible) return null;\r\n\r\n return (\r\n <div className=\"plex-video-player__loader\">\r\n <div className=\"plex-video-player__loader-spinner\" />\r\n </div>\r\n );\r\n};\r\n\r\nexport default Loader;\r\n","// useKeyboard Hook for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { useEffect, useCallback } from 'react';\r\nimport { HotkeyConfig } from '../types';\r\n\r\nconst DEFAULT_HOTKEYS: Required<HotkeyConfig> = {\r\n play: 'Space',\r\n mute: 'm',\r\n fullscreen: 'f',\r\n pip: 'p',\r\n seekForward: 'ArrowRight',\r\n seekBackward: 'ArrowLeft',\r\n volumeUp: 'ArrowUp',\r\n volumeDown: 'ArrowDown',\r\n};\r\n\r\ninterface UseKeyboardOptions {\r\n enabled: boolean;\r\n hotkeys?: HotkeyConfig;\r\n onPlay: () => void;\r\n onMute: () => void;\r\n onFullscreen: () => void;\r\n onPip: () => void;\r\n onSeek: (delta: number) => void;\r\n onVolume: (delta: number) => void;\r\n containerRef: React.RefObject<HTMLDivElement>;\r\n}\r\n\r\nexport const useKeyboard = ({\r\n enabled,\r\n hotkeys = {},\r\n onPlay,\r\n onMute,\r\n onFullscreen,\r\n onPip,\r\n onSeek,\r\n onVolume,\r\n containerRef,\r\n}: UseKeyboardOptions): void => {\r\n const mergedHotkeys = { ...DEFAULT_HOTKEYS, ...hotkeys };\r\n\r\n const handleKeyDown = useCallback(\r\n (event: KeyboardEvent) => {\r\n if (!enabled) return;\r\n\r\n // Ignore if typing in an input\r\n const target = event.target as HTMLElement;\r\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {\r\n return;\r\n }\r\n\r\n // Check if focus is within the player container\r\n const container = containerRef.current;\r\n if (!container || !container.contains(document.activeElement)) {\r\n return;\r\n }\r\n\r\n const key = event.key;\r\n\r\n switch (key) {\r\n case mergedHotkeys.play:\r\n case ' ':\r\n event.preventDefault();\r\n onPlay();\r\n break;\r\n case mergedHotkeys.mute:\r\n case 'M':\r\n event.preventDefault();\r\n onMute();\r\n break;\r\n case mergedHotkeys.fullscreen:\r\n case 'F':\r\n event.preventDefault();\r\n onFullscreen();\r\n break;\r\n case mergedHotkeys.pip:\r\n case 'P':\r\n event.preventDefault();\r\n onPip();\r\n break;\r\n case mergedHotkeys.seekForward:\r\n event.preventDefault();\r\n onSeek(event.shiftKey ? 30 : 10);\r\n break;\r\n case mergedHotkeys.seekBackward:\r\n event.preventDefault();\r\n onSeek(event.shiftKey ? -30 : -10);\r\n break;\r\n case mergedHotkeys.volumeUp:\r\n event.preventDefault();\r\n onVolume(0.1);\r\n break;\r\n case mergedHotkeys.volumeDown:\r\n event.preventDefault();\r\n onVolume(-0.1);\r\n break;\r\n case '0':\r\n case '1':\r\n case '2':\r\n case '3':\r\n case '4':\r\n case '5':\r\n case '6':\r\n case '7':\r\n case '8':\r\n case '9':\r\n event.preventDefault();\r\n // Seek to percentage of video\r\n onSeek(-Infinity); // Will be handled by caller with percent\r\n break;\r\n default:\r\n break;\r\n }\r\n },\r\n [enabled, mergedHotkeys, onPlay, onMute, onFullscreen, onPip, onSeek, onVolume, containerRef]\r\n );\r\n\r\n useEffect(() => {\r\n if (!enabled) return;\r\n\r\n document.addEventListener('keydown', handleKeyDown);\r\n\r\n return () => {\r\n document.removeEventListener('keydown', handleKeyDown);\r\n };\r\n }, [enabled, handleKeyDown]);\r\n};\r\n\r\nexport default useKeyboard;\r\n","// VAST Ads Parser for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { VastAdInfo, VastConfig } from '../types';\r\n\r\ninterface VastMediaFile {\r\n url: string;\r\n type: string;\r\n width: number;\r\n height: number;\r\n bitrate?: number;\r\n}\r\n\r\ninterface VastAd {\r\n id: string;\r\n title: string;\r\n description?: string;\r\n duration: number;\r\n skipOffset?: number;\r\n clickThrough?: string;\r\n clickTracking?: string[];\r\n impressionUrls: string[];\r\n mediaFiles: VastMediaFile[];\r\n trackingEvents: Record<string, string[]>;\r\n}\r\n\r\ninterface VastResponse {\r\n ads: VastAd[];\r\n error?: string;\r\n}\r\n\r\n/**\r\n * Parse VAST XML response\r\n */\r\nexport const parseVastXml = (xmlString: string): VastResponse => {\r\n const parser = new DOMParser();\r\n const doc = parser.parseFromString(xmlString, 'text/xml');\r\n \r\n const parseError = doc.querySelector('parsererror');\r\n if (parseError) {\r\n return { ads: [], error: 'Failed to parse VAST XML' };\r\n }\r\n\r\n const ads: VastAd[] = [];\r\n const adElements = doc.querySelectorAll('Ad');\r\n\r\n adElements.forEach((adElement) => {\r\n const inLine = adElement.querySelector('InLine');\r\n if (!inLine) return;\r\n\r\n const id = adElement.getAttribute('id') || '';\r\n const title = inLine.querySelector('AdTitle')?.textContent || '';\r\n const description = inLine.querySelector('Description')?.textContent || undefined;\r\n\r\n // Parse creatives\r\n const creatives = inLine.querySelectorAll('Creative');\r\n let duration = 0;\r\n let skipOffset: number | undefined;\r\n let clickThrough: string | undefined;\r\n const clickTracking: string[] = [];\r\n const mediaFiles: VastMediaFile[] = [];\r\n const trackingEvents: Record<string, string[]> = {};\r\n\r\n creatives.forEach((creative) => {\r\n const linear = creative.querySelector('Linear');\r\n if (!linear) return;\r\n\r\n // Duration\r\n const durationStr = linear.querySelector('Duration')?.textContent;\r\n if (durationStr) {\r\n duration = parseVastDuration(durationStr);\r\n }\r\n\r\n // Skip offset\r\n const skipOffsetAttr = linear.getAttribute('skipoffset');\r\n if (skipOffsetAttr) {\r\n skipOffset = parseVastDuration(skipOffsetAttr);\r\n }\r\n\r\n // Media files\r\n const mediaFileElements = linear.querySelectorAll('MediaFile');\r\n mediaFileElements.forEach((mediaFile) => {\r\n const url = mediaFile.textContent?.trim();\r\n if (url) {\r\n mediaFiles.push({\r\n url,\r\n type: mediaFile.getAttribute('type') || 'video/mp4',\r\n width: parseInt(mediaFile.getAttribute('width') || '0', 10),\r\n height: parseInt(mediaFile.getAttribute('height') || '0', 10),\r\n bitrate: parseInt(mediaFile.getAttribute('bitrate') || '0', 10) || undefined,\r\n });\r\n }\r\n });\r\n\r\n // Video clicks\r\n const videoClicks = linear.querySelector('VideoClicks');\r\n if (videoClicks) {\r\n clickThrough = videoClicks.querySelector('ClickThrough')?.textContent?.trim();\r\n videoClicks.querySelectorAll('ClickTracking').forEach((ct) => {\r\n const url = ct.textContent?.trim();\r\n if (url) clickTracking.push(url);\r\n });\r\n }\r\n\r\n // Tracking events\r\n const trackingEventsEl = linear.querySelectorAll('TrackingEvents Tracking');\r\n trackingEventsEl.forEach((tracking) => {\r\n const event = tracking.getAttribute('event');\r\n const url = tracking.textContent?.trim();\r\n if (event && url) {\r\n if (!trackingEvents[event]) {\r\n trackingEvents[event] = [];\r\n }\r\n trackingEvents[event].push(url);\r\n }\r\n });\r\n });\r\n\r\n // Impression URLs\r\n const impressionUrls: string[] = [];\r\n inLine.querySelectorAll('Impression').forEach((impression) => {\r\n const url = impression.textContent?.trim();\r\n if (url) impressionUrls.push(url);\r\n });\r\n\r\n if (mediaFiles.length > 0) {\r\n ads.push({\r\n id,\r\n title,\r\n description,\r\n duration,\r\n skipOffset,\r\n clickThrough,\r\n clickTracking,\r\n impressionUrls,\r\n mediaFiles,\r\n trackingEvents,\r\n });\r\n }\r\n });\r\n\r\n return { ads };\r\n};\r\n\r\n/**\r\n * Parse VAST duration string (HH:MM:SS or HH:MM:SS.mmm)\r\n */\r\nconst parseVastDuration = (duration: string): number => {\r\n if (duration.includes('%')) {\r\n return -1; // Percentage-based, handle separately\r\n }\r\n\r\n const parts = duration.split(':');\r\n if (parts.length !== 3) return 0;\r\n\r\n const hours = parseInt(parts[0], 10);\r\n const minutes = parseInt(parts[1], 10);\r\n const seconds = parseFloat(parts[2]);\r\n\r\n return hours * 3600 + minutes * 60 + seconds;\r\n};\r\n\r\n/**\r\n * Fetch and parse VAST ad\r\n */\r\nexport const fetchVastAd = async (config: VastConfig): Promise<VastAd | null> => {\r\n try {\r\n const response = await fetch(config.url, {\r\n method: 'GET',\r\n headers: {\r\n 'Accept': 'application/xml',\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n console.error('VAST fetch failed:', response.status);\r\n return null;\r\n }\r\n\r\n const xmlString = await response.text();\r\n const result = parseVastXml(xmlString);\r\n\r\n if (result.error || result.ads.length === 0) {\r\n console.error('VAST parse error:', result.error || 'No ads found');\r\n return null;\r\n }\r\n\r\n const ad = result.ads[0];\r\n \r\n // Apply skip delay from config if not in VAST\r\n if (config.skipDelay !== undefined && ad.skipOffset === undefined) {\r\n ad.skipOffset = config.skipDelay;\r\n }\r\n\r\n return ad;\r\n } catch (error) {\r\n console.error('VAST fetch error:', error);\r\n return null;\r\n }\r\n};\r\n\r\n/**\r\n * Select best media file based on device capabilities\r\n */\r\nexport const selectBestMediaFile = (mediaFiles: VastMediaFile[]): VastMediaFile | null => {\r\n if (mediaFiles.length === 0) return null;\r\n\r\n const video = document.createElement('video');\r\n const supportedFiles = mediaFiles.filter((file) => {\r\n return video.canPlayType(file.type) !== '';\r\n });\r\n\r\n if (supportedFiles.length === 0) return null;\r\n\r\n // Sort by resolution (prefer higher) and bitrate\r\n supportedFiles.sort((a, b) => {\r\n const resA = a.width * a.height;\r\n const resB = b.width * b.height;\r\n if (resA !== resB) return resB - resA;\r\n return (b.bitrate || 0) - (a.bitrate || 0);\r\n });\r\n\r\n // On mobile, prefer lower resolution\r\n const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);\r\n if (isMobile && supportedFiles.length > 1) {\r\n return supportedFiles[supportedFiles.length - 1];\r\n }\r\n\r\n return supportedFiles[0];\r\n};\r\n\r\n/**\r\n * Fire tracking pixel\r\n */\r\nexport const fireTrackingPixel = (url: string): void => {\r\n const img = new Image();\r\n img.src = url;\r\n};\r\n\r\n/**\r\n * Fire multiple tracking pixels\r\n */\r\nexport const fireTrackingPixels = (urls: string[]): void => {\r\n urls.forEach(fireTrackingPixel);\r\n};\r\n\r\n/**\r\n * Convert VAST ad to player-friendly format\r\n */\r\nexport const convertToAdInfo = (ad: VastAd): VastAdInfo => {\r\n return {\r\n id: ad.id,\r\n title: ad.title,\r\n duration: ad.duration,\r\n skipOffset: ad.skipOffset,\r\n clickThrough: ad.clickThrough,\r\n };\r\n};\r\n\r\nexport type { VastAd, VastMediaFile, VastResponse };\r\n","// useVast Hook for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { useState, useCallback, useRef, useEffect } from 'react';\r\nimport { VastConfig, VastAdInfo } from '../types';\r\nimport {\r\n fetchVastAd,\r\n selectBestMediaFile,\r\n fireTrackingPixels,\r\n convertToAdInfo,\r\n VastAd,\r\n} from '../utils/vast';\r\n\r\ninterface UseVastOptions {\r\n vastConfig?: VastConfig | VastConfig[];\r\n videoRef: React.RefObject<HTMLVideoElement>;\r\n onAdStart?: (ad: VastAdInfo) => void;\r\n onAdEnd?: () => void;\r\n onAdSkip?: () => void;\r\n onAdError?: (error: Error) => void;\r\n}\r\n\r\ninterface UseVastReturn {\r\n isAdPlaying: boolean;\r\n currentAd: VastAdInfo | null;\r\n adTimeRemaining: number;\r\n canSkip: boolean;\r\n skipAd: () => void;\r\n handleAdClick: () => void;\r\n checkForAd: (currentTime: number, duration: number) => void;\r\n}\r\n\r\nexport const useVast = ({\r\n vastConfig,\r\n videoRef,\r\n onAdStart,\r\n onAdEnd,\r\n onAdSkip,\r\n onAdError,\r\n}: UseVastOptions): UseVastReturn => {\r\n const [isAdPlaying, setIsAdPlaying] = useState(false);\r\n const [currentAd, setCurrentAd] = useState<VastAdInfo | null>(null);\r\n const [adTimeRemaining, setAdTimeRemaining] = useState(0);\r\n const [canSkip, setCanSkip] = useState(false);\r\n\r\n const currentVastAd = useRef<VastAd | null>(null);\r\n const originalSrc = useRef<string>('');\r\n const originalTime = useRef<number>(0);\r\n const playedPositions = useRef<Set<string>>(new Set());\r\n const adIntervalRef = useRef<number | null>(null);\r\n\r\n // Normalize config to array\r\n const vastConfigs = Array.isArray(vastConfig) ? vastConfig : vastConfig ? [vastConfig] : [];\r\n\r\n // Play ad\r\n const playAd = useCallback(\r\n async (config: VastConfig) => {\r\n const video = videoRef.current;\r\n if (!video || isAdPlaying) return;\r\n\r\n try {\r\n const ad = await fetchVastAd(config);\r\n if (!ad) {\r\n onAdError?.(new Error('Failed to fetch VAST ad'));\r\n return;\r\n }\r\n\r\n const mediaFile = selectBestMediaFile(ad.mediaFiles);\r\n if (!mediaFile) {\r\n onAdError?.(new Error('No compatible media file found'));\r\n return;\r\n }\r\n\r\n // Store original video state\r\n originalSrc.current = video.currentSrc;\r\n originalTime.current = video.currentTime;\r\n\r\n // Fire impression pixels\r\n fireTrackingPixels(ad.impressionUrls);\r\n\r\n // Set up ad\r\n currentVastAd.current = ad;\r\n setCurrentAd(convertToAdInfo(ad));\r\n setIsAdPlaying(true);\r\n setAdTimeRemaining(ad.duration);\r\n setCanSkip(ad.skipOffset === undefined || ad.skipOffset === 0);\r\n\r\n // Change video source to ad\r\n video.src = mediaFile.url;\r\n video.currentTime = 0;\r\n await video.play();\r\n\r\n // Fire start tracking\r\n if (ad.trackingEvents.start) {\r\n fireTrackingPixels(ad.trackingEvents.start);\r\n }\r\n\r\n onAdStart?.(convertToAdInfo(ad));\r\n\r\n // Start ad timer\r\n adIntervalRef.current = window.setInterval(() => {\r\n const remaining = Math.max(0, ad.duration - video.currentTime);\r\n setAdTimeRemaining(remaining);\r\n\r\n // Check if can skip\r\n if (ad.skipOffset && video.currentTime >= ad.skipOffset) {\r\n setCanSkip(true);\r\n }\r\n\r\n // Track quartiles\r\n const percent = (video.currentTime / ad.duration) * 100;\r\n if (percent >= 25 && ad.trackingEvents.firstQuartile) {\r\n fireTrackingPixels(ad.trackingEvents.firstQuartile);\r\n }\r\n if (percent >= 50 && ad.trackingEvents.midpoint) {\r\n fireTrackingPixels(ad.trackingEvents.midpoint);\r\n }\r\n if (percent >= 75 && ad.trackingEvents.thirdQuartile) {\r\n fireTrackingPixels(ad.trackingEvents.thirdQuartile);\r\n }\r\n }, 250);\r\n\r\n // Handle ad completion\r\n const handleAdEnd = () => {\r\n if (ad.trackingEvents.complete) {\r\n fireTrackingPixels(ad.trackingEvents.complete);\r\n }\r\n endAd();\r\n };\r\n\r\n video.addEventListener('ended', handleAdEnd, { once: true });\r\n } catch (error) {\r\n onAdError?.(error instanceof Error ? error : new Error('Ad playback failed'));\r\n endAd();\r\n }\r\n },\r\n [videoRef, isAdPlaying, onAdStart, onAdError]\r\n );\r\n\r\n // End ad and restore original video\r\n const endAd = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n // Clear interval\r\n if (adIntervalRef.current) {\r\n clearInterval(adIntervalRef.current);\r\n adIntervalRef.current = null;\r\n }\r\n\r\n // Restore original video\r\n if (originalSrc.current) {\r\n video.src = originalSrc.current;\r\n video.currentTime = originalTime.current;\r\n video.play().catch(() => {});\r\n }\r\n\r\n setIsAdPlaying(false);\r\n setCurrentAd(null);\r\n setAdTimeRemaining(0);\r\n setCanSkip(false);\r\n currentVastAd.current = null;\r\n\r\n onAdEnd?.();\r\n }, [videoRef, onAdEnd]);\r\n\r\n // Skip ad\r\n const skipAd = useCallback(() => {\r\n if (!canSkip || !currentVastAd.current) return;\r\n\r\n const ad = currentVastAd.current;\r\n if (ad.trackingEvents.skip) {\r\n fireTrackingPixels(ad.trackingEvents.skip);\r\n }\r\n\r\n onAdSkip?.();\r\n endAd();\r\n }, [canSkip, endAd, onAdSkip]);\r\n\r\n // Handle ad click\r\n const handleAdClick = useCallback(() => {\r\n if (!currentVastAd.current) return;\r\n\r\n const ad = currentVastAd.current;\r\n if (ad.clickThrough) {\r\n window.open(ad.clickThrough, '_blank');\r\n }\r\n if (ad.clickTracking) {\r\n fireTrackingPixels(ad.clickTracking);\r\n }\r\n }, []);\r\n\r\n // Check for ad at position\r\n const checkForAd = useCallback(\r\n (currentTime: number, duration: number) => {\r\n if (isAdPlaying || vastConfigs.length === 0) return;\r\n\r\n vastConfigs.forEach((config) => {\r\n const position = config.position || 'preroll';\r\n const key = `${position}-${config.midrollTime || 0}`;\r\n\r\n if (playedPositions.current.has(key)) return;\r\n\r\n let shouldPlay = false;\r\n\r\n switch (position) {\r\n case 'preroll':\r\n shouldPlay = currentTime === 0;\r\n break;\r\n case 'midroll':\r\n if (config.midrollTime && currentTime >= config.midrollTime) {\r\n shouldPlay = true;\r\n }\r\n break;\r\n case 'postroll':\r\n shouldPlay = currentTime >= duration - 0.5;\r\n break;\r\n }\r\n\r\n if (shouldPlay) {\r\n playedPositions.current.add(key);\r\n playAd(config);\r\n }\r\n });\r\n },\r\n [vastConfigs, isAdPlaying, playAd]\r\n );\r\n\r\n // Cleanup on unmount\r\n useEffect(() => {\r\n return () => {\r\n if (adIntervalRef.current) {\r\n clearInterval(adIntervalRef.current);\r\n }\r\n };\r\n }, []);\r\n\r\n return {\r\n isAdPlaying,\r\n currentAd,\r\n adTimeRemaining,\r\n canSkip,\r\n skipAd,\r\n handleAdClick,\r\n checkForAd,\r\n };\r\n};\r\n\r\nexport default useVast;\r\n","// PlexVideo Player - Main Component\r\n// © FRAMESET STUDIO\r\n\r\nimport React, {\r\n useRef,\r\n useState,\r\n useEffect,\r\n useCallback,\r\n forwardRef,\r\n useImperativeHandle,\r\n useMemo,\r\n} from 'react';\r\nimport { ProgressBar } from './ProgressBar';\r\nimport { VolumeControl } from './VolumeControl';\r\nimport { SettingsMenu } from './SettingsMenu';\r\nimport { AdOverlay } from './AdOverlay';\r\nimport { ErrorDisplay } from './ErrorDisplay';\r\nimport { Loader } from './Loader';\r\nimport {\r\n PlayIcon,\r\n PauseIcon,\r\n FullscreenIcon,\r\n FullscreenExitIcon,\r\n PipIcon,\r\n} from './Icons';\r\nimport { useKeyboard } from '../hooks/useKeyboard';\r\nimport { useVast } from '../hooks/useVast';\r\nimport { formatTime, throttle } from '../utils/helpers';\r\nimport type {\r\n PlexVideoPlayerProps,\r\n PlexVideoPlayerRef,\r\n VideoSource,\r\n} from '../types';\r\n\r\nexport const PlexVideoPlayer = forwardRef<PlexVideoPlayerRef, PlexVideoPlayerProps>(\r\n (props, ref) => {\r\n const {\r\n src,\r\n poster,\r\n autoPlay = false,\r\n muted = false,\r\n loop = false,\r\n controls = true,\r\n width = '100%',\r\n className = '',\r\n style = {},\r\n pip = true,\r\n fullscreen = true,\r\n playbackSpeed = true,\r\n playbackSpeeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2],\r\n volume: volumeEnabled = true,\r\n initialVolume = 1,\r\n progressBar = true,\r\n timeDisplay = true,\r\n qualitySelector = true,\r\n textTracks = [],\r\n vast,\r\n keyboard = true,\r\n hotkeys,\r\n accentColor = '#e50914',\r\n controlsTimeout = 3000,\r\n doubleClickFullscreen = true,\r\n clickToPlay = true,\r\n preload = 'metadata',\r\n onPlay,\r\n onPause,\r\n onEnded,\r\n onTimeUpdate,\r\n onProgress,\r\n onVolumeChange,\r\n onFullscreenChange,\r\n onPipChange,\r\n onQualityChange,\r\n onError,\r\n onAdStart,\r\n onAdEnd,\r\n onAdSkip,\r\n onAdError,\r\n onReady,\r\n } = props;\r\n\r\n // Refs\r\n const videoRef = useRef<HTMLVideoElement>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const controlsTimeoutRef = useRef<NodeJS.Timeout | null>(null);\r\n const lastClickRef = useRef<number>(0);\r\n\r\n // State\r\n const [isPlaying, setIsPlaying] = useState(false);\r\n const [currentTime, setCurrentTime] = useState(0);\r\n const [duration, setDuration] = useState(0);\r\n const [buffered, setBuffered] = useState(0);\r\n const [volumeState, setVolumeState] = useState(muted ? 0 : initialVolume);\r\n const [isMuted, setIsMuted] = useState(muted);\r\n const [isFullscreen, setIsFullscreen] = useState(false);\r\n const [isPipActive, setIsPipActive] = useState(false);\r\n const [isLoading, setIsLoading] = useState(true);\r\n const [error, setError] = useState<MediaError | null>(null);\r\n const [showControls, setShowControls] = useState(true);\r\n const [playbackRate, setPlaybackRateState] = useState(1);\r\n const [currentQuality, setCurrentQuality] = useState<string>('auto');\r\n const [currentTrackLabel, setCurrentTrackLabel] = useState<string | null>(null);\r\n\r\n // Parse sources\r\n const sources = useMemo<VideoSource[]>(() => {\r\n if (typeof src === 'string') {\r\n return [{ src, type: 'video/mp4' }];\r\n }\r\n return src as VideoSource[];\r\n }, [src]);\r\n\r\n // VAST ads hook\r\n const {\r\n currentAd,\r\n isAdPlaying,\r\n adTimeRemaining,\r\n canSkip,\r\n skipAd,\r\n handleAdClick,\r\n checkForAd,\r\n } = useVast({\r\n vastConfig: vast,\r\n videoRef,\r\n onAdStart,\r\n onAdEnd,\r\n onAdSkip,\r\n onAdError,\r\n });\r\n\r\n // Player control functions\r\n const play = useCallback(async () => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n try {\r\n await video.play();\r\n } catch (err) {\r\n console.error('Play error:', err);\r\n }\r\n }, []);\r\n\r\n const pause = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.pause();\r\n }, []);\r\n\r\n const stop = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.pause();\r\n video.currentTime = 0;\r\n }, []);\r\n\r\n const togglePlay = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n if (video.paused) {\r\n play();\r\n } else {\r\n pause();\r\n }\r\n }, [play, pause]);\r\n\r\n const seek = useCallback((time: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.currentTime = Math.max(0, Math.min(time, video.duration || 0));\r\n }, []);\r\n\r\n const setVolume = useCallback((value: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n const newVolume = Math.max(0, Math.min(1, value));\r\n video.volume = newVolume;\r\n video.muted = newVolume === 0;\r\n setVolumeState(newVolume);\r\n setIsMuted(newVolume === 0);\r\n }, []);\r\n\r\n const mute = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.muted = true;\r\n setIsMuted(true);\r\n }, []);\r\n\r\n const unmute = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.muted = false;\r\n if (video.volume === 0) {\r\n video.volume = 0.5;\r\n setVolumeState(0.5);\r\n }\r\n setIsMuted(false);\r\n }, []);\r\n\r\n const toggleMute = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n if (video.muted) {\r\n unmute();\r\n } else {\r\n mute();\r\n }\r\n }, [mute, unmute]);\r\n\r\n const enterFullscreen = useCallback(async () => {\r\n const container = containerRef.current;\r\n if (!container) return;\r\n try {\r\n await container.requestFullscreen();\r\n setIsFullscreen(true);\r\n } catch (err) {\r\n console.error('Fullscreen error:', err);\r\n }\r\n }, []);\r\n\r\n const exitFullscreen = useCallback(async () => {\r\n try {\r\n if (document.fullscreenElement) {\r\n await document.exitFullscreen();\r\n setIsFullscreen(false);\r\n }\r\n } catch (err) {\r\n console.error('Exit fullscreen error:', err);\r\n }\r\n }, []);\r\n\r\n const toggleFullscreen = useCallback(async () => {\r\n if (document.fullscreenElement) {\r\n await exitFullscreen();\r\n } else {\r\n await enterFullscreen();\r\n }\r\n }, [enterFullscreen, exitFullscreen]);\r\n\r\n const enterPip = useCallback(async () => {\r\n const video = videoRef.current;\r\n if (!video || !document.pictureInPictureEnabled) return;\r\n try {\r\n await video.requestPictureInPicture();\r\n setIsPipActive(true);\r\n } catch (err) {\r\n console.error('PiP error:', err);\r\n }\r\n }, []);\r\n\r\n const exitPip = useCallback(async () => {\r\n try {\r\n if (document.pictureInPictureElement) {\r\n await document.exitPictureInPicture();\r\n setIsPipActive(false);\r\n }\r\n } catch (err) {\r\n console.error('Exit PiP error:', err);\r\n }\r\n }, []);\r\n\r\n const togglePip = useCallback(async () => {\r\n if (document.pictureInPictureElement) {\r\n await exitPip();\r\n } else {\r\n await enterPip();\r\n }\r\n }, [enterPip, exitPip]);\r\n\r\n const setPlaybackRate = useCallback((rate: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.playbackRate = rate;\r\n setPlaybackRateState(rate);\r\n }, []);\r\n\r\n const setQuality = useCallback((qualityLabel: string) => {\r\n const source = sources.find(s => s.label === qualityLabel || s.quality === qualityLabel);\r\n if (source && videoRef.current) {\r\n const video = videoRef.current;\r\n const wasPlaying = !video.paused;\r\n const time = video.currentTime;\r\n video.src = source.src;\r\n video.currentTime = time;\r\n if (wasPlaying) {\r\n video.play();\r\n }\r\n setCurrentQuality(qualityLabel);\r\n onQualityChange?.(qualityLabel);\r\n }\r\n }, [sources, onQualityChange]);\r\n\r\n const setTextTrack = useCallback((trackLabel: string | null) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n for (let i = 0; i < video.textTracks.length; i++) {\r\n const track = video.textTracks[i];\r\n track.mode = track.label === trackLabel ? 'showing' : 'hidden';\r\n }\r\n setCurrentTrackLabel(trackLabel);\r\n }, []);\r\n\r\n // Hide controls logic\r\n const hideControls = useCallback(() => {\r\n if (isPlaying) {\r\n setShowControls(false);\r\n }\r\n }, [isPlaying]);\r\n\r\n const showControlsTemporarily = useCallback(() => {\r\n setShowControls(true);\r\n if (controlsTimeoutRef.current) {\r\n clearTimeout(controlsTimeoutRef.current);\r\n }\r\n if (isPlaying) {\r\n controlsTimeoutRef.current = setTimeout(hideControls, controlsTimeout);\r\n }\r\n }, [isPlaying, controlsTimeout, hideControls]);\r\n\r\n const handleMouseMove = useMemo(\r\n () => throttle(showControlsTemporarily, 200),\r\n [showControlsTemporarily]\r\n );\r\n\r\n // Video click handler\r\n const handleVideoClick = useCallback(() => {\r\n if (!clickToPlay) return;\r\n \r\n const now = Date.now();\r\n if (doubleClickFullscreen && now - lastClickRef.current < 300) {\r\n toggleFullscreen();\r\n } else {\r\n togglePlay();\r\n }\r\n lastClickRef.current = now;\r\n }, [clickToPlay, doubleClickFullscreen, togglePlay, toggleFullscreen]);\r\n\r\n // Video event handlers\r\n useEffect(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const handlePlay = () => {\r\n setIsPlaying(true);\r\n onPlay?.();\r\n };\r\n\r\n const handlePause = () => {\r\n setIsPlaying(false);\r\n onPause?.();\r\n };\r\n\r\n const handleEnded = () => {\r\n setIsPlaying(false);\r\n onEnded?.();\r\n };\r\n\r\n const handleTimeUpdate = () => {\r\n setCurrentTime(video.currentTime);\r\n onTimeUpdate?.(video.currentTime);\r\n checkForAd(video.currentTime, video.duration);\r\n };\r\n\r\n const handleDurationChange = () => {\r\n setDuration(video.duration);\r\n };\r\n\r\n const handleProgressEvent = () => {\r\n if (video.buffered.length > 0) {\r\n const bufferedEnd = video.buffered.end(video.buffered.length - 1);\r\n const bufferedPercent = (bufferedEnd / video.duration) * 100;\r\n setBuffered(bufferedPercent);\r\n onProgress?.(bufferedPercent);\r\n }\r\n };\r\n\r\n const handleVolumeChangeEvent = () => {\r\n setVolumeState(video.volume);\r\n setIsMuted(video.muted);\r\n onVolumeChange?.(video.volume, video.muted);\r\n };\r\n\r\n const handleLoadedMetadata = () => {\r\n setDuration(video.duration);\r\n setIsLoading(false);\r\n onReady?.();\r\n };\r\n\r\n const handleWaiting = () => {\r\n setIsLoading(true);\r\n };\r\n\r\n const handlePlaying = () => {\r\n setIsLoading(false);\r\n };\r\n\r\n const handleCanPlay = () => {\r\n setIsLoading(false);\r\n };\r\n\r\n const handleErrorEvent = () => {\r\n setError(video.error);\r\n setIsLoading(false);\r\n onError?.(video.error);\r\n };\r\n\r\n video.addEventListener('play', handlePlay);\r\n video.addEventListener('pause', handlePause);\r\n video.addEventListener('ended', handleEnded);\r\n video.addEventListener('timeupdate', handleTimeUpdate);\r\n video.addEventListener('durationchange', handleDurationChange);\r\n video.addEventListener('progress', handleProgressEvent);\r\n video.addEventListener('volumechange', handleVolumeChangeEvent);\r\n video.addEventListener('loadedmetadata', handleLoadedMetadata);\r\n video.addEventListener('waiting', handleWaiting);\r\n video.addEventListener('playing', handlePlaying);\r\n video.addEventListener('canplay', handleCanPlay);\r\n video.addEventListener('error', handleErrorEvent);\r\n\r\n return () => {\r\n video.removeEventListener('play', handlePlay);\r\n video.removeEventListener('pause', handlePause);\r\n video.removeEventListener('ended', handleEnded);\r\n video.removeEventListener('timeupdate', handleTimeUpdate);\r\n video.removeEventListener('durationchange', handleDurationChange);\r\n video.removeEventListener('progress', handleProgressEvent);\r\n video.removeEventListener('volumechange', handleVolumeChangeEvent);\r\n video.removeEventListener('loadedmetadata', handleLoadedMetadata);\r\n video.removeEventListener('waiting', handleWaiting);\r\n video.removeEventListener('playing', handlePlaying);\r\n video.removeEventListener('canplay', handleCanPlay);\r\n video.removeEventListener('error', handleErrorEvent);\r\n };\r\n }, [onPlay, onPause, onEnded, onTimeUpdate, onProgress, onVolumeChange, onReady, onError, checkForAd]);\r\n\r\n // Fullscreen change listener\r\n useEffect(() => {\r\n const handleFullscreenChange = () => {\r\n const isFs = !!document.fullscreenElement;\r\n setIsFullscreen(isFs);\r\n onFullscreenChange?.(isFs);\r\n };\r\n\r\n document.addEventListener('fullscreenchange', handleFullscreenChange);\r\n return () => {\r\n document.removeEventListener('fullscreenchange', handleFullscreenChange);\r\n };\r\n }, [onFullscreenChange]);\r\n\r\n // PiP change listener\r\n useEffect(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const handleEnterPip = () => {\r\n setIsPipActive(true);\r\n onPipChange?.(true);\r\n };\r\n\r\n const handleLeavePip = () => {\r\n setIsPipActive(false);\r\n onPipChange?.(false);\r\n };\r\n\r\n video.addEventListener('enterpictureinpicture', handleEnterPip);\r\n video.addEventListener('leavepictureinpicture', handleLeavePip);\r\n\r\n return () => {\r\n video.removeEventListener('enterpictureinpicture', handleEnterPip);\r\n video.removeEventListener('leavepictureinpicture', handleLeavePip);\r\n };\r\n }, [onPipChange]);\r\n\r\n // Keyboard shortcuts\r\n useKeyboard({\r\n enabled: keyboard,\r\n containerRef,\r\n hotkeys,\r\n onPlay: togglePlay,\r\n onMute: toggleMute,\r\n onFullscreen: toggleFullscreen,\r\n onPip: togglePip,\r\n onSeek: (delta: number) => seek(currentTime + delta),\r\n onVolume: (delta: number) => setVolume(volumeState + delta),\r\n });\r\n\r\n // Cleanup timeout on unmount\r\n useEffect(() => {\r\n return () => {\r\n if (controlsTimeoutRef.current) {\r\n clearTimeout(controlsTimeoutRef.current);\r\n }\r\n };\r\n }, []);\r\n\r\n // Retry handler\r\n const handleRetry = useCallback(() => {\r\n setError(null);\r\n videoRef.current?.load();\r\n }, []);\r\n\r\n // Imperative handle for external control\r\n useImperativeHandle(\r\n ref,\r\n () => ({\r\n play,\r\n pause,\r\n stop,\r\n seek,\r\n setVolume,\r\n mute,\r\n unmute,\r\n toggleMute,\r\n enterFullscreen,\r\n exitFullscreen,\r\n toggleFullscreen,\r\n enterPip,\r\n exitPip,\r\n togglePip,\r\n setPlaybackRate,\r\n setQuality,\r\n getCurrentTime: () => videoRef.current?.currentTime || 0,\r\n getDuration: () => videoRef.current?.duration || 0,\r\n getVolume: () => videoRef.current?.volume || 0,\r\n isMuted: () => videoRef.current?.muted ?? true,\r\n isPlaying: () => !videoRef.current?.paused,\r\n isFullscreen: () => isFullscreen,\r\n isPip: () => isPipActive,\r\n getVideoElement: () => videoRef.current,\r\n }),\r\n [\r\n play,\r\n pause,\r\n stop,\r\n seek,\r\n setVolume,\r\n mute,\r\n unmute,\r\n toggleMute,\r\n enterFullscreen,\r\n exitFullscreen,\r\n toggleFullscreen,\r\n enterPip,\r\n exitPip,\r\n togglePip,\r\n setPlaybackRate,\r\n setQuality,\r\n isFullscreen,\r\n isPipActive,\r\n ]\r\n );\r\n\r\n // Get video source\r\n const videoSrc = sources[0]?.src || '';\r\n\r\n // CSS variables for accent color\r\n const cssVariables = {\r\n '--plex-accent-color': accentColor,\r\n } as React.CSSProperties;\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={`plex-video-player ${isFullscreen ? 'plex-video-player--fullscreen' : ''} ${className}`}\r\n style={{ ...cssVariables, width, ...style }}\r\n onMouseMove={handleMouseMove}\r\n onMouseLeave={() => isPlaying && setShowControls(false)}\r\n tabIndex={0}\r\n >\r\n {/* Video Element */}\r\n <video\r\n ref={videoRef}\r\n className=\"plex-video-player__video\"\r\n src={videoSrc}\r\n poster={poster}\r\n autoPlay={autoPlay}\r\n muted={muted}\r\n loop={loop}\r\n preload={preload}\r\n playsInline\r\n onClick={handleVideoClick}\r\n >\r\n {sources.slice(1).map((source, index) => (\r\n <source key={index} src={source.src} type={source.type} />\r\n ))}\r\n {textTracks.map((track, index) => (\r\n <track\r\n key={index}\r\n kind={track.kind}\r\n src={track.src}\r\n srcLang={track.srclang}\r\n label={track.label}\r\n default={track.default}\r\n />\r\n ))}\r\n </video>\r\n\r\n {/* Loading Indicator */}\r\n <Loader visible={isLoading && !error} />\r\n\r\n {/* Error Display */}\r\n {error && <ErrorDisplay error={error} onRetry={handleRetry} />}\r\n\r\n {/* Ad Overlay */}\r\n {isAdPlaying && currentAd && (\r\n <AdOverlay\r\n ad={currentAd}\r\n timeRemaining={adTimeRemaining}\r\n canSkip={canSkip}\r\n onSkip={skipAd}\r\n onClick={handleAdClick}\r\n />\r\n )}\r\n\r\n {/* Controls */}\r\n {controls && !isAdPlaying && (\r\n <div\r\n className={`plex-video-player__controls ${showControls ? 'plex-video-player__controls--visible' : ''}`}\r\n >\r\n {/* Progress Bar */}\r\n {progressBar && (\r\n <ProgressBar\r\n currentTime={currentTime}\r\n duration={duration}\r\n buffered={buffered}\r\n onSeek={seek}\r\n />\r\n )}\r\n\r\n {/* Bottom Controls */}\r\n <div className=\"plex-video-player__controls-bottom\">\r\n {/* Left Controls */}\r\n <div className=\"plex-video-player__controls-left\">\r\n {/* Play/Pause Button */}\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={togglePlay}\r\n aria-label={isPlaying ? 'Pause' : 'Play'}\r\n type=\"button\"\r\n >\r\n {isPlaying ? <PauseIcon /> : <PlayIcon />}\r\n </button>\r\n\r\n {/* Volume Control */}\r\n {volumeEnabled && (\r\n <VolumeControl\r\n volume={volumeState}\r\n muted={isMuted}\r\n onVolumeChange={setVolume}\r\n onToggleMute={toggleMute}\r\n />\r\n )}\r\n\r\n {/* Time Display */}\r\n {timeDisplay && (\r\n <div className=\"plex-video-player__time\">\r\n <span>{formatTime(currentTime)}</span>\r\n <span> / </span>\r\n <span>{formatTime(duration)}</span>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Right Controls */}\r\n <div className=\"plex-video-player__controls-right\">\r\n {/* Settings Menu */}\r\n {playbackSpeed && (\r\n <SettingsMenu\r\n playbackRate={playbackRate}\r\n playbackSpeeds={playbackSpeeds}\r\n onPlaybackRateChange={setPlaybackRate}\r\n qualityEnabled={qualitySelector && sources.length > 1}\r\n sources={sources}\r\n currentQuality={currentQuality}\r\n onQualityChange={setQuality}\r\n captionsEnabled={textTracks.length > 0}\r\n textTracks={textTracks}\r\n currentTrack={currentTrackLabel ?? undefined}\r\n onTrackChange={setTextTrack}\r\n />\r\n )}\r\n\r\n {/* PiP Button */}\r\n {pip && document.pictureInPictureEnabled && (\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={togglePip}\r\n aria-label={isPipActive ? 'Exit Picture in Picture' : 'Picture in Picture'}\r\n type=\"button\"\r\n >\r\n <PipIcon />\r\n </button>\r\n )}\r\n\r\n {/* Fullscreen Button */}\r\n {fullscreen && (\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={toggleFullscreen}\r\n aria-label={isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}\r\n type=\"button\"\r\n >\r\n {isFullscreen ? <FullscreenExitIcon /> : <FullscreenIcon />}\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Big Play Button */}\r\n {!isPlaying && !isLoading && !error && !isAdPlaying && (\r\n <button\r\n className=\"plex-video-player__big-play\"\r\n onClick={togglePlay}\r\n aria-label=\"Play\"\r\n type=\"button\"\r\n >\r\n <PlayIcon />\r\n </button>\r\n )}\r\n </div>\r\n );\r\n }\r\n);\r\n\r\nPlexVideoPlayer.displayName = 'PlexVideoPlayer';\r\n\r\nexport default PlexVideoPlayer;\r\n","// usePlayer Hook for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { useState, useRef, useCallback, useEffect } from 'react';\r\nimport {\r\n PlayerState,\r\n UsePlayerOptions,\r\n UsePlayerReturn,\r\n} from '../types';\r\nimport {\r\n requestFullscreen,\r\n exitFullscreen,\r\n getFullscreenElement,\r\n getBufferedEnd,\r\n isPipSupported,\r\n} from '../utils/helpers';\r\n\r\nconst initialState: PlayerState = {\r\n isPlaying: false,\r\n isPaused: true,\r\n isEnded: false,\r\n isBuffering: false,\r\n isSeeking: false,\r\n isFullscreen: false,\r\n isPip: false,\r\n isMuted: false,\r\n isAdPlaying: false,\r\n volume: 1,\r\n currentTime: 0,\r\n duration: 0,\r\n buffered: 0,\r\n playbackRate: 1,\r\n currentQuality: null,\r\n error: null,\r\n};\r\n\r\nexport const usePlayer = (options: UsePlayerOptions = {}): UsePlayerReturn => {\r\n const {\r\n autoPlay = false,\r\n muted = false,\r\n loop = false,\r\n volume: initialVolume = 1,\r\n playbackRate: initialRate = 1,\r\n } = options;\r\n\r\n const videoRef = useRef<HTMLVideoElement>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const [state, setState] = useState<PlayerState>({\r\n ...initialState,\r\n volume: initialVolume,\r\n isMuted: muted,\r\n playbackRate: initialRate,\r\n });\r\n\r\n // Update state helper\r\n const updateState = useCallback((updates: Partial<PlayerState>) => {\r\n setState((prev) => ({ ...prev, ...updates }));\r\n }, []);\r\n\r\n // Play\r\n const play = useCallback(async () => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n try {\r\n await video.play();\r\n updateState({ isPlaying: true, isPaused: false, isEnded: false });\r\n } catch (error) {\r\n console.error('Play failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Pause\r\n const pause = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n video.pause();\r\n updateState({ isPlaying: false, isPaused: true });\r\n }, [updateState]);\r\n\r\n // Toggle play/pause\r\n const togglePlay = useCallback(() => {\r\n if (state.isPlaying) {\r\n pause();\r\n } else {\r\n play();\r\n }\r\n }, [state.isPlaying, play, pause]);\r\n\r\n // Seek\r\n const seek = useCallback((time: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const clampedTime = Math.max(0, Math.min(time, video.duration || 0));\r\n video.currentTime = clampedTime;\r\n updateState({ currentTime: clampedTime });\r\n }, [updateState]);\r\n\r\n // Set volume\r\n const setVolume = useCallback((volume: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const clampedVolume = Math.max(0, Math.min(1, volume));\r\n video.volume = clampedVolume;\r\n video.muted = clampedVolume === 0;\r\n updateState({\r\n volume: clampedVolume,\r\n isMuted: clampedVolume === 0,\r\n });\r\n }, [updateState]);\r\n\r\n // Toggle mute\r\n const toggleMute = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n video.muted = !video.muted;\r\n updateState({ isMuted: video.muted });\r\n }, [updateState]);\r\n\r\n // Set playback rate\r\n const setPlaybackRate = useCallback((rate: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n video.playbackRate = rate;\r\n updateState({ playbackRate: rate });\r\n }, [updateState]);\r\n\r\n // Enter fullscreen\r\n const enterFullscreen = useCallback(async () => {\r\n const container = containerRef.current;\r\n if (!container) return;\r\n\r\n try {\r\n await requestFullscreen(container);\r\n updateState({ isFullscreen: true });\r\n } catch (error) {\r\n console.error('Fullscreen failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Exit fullscreen\r\n const exitFullscreenFn = useCallback(async () => {\r\n try {\r\n await exitFullscreen();\r\n updateState({ isFullscreen: false });\r\n } catch (error) {\r\n console.error('Exit fullscreen failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Toggle fullscreen\r\n const toggleFullscreen = useCallback(async () => {\r\n if (state.isFullscreen) {\r\n await exitFullscreenFn();\r\n } else {\r\n await enterFullscreen();\r\n }\r\n }, [state.isFullscreen, enterFullscreen, exitFullscreenFn]);\r\n\r\n // Enter Picture-in-Picture\r\n const enterPip = useCallback(async () => {\r\n const video = videoRef.current;\r\n if (!video || !isPipSupported()) return;\r\n\r\n try {\r\n await video.requestPictureInPicture();\r\n updateState({ isPip: true });\r\n } catch (error) {\r\n console.error('PiP failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Exit Picture-in-Picture\r\n const exitPipFn = useCallback(async () => {\r\n if (!document.pictureInPictureElement) return;\r\n\r\n try {\r\n await document.exitPictureInPicture();\r\n updateState({ isPip: false });\r\n } catch (error) {\r\n console.error('Exit PiP failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Toggle Picture-in-Picture\r\n const togglePip = useCallback(async () => {\r\n if (state.isPip) {\r\n await exitPipFn();\r\n } else {\r\n await enterPip();\r\n }\r\n }, [state.isPip, enterPip, exitPipFn]);\r\n\r\n // Video event handlers\r\n useEffect(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n // Set initial values\r\n video.muted = muted;\r\n video.volume = initialVolume;\r\n video.playbackRate = initialRate;\r\n video.loop = loop;\r\n\r\n const handlers = {\r\n loadedmetadata: () => {\r\n updateState({ duration: video.duration });\r\n },\r\n timeupdate: () => {\r\n updateState({\r\n currentTime: video.currentTime,\r\n buffered: getBufferedEnd(video),\r\n });\r\n },\r\n play: () => {\r\n updateState({ isPlaying: true, isPaused: false, isEnded: false });\r\n },\r\n pause: () => {\r\n updateState({ isPlaying: false, isPaused: true });\r\n },\r\n ended: () => {\r\n updateState({ isPlaying: false, isPaused: true, isEnded: true });\r\n },\r\n waiting: () => {\r\n updateState({ isBuffering: true });\r\n },\r\n canplay: () => {\r\n updateState({ isBuffering: false });\r\n },\r\n seeking: () => {\r\n updateState({ isSeeking: true });\r\n },\r\n seeked: () => {\r\n updateState({ isSeeking: false });\r\n },\r\n volumechange: () => {\r\n updateState({\r\n volume: video.volume,\r\n isMuted: video.muted,\r\n });\r\n },\r\n ratechange: () => {\r\n updateState({ playbackRate: video.playbackRate });\r\n },\r\n error: () => {\r\n updateState({ error: video.error });\r\n },\r\n enterpictureinpicture: () => {\r\n updateState({ isPip: true });\r\n },\r\n leavepictureinpicture: () => {\r\n updateState({ isPip: false });\r\n },\r\n };\r\n\r\n // Add event listeners\r\n Object.entries(handlers).forEach(([event, handler]) => {\r\n video.addEventListener(event, handler);\r\n });\r\n\r\n // Autoplay\r\n if (autoPlay) {\r\n play();\r\n }\r\n\r\n return () => {\r\n Object.entries(handlers).forEach(([event, handler]) => {\r\n video.removeEventListener(event, handler);\r\n });\r\n };\r\n }, [autoPlay, muted, loop, initialVolume, initialRate, play, updateState]);\r\n\r\n // Fullscreen change handler\r\n useEffect(() => {\r\n const handleFullscreenChange = () => {\r\n const isFs = !!getFullscreenElement();\r\n updateState({ isFullscreen: isFs });\r\n };\r\n\r\n document.addEventListener('fullscreenchange', handleFullscreenChange);\r\n document.addEventListener('webkitfullscreenchange', handleFullscreenChange);\r\n document.addEventListener('mozfullscreenchange', handleFullscreenChange);\r\n document.addEventListener('MSFullscreenChange', handleFullscreenChange);\r\n\r\n return () => {\r\n document.removeEventListener('fullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('webkitfullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('mozfullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('MSFullscreenChange', handleFullscreenChange);\r\n };\r\n }, [updateState]);\r\n\r\n return {\r\n state,\r\n videoRef,\r\n containerRef,\r\n play,\r\n pause,\r\n togglePlay,\r\n seek,\r\n setVolume,\r\n toggleMute,\r\n setPlaybackRate,\r\n enterFullscreen,\r\n exitFullscreen: exitFullscreenFn,\r\n toggleFullscreen,\r\n enterPip,\r\n exitPip: exitPipFn,\r\n togglePip,\r\n };\r\n};\r\n\r\nexport default usePlayer;\r\n"],"names":["formatTime","seconds","isFinite","isNaN","hrs","Math","floor","mins","secs","toString","padStart","percentage","value","total","min","max","clamp","throttle","func","limit","inThrottle","args","setTimeout","isPipSupported","document","pictureInPictureEnabled","getFullscreenElement","fullscreenElement","webkitFullscreenElement","mozFullScreenElement","msFullscreenElement","requestFullscreen","async","element","webkitRequestFullscreen","mozRequestFullScreen","msRequestFullscreen","exitFullscreen","webkitExitFullscreen","mozCancelFullScreen","msExitFullscreen","getBufferedEnd","video","buffered","length","currentTime","i","start","end","ProgressBar","duration","onSeek","thumbnailPreview","disabled","progressRef","useRef","isDragging","setIsDragging","useState","hoverTime","setHoverTime","hoverPosition","setHoverPosition","playedPercent","bufferedPercent","getTimeFromPosition","useCallback","clientX","current","rect","getBoundingClientRect","left","width","handleMouseMove","e","position","time","handleMouseDown","preventDefault","handleMouseUp","handleMouseEnter","handleMouseLeave","useEffect","mouseMoveHandler","window","addEventListener","removeEventListener","handleTouchStart","touch","touches","handleTouchMove","handleTouchEnd","_jsxs","ref","className","onMouseDown","onMouseEnter","onMouseLeave","onMouseMove","nativeEvent","onTouchStart","onTouchMove","onTouchEnd","role","tabIndex","children","_jsx","style","enabled","height","sprites","backgroundImage","backgroundPosition","calculateSpritePosition","interval","backgroundSize","padding","color","fontSize","_duration","index","PlayIcon","size","viewBox","fill","xmlns","d","PauseIcon","VolumeHighIcon","VolumeMediumIcon","VolumeLowIcon","VolumeMuteIcon","FullscreenIcon","FullscreenExitIcon","PipIcon","SettingsIcon","ErrorIcon","ExternalLinkIcon","CheckIcon","VolumeControl","volume","muted","onVolumeChange","onToggleMute","handleSliderChange","newVolume","parseFloat","target","onClick","title","type","step","onChange","background","SettingsMenu","playbackRate","playbackSpeeds","onPlaybackRateChange","qualityEnabled","sources","currentQuality","onQualityChange","captionsEnabled","textTracks","currentTrack","onTrackChange","isOpen","setIsOpen","currentView","setCurrentView","containerRef","handleClickOutside","contains","handleToggle","prev","handleSpeedSelect","speed","handleQualitySelect","quality","handleTrackSelect","track","_Fragment","cursor","border","textAlign","map","source","src","label","srclang","AdOverlay","ad","timeRemaining","canSkip","onSkip","skipText","ceil","clickThrough","stopPropagation","ERROR_MESSAGES","ErrorDisplay","error","onRetry","errorCode","code","message","Loader","visible","DEFAULT_HOTKEYS","play","mute","fullscreen","pip","seekForward","seekBackward","volumeUp","volumeDown","useKeyboard","hotkeys","onPlay","onMute","onFullscreen","onPip","onVolume","mergedHotkeys","handleKeyDown","event","tagName","isContentEditable","container","activeElement","key","shiftKey","Infinity","parseVastXml","xmlString","doc","DOMParser","parseFromString","querySelector","ads","querySelectorAll","forEach","adElement","inLine","id","getAttribute","textContent","description","undefined","creatives","skipOffset","clickTracking","mediaFiles","trackingEvents","creative","linear","durationStr","parseVastDuration","skipOffsetAttr","mediaFile","url","trim","push","parseInt","bitrate","videoClicks","ct","tracking","impressionUrls","impression","includes","parts","split","fetchVastAd","config","response","fetch","method","headers","Accept","ok","text","result","skipDelay","selectBestMediaFile","createElement","supportedFiles","filter","file","canPlayType","sort","a","b","resA","resB","test","navigator","userAgent","fireTrackingPixel","Image","fireTrackingPixels","urls","convertToAdInfo","useVast","vastConfig","videoRef","onAdStart","onAdEnd","onAdSkip","onAdError","isAdPlaying","setIsAdPlaying","currentAd","setCurrentAd","adTimeRemaining","setAdTimeRemaining","setCanSkip","currentVastAd","originalSrc","originalTime","playedPositions","Set","adIntervalRef","vastConfigs","Array","isArray","playAd","Error","currentSrc","setInterval","remaining","percent","firstQuartile","midpoint","thirdQuartile","handleAdEnd","complete","endAd","once","clearInterval","catch","skipAd","skip","handleAdClick","open","checkForAd","midrollTime","has","shouldPlay","add","PlexVideoPlayer","forwardRef","props","poster","autoPlay","loop","controls","playbackSpeed","volumeEnabled","initialVolume","progressBar","timeDisplay","qualitySelector","vast","keyboard","accentColor","controlsTimeout","doubleClickFullscreen","clickToPlay","preload","onPause","onEnded","onTimeUpdate","onProgress","onFullscreenChange","onPipChange","onError","onReady","controlsTimeoutRef","lastClickRef","isPlaying","setIsPlaying","setCurrentTime","setDuration","setBuffered","volumeState","setVolumeState","isMuted","setIsMuted","isFullscreen","setIsFullscreen","isPipActive","setIsPipActive","isLoading","setIsLoading","setError","showControls","setShowControls","setPlaybackRateState","setCurrentQuality","currentTrackLabel","setCurrentTrackLabel","useMemo","err","pause","stop","togglePlay","paused","seek","setVolume","unmute","toggleMute","enterFullscreen","toggleFullscreen","enterPip","requestPictureInPicture","exitPip","pictureInPictureElement","exitPictureInPicture","togglePip","setPlaybackRate","rate","setQuality","qualityLabel","find","s","wasPlaying","setTextTrack","trackLabel","mode","hideControls","showControlsTemporarily","clearTimeout","handleVideoClick","now","Date","handlePlay","handlePause","handleEnded","handleTimeUpdate","handleDurationChange","handleProgressEvent","handleVolumeChangeEvent","handleLoadedMetadata","handleWaiting","handlePlaying","handleCanPlay","handleErrorEvent","handleFullscreenChange","isFs","handleEnterPip","handleLeavePip","delta","handleRetry","load","useImperativeHandle","getCurrentTime","getDuration","getVolume","isPip","getVideoElement","videoSrc","cssVariables","playsInline","slice","kind","srcLang","default","displayName","initialState","isPaused","isEnded","isBuffering","isSeeking","wait","timeout","extension","pop","toLowerCase","mp4","webm","ogg","ogv","m3u8","mpd","mov","avi","mkv","random","substring","fullscreenEnabled","webkitFullscreenEnabled","mozFullScreenEnabled","msFullscreenEnabled","maxTouchPoints","Number","options","initialRate","state","setState","updateState","updates","clampedTime","clampedVolume","exitFullscreenFn","exitPipFn","handlers","loadedmetadata","timeupdate","ended","waiting","canplay","seeking","seeked","volumechange","ratechange","enterpictureinpicture","leavepictureinpicture","Object","entries","handler"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ :root{--plex-primary:#e50914;--plex-secondary:#fff;--plex-bg:rgba(0,0,0,.8);--plex-control-bg:rgba(0,0,0,.7);--plex-progress-bg:hsla(0,0%,100%,.3);--plex-buffered-bg:hsla(0,0%,100%,.5);--plex-hover:hsla(0,0%,100%,.1);--plex-shadow:0 2px 10px rgba(0,0,0,.3);--plex-transition:all 0.2s ease;--plex-font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,sans-serif}.plex-video-player{-webkit-tap-highlight-color:transparent;background-color:#000;font-family:var(--plex-font);height:100%;overflow:hidden;position:relative;user-select:none;width:100%}.plex-video-player *{box-sizing:border-box}.plex-video-player:focus{outline:none}.plex-video-player__video{display:block;height:100%;object-fit:contain;width:100%}.plex-video-player__poster{background-position:50%;background-repeat:no-repeat;background-size:cover;cursor:pointer;height:100%;left:0;position:absolute;top:0;width:100%;z-index:1}.plex-video-player__poster:after{background-color:var(--plex-primary);border-radius:50%;content:"";height:80px;left:50%;opacity:.9;position:absolute;top:50%;transform:translate(-50%,-50%);transition:var(--plex-transition);width:80px}.plex-video-player__poster:before{border-color:transparent transparent transparent #fff;border-style:solid;border-width:15px 0 15px 25px;content:"";left:50%;position:absolute;top:50%;transform:translate(-45%,-50%);z-index:2}.plex-video-player__poster:hover:after{opacity:1;transform:translate(-50%,-50%) scale(1.1)}.plex-video-player__controls{background:linear-gradient(transparent,rgba(0,0,0,.8));bottom:0;left:0;opacity:0;padding:10px 15px;position:absolute;right:0;transition:opacity .3s ease,visibility .3s ease;visibility:hidden;z-index:10}.plex-video-player--controls-visible .plex-video-player__controls,.plex-video-player:hover .plex-video-player__controls{opacity:1;visibility:visible}.plex-video-player--ad-playing .plex-video-player__controls{pointer-events:none}.plex-video-player__progress-container{cursor:pointer;height:20px;padding:8px 0;position:relative;width:100%}.plex-video-player__progress{background-color:var(--plex-progress-bg);border-radius:2px;height:4px;overflow:hidden;position:relative;transition:height .1s ease;width:100%}.plex-video-player__progress-container:hover .plex-video-player__progress{height:6px}.plex-video-player__progress-buffered{background-color:var(--plex-buffered-bg);border-radius:2px;height:100%;left:0;position:absolute;top:0;transition:width .1s ease}.plex-video-player__progress-played{background-color:var(--plex-primary);border-radius:2px;height:100%;left:0;position:absolute;top:0;transition:width .05s linear}.plex-video-player__progress-handle{background-color:var(--plex-primary);border-radius:50%;box-shadow:var(--plex-shadow);height:14px;position:absolute;top:50%;transform:translate(-50%,-50%) scale(0);transition:transform .1s ease;width:14px}.plex-video-player__progress-container:hover .plex-video-player__progress-handle{transform:translate(-50%,-50%) scale(1)}.plex-video-player__thumbnail-preview{background-color:#000;border:2px solid var(--plex-secondary);border-radius:4px;bottom:30px;opacity:0;overflow:hidden;pointer-events:none;position:absolute;transform:translateX(-50%);transition:opacity .2s ease}.plex-video-player__progress-container:hover .plex-video-player__thumbnail-preview{opacity:1}.plex-video-player__thumbnail-time{background-color:rgba(0,0,0,.7);bottom:0;color:#fff;font-size:12px;left:0;padding:4px;position:absolute;right:0;text-align:center}.plex-video-player__controls-row{align-items:center;display:flex;gap:10px;justify-content:space-between;margin-top:8px}.plex-video-player__controls-left,.plex-video-player__controls-right{align-items:center;display:flex;gap:8px}.plex-video-player__btn{align-items:center;background:transparent;border:none;border-radius:4px;color:var(--plex-secondary);cursor:pointer;display:flex;height:36px;justify-content:center;padding:0;transition:var(--plex-transition);width:36px}.plex-video-player__btn:hover{background-color:var(--plex-hover)}.plex-video-player__btn:focus{outline:none}.plex-video-player__btn:focus-visible{box-shadow:0 0 0 2px var(--plex-primary)}.plex-video-player__btn svg{fill:currentColor;height:22px;width:22px}.plex-video-player__btn--play{height:40px;width:40px}.plex-video-player__btn--play svg{height:26px;width:26px}.plex-video-player__volume-container{align-items:center;display:flex;gap:6px}.plex-video-player__volume-slider-container{overflow:hidden;transition:width .2s ease;width:0}.plex-video-player__volume-container:hover .plex-video-player__volume-slider-container{width:80px}.plex-video-player__volume-slider{-webkit-appearance:none;appearance:none;background:var(--plex-progress-bg);border-radius:2px;cursor:pointer;height:4px;width:80px}.plex-video-player__volume-slider::-webkit-slider-thumb{-webkit-appearance:none;background:var(--plex-secondary);border-radius:50%;cursor:pointer;height:12px;width:12px}.plex-video-player__volume-slider::-moz-range-thumb{background:var(--plex-secondary);border:none;border-radius:50%;cursor:pointer;height:12px;width:12px}.plex-video-player__time{color:var(--plex-secondary);font-size:13px;font-variant-numeric:tabular-nums;white-space:nowrap}.plex-video-player__time-separator{margin:0 4px;opacity:.7}.plex-video-player__settings-container{position:relative}.plex-video-player__settings-menu{backdrop-filter:blur(10px);background-color:var(--plex-control-bg);border-radius:8px;bottom:100%;box-shadow:var(--plex-shadow);margin-bottom:10px;min-width:180px;opacity:0;overflow:hidden;position:absolute;right:0;transform:translateY(10px);transition:all .2s ease;visibility:hidden}.plex-video-player__settings-container:hover .plex-video-player__settings-menu,.plex-video-player__settings-menu--open{opacity:1;transform:translateY(0);visibility:visible}.plex-video-player__settings-title{border-bottom:1px solid hsla(0,0%,100%,.1);color:var(--plex-secondary);font-size:13px;font-weight:600;padding:12px 15px}.plex-video-player__settings-item{align-items:center;background:transparent;border:none;color:var(--plex-secondary);cursor:pointer;display:flex;font-size:13px;justify-content:space-between;padding:10px 15px;text-align:left;transition:var(--plex-transition);width:100%}.plex-video-player__settings-item:hover{background-color:var(--plex-hover)}.plex-video-player__settings-item--active{color:var(--plex-primary)}.plex-video-player__settings-item--active:after{content:"✓";margin-left:8px}.plex-video-player__speed-menu{display:grid;gap:4px;grid-template-columns:repeat(2,1fr);padding:8px}.plex-video-player__speed-btn{background:transparent;border:1px solid hsla(0,0%,100%,.2);border-radius:4px;color:var(--plex-secondary);cursor:pointer;font-size:12px;padding:8px 12px;transition:var(--plex-transition)}.plex-video-player__speed-btn:hover{background-color:var(--plex-hover);border-color:hsla(0,0%,100%,.4)}.plex-video-player__speed-btn--active{background-color:var(--plex-primary);border-color:var(--plex-primary)}.plex-video-player__quality-menu{padding:8px}.plex-video-player__loader{height:50px;left:50%;pointer-events:none;position:absolute;top:50%;transform:translate(-50%,-50%);width:50px;z-index:5}.plex-video-player__loader-spinner{animation:plex-spin 1s linear infinite;border:3px solid transparent;border-radius:50%;border-top:3px solid var(--plex-primary);height:100%;width:100%}@keyframes plex-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.plex-video-player__big-play{background-color:var(--plex-primary);border:none;border-radius:50%;cursor:pointer;height:80px;left:50%;opacity:.9;position:absolute;top:50%;transform:translate(-50%,-50%);transition:var(--plex-transition);width:80px;z-index:5}.plex-video-player__big-play:hover{opacity:1;transform:translate(-50%,-50%) scale(1.1)}.plex-video-player__big-play svg{fill:#fff;height:30px;left:55%;position:absolute;top:50%;transform:translate(-50%,-50%);width:30px}.plex-video-player__ad-overlay{bottom:0;cursor:pointer;left:0;position:absolute;right:0;top:0;z-index:20}.plex-video-player__ad-info{align-items:center;background-color:rgba(0,0,0,.7);border-radius:4px;color:#fff;display:flex;font-size:13px;gap:10px;left:15px;padding:8px 12px;position:absolute;top:15px}.plex-video-player__ad-badge{background-color:#f5c518;border-radius:2px;color:#000;font-size:11px;font-weight:600;padding:2px 6px;text-transform:uppercase}.plex-video-player__ad-skip{background-color:rgba(0,0,0,.8);border:1px solid #fff;border-radius:4px;bottom:80px;color:#fff;cursor:pointer;font-size:14px;padding:10px 20px;position:absolute;right:15px;transition:var(--plex-transition)}.plex-video-player__ad-skip:hover{background-color:#fff;color:#000}.plex-video-player__ad-skip:disabled{cursor:not-allowed;opacity:.5}.plex-video-player__ad-skip:disabled:hover{background-color:rgba(0,0,0,.8);color:#fff}.plex-video-player__ad-learn-more{align-items:center;background-color:rgba(0,0,0,.7);border:1px solid hsla(0,0%,100%,.5);border-radius:4px;bottom:80px;color:#fff;cursor:pointer;display:flex;font-size:13px;gap:6px;left:15px;padding:8px 14px;position:absolute;transition:var(--plex-transition)}.plex-video-player__ad-learn-more:hover{background-color:hsla(0,0%,100%,.2)}.plex-video-player__error{align-items:center;background-color:rgba(0,0,0,.9);bottom:0;color:#fff;display:flex;flex-direction:column;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:25}.plex-video-player__error-icon{fill:var(--plex-primary);height:60px;margin-bottom:20px;width:60px}.plex-video-player__error-message{font-size:16px;margin-bottom:10px}.plex-video-player__error-code{font-size:12px;opacity:.7}.plex-video-player__error-retry{background-color:var(--plex-primary);border:none;border-radius:4px;color:#fff;cursor:pointer;font-size:14px;margin-top:20px;padding:10px 25px;transition:var(--plex-transition)}.plex-video-player__error-retry:hover{opacity:.9}.plex-video-player__captions{background-color:rgba(0,0,0,.75);border-radius:4px;bottom:60px;color:#fff;font-size:18px;left:50%;line-height:1.4;max-width:80%;padding:8px 16px;position:absolute;text-align:center;transform:translateX(-50%);z-index:8}.plex-video-player--fullscreen{height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:9999}.plex-video-player--fullscreen .plex-video-player__captions{font-size:24px}.plex-video-player--theme-light .plex-video-player__controls{background:linear-gradient(transparent,hsla(0,0%,100%,.9))}.plex-video-player--theme-light .plex-video-player__btn,.plex-video-player--theme-light .plex-video-player__time{color:#333}.plex-video-player--theme-light .plex-video-player__settings-menu{background-color:hsla(0,0%,100%,.95)}.plex-video-player--theme-light .plex-video-player__settings-item,.plex-video-player--theme-light .plex-video-player__settings-title{color:#333}@media (max-width:768px){.plex-video-player__controls{padding:8px 10px}.plex-video-player__btn{height:32px;width:32px}.plex-video-player__btn svg{height:18px;width:18px}.plex-video-player__time{font-size:11px}.plex-video-player__volume-container:hover .plex-video-player__volume-slider-container{width:60px}.plex-video-player__big-play{height:60px;width:60px}.plex-video-player__big-play svg{height:24px;width:24px}.plex-video-player__captions{bottom:50px;font-size:14px}}@media (max-width:480px){.plex-video-player__controls-row{gap:5px}.plex-video-player__controls-left,.plex-video-player__controls-right{gap:4px}.plex-video-player__btn{height:28px;width:28px}.plex-video-player__volume-slider-container{display:none}}.plex-fade-in{animation:plex-fade-in .3s ease}@keyframes plex-fade-in{0%{opacity:0}to{opacity:1}}.plex-scale-in{animation:plex-scale-in .2s ease}@keyframes plex-scale-in{0%{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}
package/package.json CHANGED
@@ -1,134 +1,97 @@
1
1
  {
2
2
  "name": "@frameset/plex-player",
3
- "version": "1.0.6",
3
+ "version": "2.0.1",
4
+ "description": "Ultra-performant React video player with VAST ads support, Picture-in-Picture, and advanced controls",
4
5
  "type": "module",
5
- "description": "Professional video player with VAST ads, Chromecast, PiP, subtitles, playlists and more. Built by FRAMESET Studio.",
6
- "main": "dist/plex-player.cjs.js",
7
- "module": "dist/plex-player.esm.js",
8
- "browser": "dist/plex-player.min.js",
9
- "types": "dist/plex-player.d.ts",
10
- "exports": {
11
- ".": {
12
- "import": "./dist/plex-player.esm.js",
13
- "require": "./dist/plex-player.cjs.js",
14
- "types": "./dist/plex-player.d.ts"
15
- },
16
- "./react": {
17
- "import": "./dist/react/index.esm.js",
18
- "require": "./dist/react/index.js",
19
- "types": "./src/types/index.d.ts"
20
- },
21
- "./vue": {
22
- "import": "./dist/vue/index.esm.js",
23
- "require": "./dist/vue/index.js",
24
- "types": "./src/types/index.d.ts"
25
- },
26
- "./css": "./dist/plex-player.css",
27
- "./dist/*": "./dist/*"
28
- },
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.esm.js",
8
+ "types": "dist/index.d.ts",
29
9
  "files": [
30
10
  "dist",
31
- "src",
32
11
  "README.md",
33
12
  "LICENSE"
34
13
  ],
14
+ "exports": {
15
+ ".": {
16
+ "import": "./dist/index.esm.js",
17
+ "require": "./dist/index.js",
18
+ "types": "./dist/index.d.ts"
19
+ },
20
+ "./styles.css": "./dist/styles.css"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
35
25
  "scripts": {
36
26
  "build": "rollup -c",
37
- "build:watch": "rollup -c -w",
38
- "dev": "vite",
39
- "preview": "vite preview",
40
- "test": "vitest",
41
- "lint": "eslint src/",
42
- "prepare": "npm run build",
43
- "prepublishOnly": "npm run build"
27
+ "build:types": "tsc --emitDeclarationOnly",
28
+ "dev": "rollup -c -w",
29
+ "lint": "eslint src --ext .ts,.tsx",
30
+ "lint:fix": "eslint src --ext .ts,.tsx --fix",
31
+ "test": "jest",
32
+ "test:coverage": "jest --coverage",
33
+ "prepublishOnly": "npm run build",
34
+ "clean": "rimraf dist",
35
+ "typecheck": "tsc --noEmit"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/deadseti/plexvideo-player.git"
44
40
  },
45
41
  "keywords": [
42
+ "react",
46
43
  "video",
47
44
  "player",
48
45
  "video-player",
49
- "html5-video",
50
- "chromecast",
51
- "cast",
46
+ "react-video",
52
47
  "vast",
53
48
  "ads",
54
- "advertising",
55
- "pip",
56
49
  "picture-in-picture",
57
- "subtitles",
58
- "vtt",
59
- "srt",
60
- "ass",
61
- "playlist",
62
- "react",
63
- "vue",
64
- "typescript",
65
- "responsive",
66
- "touch",
67
- "mobile",
50
+ "pip",
51
+ "hls",
68
52
  "streaming",
69
53
  "media",
54
+ "plexvideo",
70
55
  "frameset"
71
56
  ],
72
- "author": {
73
- "name": "FRAMESET Studio",
74
- "email": "hello@frameset.dev",
75
- "url": "https://frameset.dev"
76
- },
57
+ "author": "FRAMESET STUDIO",
77
58
  "license": "MIT",
78
- "homepage": "https://frameset.dev/plex-player",
79
- "repository": {
80
- "type": "git",
81
- "url": "git+https://github.com/deadseti/plex-player.git"
82
- },
83
59
  "bugs": {
84
- "url": "https://github.com/deadseti/plex-player/issues"
85
- },
86
- "funding": {
87
- "type": "github",
88
- "url": "https://github.com/sponsors/deadseti"
89
- },
90
- "engines": {
91
- "node": ">=16.0.0"
60
+ "url": "https://github.com/deadseti/plexvideo-player/issues"
92
61
  },
62
+ "homepage": "https://github.com/deadseti/plexvideo-player#readme",
93
63
  "peerDependencies": {
94
- "react": ">=16.8.0",
95
- "react-dom": ">=16.8.0",
96
- "vue": ">=3.0.0"
97
- },
98
- "peerDependenciesMeta": {
99
- "react": {
100
- "optional": true
101
- },
102
- "react-dom": {
103
- "optional": true
104
- },
105
- "vue": {
106
- "optional": true
107
- }
64
+ "react": ">=17.0.0",
65
+ "react-dom": ">=17.0.0"
108
66
  },
109
67
  "devDependencies": {
110
- "@babel/core": "^7.24.0",
111
- "@babel/preset-env": "^7.24.0",
112
- "@babel/preset-react": "^7.24.0",
113
- "@rollup/plugin-alias": "^6.0.0",
114
- "@rollup/plugin-babel": "^6.0.0",
115
- "@rollup/plugin-commonjs": "^25.0.0",
116
- "@rollup/plugin-node-resolve": "^15.0.0",
117
- "@rollup/plugin-replace": "^6.0.3",
118
- "@rollup/plugin-terser": "^0.4.0",
119
- "@types/node": "^20.0.0",
120
- "@types/react": "^18.0.0",
121
- "@types/react-dom": "^18.0.0",
122
- "eslint": "^8.0.0",
123
- "postcss": "^8.0.0",
124
- "rollup": "^4.0.0",
125
- "rollup-plugin-copy": "^3.5.0",
126
- "rollup-plugin-postcss": "^4.0.0",
127
- "typescript": "^5.0.0",
128
- "vite": "^5.0.0",
129
- "vitest": "^1.0.0"
68
+ "@rollup/plugin-commonjs": "^25.0.7",
69
+ "@rollup/plugin-node-resolve": "^15.2.3",
70
+ "@rollup/plugin-terser": "^0.4.4",
71
+ "@rollup/plugin-typescript": "^11.1.6",
72
+ "@testing-library/jest-dom": "^6.4.2",
73
+ "@testing-library/react": "^14.2.1",
74
+ "@types/jest": "^29.5.12",
75
+ "@types/react": "^18.2.55",
76
+ "@types/react-dom": "^18.2.19",
77
+ "@typescript-eslint/eslint-plugin": "^7.0.1",
78
+ "@typescript-eslint/parser": "^7.0.1",
79
+ "eslint": "^8.56.0",
80
+ "eslint-plugin-react": "^7.33.2",
81
+ "eslint-plugin-react-hooks": "^4.6.0",
82
+ "jest": "^29.7.0",
83
+ "jest-environment-jsdom": "^29.7.0",
84
+ "react": "^18.2.0",
85
+ "react-dom": "^18.2.0",
86
+ "rimraf": "^5.0.5",
87
+ "rollup": "^4.12.0",
88
+ "rollup-plugin-dts": "^6.1.0",
89
+ "rollup-plugin-postcss": "^4.0.2",
90
+ "ts-jest": "^29.1.2",
91
+ "tslib": "^2.8.1",
92
+ "typescript": "^5.3.3"
130
93
  },
131
- "sideEffects": [
132
- "**/*.css"
133
- ]
94
+ "engines": {
95
+ "node": ">=16.0.0"
96
+ }
134
97
  }