@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.
- package/LICENSE +1 -1
- package/README.md +310 -505
- package/dist/index.d.ts +438 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +1 -0
- package/package.json +67 -104
- package/dist/plex-player.cjs.js +0 -1027
- package/dist/plex-player.cjs.js.map +0 -1
- package/dist/plex-player.css +0 -1651
- package/dist/plex-player.css.map +0 -1
- package/dist/plex-player.d.ts +0 -457
- package/dist/plex-player.esm.js +0 -1021
- package/dist/plex-player.esm.js.map +0 -1
- package/dist/plex-player.js +0 -1033
- package/dist/plex-player.js.map +0 -1
- package/dist/plex-player.min.js +0 -9
- package/dist/plex-player.min.js.map +0 -1
- package/dist/react/index.esm.js +0 -257
- package/dist/react/index.esm.js.map +0 -1
- package/dist/react/index.js +0 -265
- package/dist/react/index.js.map +0 -1
- package/dist/styles.tmp.js +0 -1
- package/dist/vue/index.esm.js +0 -283
- package/dist/vue/index.esm.js.map +0 -1
- package/dist/vue/index.js +0 -291
- package/dist/vue/index.js.map +0 -1
- package/src/core/index.js +0 -1082
- package/src/core/player-core.js +0 -225
- package/src/react/index.jsx +0 -277
- package/src/styles.js +0 -8
- package/src/types/index.d.ts +0 -457
- package/src/vue/index.js +0 -304
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.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","parseTime","time","parts","split","map","Number","length","percentage","value","total","min","max","clamp","throttle","func","limit","inThrottle","args","setTimeout","debounce","wait","timeout","clearTimeout","isFullscreenSupported","document","fullscreenEnabled","webkitFullscreenEnabled","mozFullScreenEnabled","msFullscreenEnabled","isPipSupported","pictureInPictureEnabled","getFullscreenElement","fullscreenElement","webkitFullscreenElement","mozFullScreenElement","msFullscreenElement","requestFullscreen","async","element","webkitRequestFullscreen","mozRequestFullScreen","msRequestFullscreen","exitFullscreen","webkitExitFullscreen","mozCancelFullScreen","msExitFullscreen","detectVideoType","url","extension","pop","toLowerCase","mp4","webm","ogg","ogv","m3u8","mpd","mov","avi","mkv","isHlsNativelySupported","createElement","canPlayType","generateId","random","substring","isMobile","test","navigator","userAgent","isTouchDevice","window","maxTouchPoints","getBufferedEnd","video","buffered","currentTime","i","start","end","type","result","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","handleMouseDown","preventDefault","handleMouseUp","handleMouseEnter","handleMouseLeave","useEffect","mouseMoveHandler","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","PipExitIcon","SettingsIcon","CaptionsIcon","SpeedIcon","QualityIcon","ForwardIcon","RewindIcon","SkipNextIcon","SkipPrevIcon","ErrorIcon","ExternalLinkIcon","CheckIcon","VolumeControl","volume","muted","onVolumeChange","onToggleMute","handleSliderChange","newVolume","parseFloat","target","onClick","title","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","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","trim","push","parseInt","bitrate","videoClicks","ct","tracking","impressionUrls","impression","includes","fetchVastAd","config","response","fetch","method","headers","Accept","ok","text","skipDelay","selectBestMediaFile","supportedFiles","filter","file","sort","a","b","resA","resB","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","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","playsInline","slice","kind","srcLang","default","displayName","initialState","isPaused","isEnded","isBuffering","isSeeking","usePlayer","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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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react/jsx-runtime"),t=require("react");const r=e=>{if(!isFinite(e)||isNaN(e))return"0:00";const t=Math.floor(e/3600),r=Math.floor(e%3600/60),n=Math.floor(e%60);return t>0?`${t}:${r.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`:`${r}:${n.toString().padStart(2,"0")}`},n=(e,t)=>0===t?0:Math.min(100,Math.max(0,e/t*100)),s=(e,t,r)=>Math.min(r,Math.max(t,e)),l=(e,t)=>{let r=!1;return(...n)=>{r||(e(...n),r=!0,setTimeout(()=>r=!1,t))}},a=()=>"pictureInPictureEnabled"in document&&document.pictureInPictureEnabled,i=()=>document.fullscreenElement||document.webkitFullscreenElement||document.mozFullScreenElement||document.msFullscreenElement||null,c=async e=>{e.requestFullscreen?await e.requestFullscreen():e.webkitRequestFullscreen?await e.webkitRequestFullscreen():e.mozRequestFullScreen?await e.mozRequestFullScreen():e.msRequestFullscreen&&await e.msRequestFullscreen()},o=async()=>{document.exitFullscreen?await document.exitFullscreen():document.webkitExitFullscreen?await document.webkitExitFullscreen():document.mozCancelFullScreen?await document.mozCancelFullScreen():document.msExitFullscreen&&await document.msExitFullscreen()},u=e=>{if(0===e.buffered.length)return 0;const t=e.currentTime;for(let r=0;r<e.buffered.length;r++)if(e.buffered.start(r)<=t&&e.buffered.end(r)>=t)return e.buffered.end(r);return e.buffered.end(e.buffered.length-1)},d=({currentTime:l,duration:a,buffered:i,onSeek:c,thumbnailPreview:o,disabled:u=!1})=>{const d=t.useRef(null),[m,h]=t.useState(!1),[v,x]=t.useState(null),[g,y]=t.useState(0),f=n(l,a),b=n(i,a),k=t.useCallback(e=>{if(!d.current)return 0;const t=d.current.getBoundingClientRect();return s((e-t.left)/t.width,0,1)*a},[a]),w=t.useCallback(e=>{if(!d.current)return;const t=d.current.getBoundingClientRect(),r="clientX"in e?e.clientX:0,n=s(r-t.left,0,t.width),l=k(r);y(n),x(l),m&&c(l)},[m,k,c]),C=t.useCallback(e=>{if(u)return;e.preventDefault(),h(!0);const t=k(e.clientX);c(t)},[u,k,c]),E=t.useCallback(()=>{h(!1)},[]),j=t.useCallback(e=>{const t=k(e.clientX);x(t)},[k]),_=t.useCallback(()=>{x(null)},[]);t.useEffect(()=>{if(m){const e=e=>w(e);return window.addEventListener("mousemove",e),window.addEventListener("mouseup",E),()=>{window.removeEventListener("mousemove",e),window.removeEventListener("mouseup",E)}}},[m,w,E]);const N=t.useCallback(e=>{if(u)return;e.preventDefault();const t=e.touches[0],r=k(t.clientX);h(!0),c(r)},[u,k,c]),P=t.useCallback(e=>{if(!m)return;const t=e.touches[0],r=k(t.clientX);c(r)},[m,k,c]),S=t.useCallback(()=>{h(!1)},[]);return e.jsxs("div",{ref:d,className:"plex-video-player__progress-container",onMouseDown:C,onMouseEnter:j,onMouseLeave:_,onMouseMove:e=>w(e.nativeEvent),onTouchStart:N,onTouchMove:P,onTouchEnd:S,role:"slider","aria-label":"Video progress","aria-valuemin":0,"aria-valuemax":a,"aria-valuenow":l,"aria-valuetext":r(l),tabIndex:0,children:[e.jsxs("div",{className:"plex-video-player__progress",children:[e.jsx("div",{className:"plex-video-player__progress-buffered",style:{width:`${b}%`}}),e.jsx("div",{className:"plex-video-player__progress-played",style:{width:`${f}%`}}),e.jsx("div",{className:"plex-video-player__progress-handle",style:{left:`${f}%`}})]}),o?.enabled&&null!==v&&e.jsxs("div",{className:"plex-video-player__thumbnail-preview",style:{left:`${g}px`,width:o.width||160,height:o.height||90},children:[o.sprites&&e.jsx("div",{style:{width:"100%",height:"100%",backgroundImage:`url(${o.sprites})`,backgroundPosition:p(v,a,o.interval||10,o.width||160,o.height||90),backgroundSize:"cover"}}),e.jsx("div",{className:"plex-video-player__thumbnail-time",children:r(v)})]}),null!==v&&!o?.enabled&&e.jsx("div",{className:"plex-video-player__thumbnail-preview",style:{left:`${g}px`,width:"auto",height:"auto",padding:"4px 8px"},children:e.jsx("span",{style:{color:"white",fontSize:"12px"},children:r(v)})})]})},p=(e,t,r,n,s)=>{const l=Math.floor(e/r);return`-${l%10*n}px -${Math.floor(l/10)*s}px`},m=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("path",{d:"M8 5v14l11-7z"})}),h=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("path",{d:"M6 19h4V5H6v14zm8-14v14h4V5h-4z"})}),v=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("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"})}),x=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("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"})}),g=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("path",{d:"M7 9v6h4l5 5V4l-5 5H7z"})}),y=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("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"})}),f=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("path",{d:"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"})}),b=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("path",{d:"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"})}),k=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("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"})}),w=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("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"})}),C=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("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"})}),E=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("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"})}),j=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("path",{d:"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"})}),_=({volume:r,muted:n,onVolumeChange:s,onToggleMute:l})=>{const a=t.useCallback(e=>{const t=parseFloat(e.target.value);s(t)},[s]);return e.jsxs("div",{className:"plex-video-player__volume-container",children:[e.jsx("button",{className:"plex-video-player__btn",onClick:l,"aria-label":n?"Unmute":"Mute",title:n?"Unmute (M)":"Mute (M)",type:"button",children:n||0===r?e.jsx(y,{}):r<.33?e.jsx(g,{}):r<.66?e.jsx(x,{}):e.jsx(v,{})}),e.jsx("div",{className:"plex-video-player__volume-slider-container",children:e.jsx("input",{type:"range",className:"plex-video-player__volume-slider",min:0,max:1,step:.01,value:n?0:r,onChange:a,"aria-label":"Volume",style:{background:`linear-gradient(to right, var(--plex-primary) 0%, var(--plex-primary) ${100*(n?0:r)}%, var(--plex-progress-bg) ${100*(n?0:r)}%, var(--plex-progress-bg) 100%)`}})})]})},N=({playbackRate:r,playbackSpeeds:n,onPlaybackRateChange:s,qualityEnabled:l,sources:a,currentQuality:i,onQualityChange:c,captionsEnabled:o,textTracks:u,currentTrack:d,onTrackChange:p})=>{const[m,h]=t.useState(!1),[v,x]=t.useState("main"),g=t.useRef(null);t.useEffect(()=>{const e=e=>{g.current&&!g.current.contains(e.target)&&(h(!1),x("main"))};if(m)return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[m]);const y=t.useCallback(()=>{h(e=>!e),x("main")},[]),f=t.useCallback(e=>{s(e),x("main")},[s]),b=t.useCallback(e=>{c?.(e),x("main")},[c]),k=t.useCallback(e=>{p?.(e),x("main")},[p]);return e.jsxs("div",{className:"plex-video-player__settings-container",ref:g,children:[e.jsx("button",{className:"plex-video-player__btn",onClick:y,"aria-label":"Settings","aria-expanded":m,title:"Settings",type:"button",children:e.jsx(w,{})}),m&&e.jsxs("div",{className:"plex-video-player__settings-menu "+(m?"plex-video-player__settings-menu--open":""),children:["main"===v&&e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"plex-video-player__settings-title",children:"Settings"}),e.jsxs("button",{className:"plex-video-player__settings-item",onClick:()=>x("speed"),type:"button",children:[e.jsx("span",{children:"Playback Speed"}),e.jsx("span",{children:1===r?"Normal":`${r}x`})]}),l&&a&&a.length>1&&e.jsxs("button",{className:"plex-video-player__settings-item",onClick:()=>x("quality"),type:"button",children:[e.jsx("span",{children:"Quality"}),e.jsx("span",{children:i||"Auto"})]}),o&&u&&u.length>0&&e.jsxs("button",{className:"plex-video-player__settings-item",onClick:()=>x("captions"),type:"button",children:[e.jsx("span",{children:"Captions"}),e.jsx("span",{children:d||"Off"})]})]}),"speed"===v&&e.jsxs(e.Fragment,{children:[e.jsx("button",{className:"plex-video-player__settings-title",onClick:()=>x("main"),style:{cursor:"pointer",border:"none",background:"transparent",width:"100%",textAlign:"left"},type:"button",children:"← Playback Speed"}),e.jsx("div",{className:"plex-video-player__speed-menu",children:n.map(t=>e.jsx("button",{className:"plex-video-player__speed-btn "+(r===t?"plex-video-player__speed-btn--active":""),onClick:()=>f(t),type:"button",children:1===t?"Normal":`${t}x`},t))})]}),"quality"===v&&e.jsxs(e.Fragment,{children:[e.jsx("button",{className:"plex-video-player__settings-title",onClick:()=>x("main"),style:{cursor:"pointer",border:"none",background:"transparent",width:"100%",textAlign:"left"},type:"button",children:"← Quality"}),e.jsx("div",{className:"plex-video-player__quality-menu",children:a?.map(t=>e.jsxs("button",{className:"plex-video-player__settings-item "+(i===t.quality?"plex-video-player__settings-item--active":""),onClick:()=>b(t.quality||t.src),type:"button",children:[e.jsx("span",{children:t.label||t.quality||"Unknown"}),i===t.quality&&e.jsx(j,{size:16})]},t.quality||t.src))})]}),"captions"===v&&e.jsxs(e.Fragment,{children:[e.jsx("button",{className:"plex-video-player__settings-title",onClick:()=>x("main"),style:{cursor:"pointer",border:"none",background:"transparent",width:"100%",textAlign:"left"},type:"button",children:"← Captions"}),e.jsxs("div",{className:"plex-video-player__quality-menu",children:[e.jsxs("button",{className:"plex-video-player__settings-item "+(d?"":"plex-video-player__settings-item--active"),onClick:()=>k(null),type:"button",children:[e.jsx("span",{children:"Off"}),!d&&e.jsx(j,{size:16})]}),u?.map(t=>e.jsxs("button",{className:"plex-video-player__settings-item "+(d===t.srclang?"plex-video-player__settings-item--active":""),onClick:()=>k(t.srclang),type:"button",children:[e.jsx("span",{children:t.label}),d===t.srclang&&e.jsx(j,{size:16})]},t.srclang))]})]})]})]})},P=({ad:t,timeRemaining:n,canSkip:s,onSkip:l,onClick:a})=>{const i=s?"Skip Ad":`Skip in ${Math.ceil(n)}s`;return e.jsxs("div",{className:"plex-video-player__ad-overlay",onClick:a,children:[e.jsxs("div",{className:"plex-video-player__ad-info",children:[e.jsx("span",{className:"plex-video-player__ad-badge",children:"Ad"}),e.jsxs("span",{children:[r(n)," remaining"]})]}),t.clickThrough&&e.jsxs("button",{className:"plex-video-player__ad-learn-more",onClick:e=>{e.stopPropagation(),a()},type:"button",children:[e.jsx(E,{size:16}),"Learn More"]}),e.jsx("button",{className:"plex-video-player__ad-skip",onClick:e=>{e.stopPropagation(),s&&l()},disabled:!s,type:"button",children:i})]})},S={1:"The video playback was aborted.",2:"A network error occurred while loading the video.",3:"The video format is not supported or cannot be decoded.",4:"The video source is not supported."},M=({error:t,onRetry:r})=>{const n=t?.code||0,s=S[n]||"An unknown error occurred.";return e.jsxs("div",{className:"plex-video-player__error",children:[e.jsx(C,{className:"plex-video-player__error-icon",size:60}),e.jsx("div",{className:"plex-video-player__error-message",children:s}),n>0&&e.jsxs("div",{className:"plex-video-player__error-code",children:["Error Code: ",n]}),e.jsx("button",{className:"plex-video-player__error-retry",onClick:r,type:"button",children:"Try Again"})]})},z=({visible:t})=>t?e.jsx("div",{className:"plex-video-player__loader",children:e.jsx("div",{className:"plex-video-player__loader-spinner"})}):null,T={play:"Space",mute:"m",fullscreen:"f",pip:"p",seekForward:"ArrowRight",seekBackward:"ArrowLeft",volumeUp:"ArrowUp",volumeDown:"ArrowDown"},L=({enabled:e,hotkeys:r={},onPlay:n,onMute:s,onFullscreen:l,onPip:a,onSeek:i,onVolume:c,containerRef:o})=>{const u={...T,...r},d=t.useCallback(t=>{if(!e)return;const r=t.target;if("INPUT"===r.tagName||"TEXTAREA"===r.tagName||r.isContentEditable)return;const d=o.current;if(!d||!d.contains(document.activeElement))return;switch(t.key){case u.play:case" ":t.preventDefault(),n();break;case u.mute:case"M":t.preventDefault(),s();break;case u.fullscreen:case"F":t.preventDefault(),l();break;case u.pip:case"P":t.preventDefault(),a();break;case u.seekForward:t.preventDefault(),i(t.shiftKey?30:10);break;case u.seekBackward:t.preventDefault(),i(t.shiftKey?-30:-10);break;case u.volumeUp:t.preventDefault(),c(.1);break;case u.volumeDown:t.preventDefault(),c(-.1);break;case"0":case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":t.preventDefault(),i(-1/0)}},[e,u,n,s,l,a,i,c,o]);t.useEffect(()=>{if(e)return document.addEventListener("keydown",d),()=>{document.removeEventListener("keydown",d)}},[e,d])},F=e=>{const t=(new DOMParser).parseFromString(e,"text/xml");if(t.querySelector("parsererror"))return{ads:[],error:"Failed to parse VAST XML"};const r=[];return t.querySelectorAll("Ad").forEach(e=>{const t=e.querySelector("InLine");if(!t)return;const n=e.getAttribute("id")||"",s=t.querySelector("AdTitle")?.textContent||"",l=t.querySelector("Description")?.textContent||void 0,a=t.querySelectorAll("Creative");let i,c,o=0;const u=[],d=[],p={};a.forEach(e=>{const t=e.querySelector("Linear");if(!t)return;const r=t.querySelector("Duration")?.textContent;r&&(o=A(r));const n=t.getAttribute("skipoffset");n&&(i=A(n));t.querySelectorAll("MediaFile").forEach(e=>{const t=e.textContent?.trim();t&&d.push({url:t,type:e.getAttribute("type")||"video/mp4",width:parseInt(e.getAttribute("width")||"0",10),height:parseInt(e.getAttribute("height")||"0",10),bitrate:parseInt(e.getAttribute("bitrate")||"0",10)||void 0})});const s=t.querySelector("VideoClicks");s&&(c=s.querySelector("ClickThrough")?.textContent?.trim(),s.querySelectorAll("ClickTracking").forEach(e=>{const t=e.textContent?.trim();t&&u.push(t)}));t.querySelectorAll("TrackingEvents Tracking").forEach(e=>{const t=e.getAttribute("event"),r=e.textContent?.trim();t&&r&&(p[t]||(p[t]=[]),p[t].push(r))})});const m=[];t.querySelectorAll("Impression").forEach(e=>{const t=e.textContent?.trim();t&&m.push(t)}),d.length>0&&r.push({id:n,title:s,description:l,duration:o,skipOffset:i,clickThrough:c,clickTracking:u,impressionUrls:m,mediaFiles:d,trackingEvents:p})}),{ads:r}},A=e=>{if(e.includes("%"))return-1;const t=e.split(":");if(3!==t.length)return 0;return 3600*parseInt(t[0],10)+60*parseInt(t[1],10)+parseFloat(t[2])},V=async e=>{try{const t=await fetch(e.url,{method:"GET",headers:{Accept:"application/xml"}});if(!t.ok)return null;const r=await t.text(),n=F(r);if(n.error||0===n.ads.length)return null;const s=n.ads[0];return void 0!==e.skipDelay&&void 0===s.skipOffset&&(s.skipOffset=e.skipDelay),s}catch(e){return null}},R=e=>{if(0===e.length)return null;const t=document.createElement("video"),r=e.filter(e=>""!==t.canPlayType(e.type));if(0===r.length)return null;r.sort((e,t)=>{const r=e.width*e.height,n=t.width*t.height;return r!==n?n-r:(t.bitrate||0)-(e.bitrate||0)});return/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)&&r.length>1?r[r.length-1]:r[0]},I=e=>{(new Image).src=e},q=e=>{e.forEach(I)},B=e=>({id:e.id,title:e.title,duration:e.duration,skipOffset:e.skipOffset,clickThrough:e.clickThrough}),H=({vastConfig:e,videoRef:r,onAdStart:n,onAdEnd:s,onAdSkip:l,onAdError:a})=>{const[i,c]=t.useState(!1),[o,u]=t.useState(null),[d,p]=t.useState(0),[m,h]=t.useState(!1),v=t.useRef(null),x=t.useRef(""),g=t.useRef(0),y=t.useRef(new Set),f=t.useRef(null),b=Array.isArray(e)?e:e?[e]:[],k=t.useCallback(async e=>{const t=r.current;if(t&&!i)try{const r=await V(e);if(!r)return void a?.(new Error("Failed to fetch VAST ad"));const s=R(r.mediaFiles);if(!s)return void a?.(new Error("No compatible media file found"));x.current=t.currentSrc,g.current=t.currentTime,q(r.impressionUrls),v.current=r,u(B(r)),c(!0),p(r.duration),h(void 0===r.skipOffset||0===r.skipOffset),t.src=s.url,t.currentTime=0,await t.play(),r.trackingEvents.start&&q(r.trackingEvents.start),n?.(B(r)),f.current=window.setInterval(()=>{const e=Math.max(0,r.duration-t.currentTime);p(e),r.skipOffset&&t.currentTime>=r.skipOffset&&h(!0);const n=t.currentTime/r.duration*100;n>=25&&r.trackingEvents.firstQuartile&&q(r.trackingEvents.firstQuartile),n>=50&&r.trackingEvents.midpoint&&q(r.trackingEvents.midpoint),n>=75&&r.trackingEvents.thirdQuartile&&q(r.trackingEvents.thirdQuartile)},250);const l=()=>{r.trackingEvents.complete&&q(r.trackingEvents.complete),w()};t.addEventListener("ended",l,{once:!0})}catch(e){a?.(e instanceof Error?e:new Error("Ad playback failed")),w()}},[r,i,n,a]),w=t.useCallback(()=>{const e=r.current;e&&(f.current&&(clearInterval(f.current),f.current=null),x.current&&(e.src=x.current,e.currentTime=g.current,e.play().catch(()=>{})),c(!1),u(null),p(0),h(!1),v.current=null,s?.())},[r,s]),C=t.useCallback(()=>{if(!m||!v.current)return;const e=v.current;e.trackingEvents.skip&&q(e.trackingEvents.skip),l?.(),w()},[m,w,l]),E=t.useCallback(()=>{if(!v.current)return;const e=v.current;e.clickThrough&&window.open(e.clickThrough,"_blank"),e.clickTracking&&q(e.clickTracking)},[]),j=t.useCallback((e,t)=>{i||0===b.length||b.forEach(r=>{const n=r.position||"preroll",s=`${n}-${r.midrollTime||0}`;if(y.current.has(s))return;let l=!1;switch(n){case"preroll":l=0===e;break;case"midroll":r.midrollTime&&e>=r.midrollTime&&(l=!0);break;case"postroll":l=e>=t-.5}l&&(y.current.add(s),k(r))})},[b,i,k]);return t.useEffect(()=>()=>{f.current&&clearInterval(f.current)},[]),{isAdPlaying:i,currentAd:o,adTimeRemaining:d,canSkip:m,skipAd:C,handleAdClick:E,checkForAd:j}},D=t.forwardRef((n,s)=>{const{src:a,poster:i,autoPlay:c=!1,muted:o=!1,loop:u=!1,controls:p=!0,width:v="100%",className:x="",style:g={},pip:y=!0,fullscreen:w=!0,playbackSpeed:C=!0,playbackSpeeds:E=[.25,.5,.75,1,1.25,1.5,1.75,2],volume:j=!0,initialVolume:S=1,progressBar:T=!0,timeDisplay:F=!0,qualitySelector:A=!0,textTracks:V=[],vast:R,keyboard:I=!0,hotkeys:q,accentColor:B="#e50914",controlsTimeout:D=3e3,doubleClickFullscreen:$=!0,clickToPlay:O=!0,preload:Q="metadata",onPlay:U,onPause:X,onEnded:K,onTimeUpdate:G,onProgress:J,onVolumeChange:W,onFullscreenChange:Y,onPipChange:Z,onQualityChange:ee,onError:te,onAdStart:re,onAdEnd:ne,onAdSkip:se,onAdError:le,onReady:ae}=n,ie=t.useRef(null),ce=t.useRef(null),oe=t.useRef(null),ue=t.useRef(0),[de,pe]=t.useState(!1),[me,he]=t.useState(0),[ve,xe]=t.useState(0),[ge,ye]=t.useState(0),[fe,be]=t.useState(o?0:S),[ke,we]=t.useState(o),[Ce,Ee]=t.useState(!1),[je,_e]=t.useState(!1),[Ne,Pe]=t.useState(!0),[Se,Me]=t.useState(null),[ze,Te]=t.useState(!0),[Le,Fe]=t.useState(1),[Ae,Ve]=t.useState("auto"),[Re,Ie]=t.useState(null),qe=t.useMemo(()=>"string"==typeof a?[{src:a,type:"video/mp4"}]:a,[a]),{currentAd:Be,isAdPlaying:He,adTimeRemaining:De,canSkip:$e,skipAd:Oe,handleAdClick:Qe,checkForAd:Ue}=H({vastConfig:R,videoRef:ie,onAdStart:re,onAdEnd:ne,onAdSkip:se,onAdError:le}),Xe=t.useCallback(async()=>{const e=ie.current;if(e)try{await e.play()}catch(e){}},[]),Ke=t.useCallback(()=>{const e=ie.current;e&&e.pause()},[]),Ge=t.useCallback(()=>{const e=ie.current;e&&(e.pause(),e.currentTime=0)},[]),Je=t.useCallback(()=>{const e=ie.current;e&&(e.paused?Xe():Ke())},[Xe,Ke]),We=t.useCallback(e=>{const t=ie.current;t&&(t.currentTime=Math.max(0,Math.min(e,t.duration||0)))},[]),Ye=t.useCallback(e=>{const t=ie.current;if(!t)return;const r=Math.max(0,Math.min(1,e));t.volume=r,t.muted=0===r,be(r),we(0===r)},[]),Ze=t.useCallback(()=>{const e=ie.current;e&&(e.muted=!0,we(!0))},[]),et=t.useCallback(()=>{const e=ie.current;e&&(e.muted=!1,0===e.volume&&(e.volume=.5,be(.5)),we(!1))},[]),tt=t.useCallback(()=>{const e=ie.current;e&&(e.muted?et():Ze())},[Ze,et]),rt=t.useCallback(async()=>{const e=ce.current;if(e)try{await e.requestFullscreen(),Ee(!0)}catch(e){}},[]),nt=t.useCallback(async()=>{try{document.fullscreenElement&&(await document.exitFullscreen(),Ee(!1))}catch(e){}},[]),st=t.useCallback(async()=>{document.fullscreenElement?await nt():await rt()},[rt,nt]),lt=t.useCallback(async()=>{const e=ie.current;if(e&&document.pictureInPictureEnabled)try{await e.requestPictureInPicture(),_e(!0)}catch(e){}},[]),at=t.useCallback(async()=>{try{document.pictureInPictureElement&&(await document.exitPictureInPicture(),_e(!1))}catch(e){}},[]),it=t.useCallback(async()=>{document.pictureInPictureElement?await at():await lt()},[lt,at]),ct=t.useCallback(e=>{const t=ie.current;t&&(t.playbackRate=e,Fe(e))},[]),ot=t.useCallback(e=>{const t=qe.find(t=>t.label===e||t.quality===e);if(t&&ie.current){const r=ie.current,n=!r.paused,s=r.currentTime;r.src=t.src,r.currentTime=s,n&&r.play(),Ve(e),ee?.(e)}},[qe,ee]),ut=t.useCallback(e=>{const t=ie.current;if(t){for(let r=0;r<t.textTracks.length;r++){const n=t.textTracks[r];n.mode=n.label===e?"showing":"hidden"}Ie(e)}},[]),dt=t.useCallback(()=>{de&&Te(!1)},[de]),pt=t.useCallback(()=>{Te(!0),oe.current&&clearTimeout(oe.current),de&&(oe.current=setTimeout(dt,D))},[de,D,dt]),mt=t.useMemo(()=>l(pt,200),[pt]),ht=t.useCallback(()=>{if(!O)return;const e=Date.now();$&&e-ue.current<300?st():Je(),ue.current=e},[O,$,Je,st]);t.useEffect(()=>{const e=ie.current;if(!e)return;const t=()=>{pe(!0),U?.()},r=()=>{pe(!1),X?.()},n=()=>{pe(!1),K?.()},s=()=>{he(e.currentTime),G?.(e.currentTime),Ue(e.currentTime,e.duration)},l=()=>{xe(e.duration)},a=()=>{if(e.buffered.length>0){const t=e.buffered.end(e.buffered.length-1)/e.duration*100;ye(t),J?.(t)}},i=()=>{be(e.volume),we(e.muted),W?.(e.volume,e.muted)},c=()=>{xe(e.duration),Pe(!1),ae?.()},o=()=>{Pe(!0)},u=()=>{Pe(!1)},d=()=>{Pe(!1)},p=()=>{Me(e.error),Pe(!1),te?.(e.error)};return e.addEventListener("play",t),e.addEventListener("pause",r),e.addEventListener("ended",n),e.addEventListener("timeupdate",s),e.addEventListener("durationchange",l),e.addEventListener("progress",a),e.addEventListener("volumechange",i),e.addEventListener("loadedmetadata",c),e.addEventListener("waiting",o),e.addEventListener("playing",u),e.addEventListener("canplay",d),e.addEventListener("error",p),()=>{e.removeEventListener("play",t),e.removeEventListener("pause",r),e.removeEventListener("ended",n),e.removeEventListener("timeupdate",s),e.removeEventListener("durationchange",l),e.removeEventListener("progress",a),e.removeEventListener("volumechange",i),e.removeEventListener("loadedmetadata",c),e.removeEventListener("waiting",o),e.removeEventListener("playing",u),e.removeEventListener("canplay",d),e.removeEventListener("error",p)}},[U,X,K,G,J,W,ae,te,Ue]),t.useEffect(()=>{const e=()=>{const e=!!document.fullscreenElement;Ee(e),Y?.(e)};return document.addEventListener("fullscreenchange",e),()=>{document.removeEventListener("fullscreenchange",e)}},[Y]),t.useEffect(()=>{const e=ie.current;if(!e)return;const t=()=>{_e(!0),Z?.(!0)},r=()=>{_e(!1),Z?.(!1)};return e.addEventListener("enterpictureinpicture",t),e.addEventListener("leavepictureinpicture",r),()=>{e.removeEventListener("enterpictureinpicture",t),e.removeEventListener("leavepictureinpicture",r)}},[Z]),L({enabled:I,containerRef:ce,hotkeys:q,onPlay:Je,onMute:tt,onFullscreen:st,onPip:it,onSeek:e=>We(me+e),onVolume:e=>Ye(fe+e)}),t.useEffect(()=>()=>{oe.current&&clearTimeout(oe.current)},[]);const vt=t.useCallback(()=>{Me(null),ie.current?.load()},[]);t.useImperativeHandle(s,()=>({play:Xe,pause:Ke,stop:Ge,seek:We,setVolume:Ye,mute:Ze,unmute:et,toggleMute:tt,enterFullscreen:rt,exitFullscreen:nt,toggleFullscreen:st,enterPip:lt,exitPip:at,togglePip:it,setPlaybackRate:ct,setQuality:ot,getCurrentTime:()=>ie.current?.currentTime||0,getDuration:()=>ie.current?.duration||0,getVolume:()=>ie.current?.volume||0,isMuted:()=>ie.current?.muted??!0,isPlaying:()=>!ie.current?.paused,isFullscreen:()=>Ce,isPip:()=>je,getVideoElement:()=>ie.current}),[Xe,Ke,Ge,We,Ye,Ze,et,tt,rt,nt,st,lt,at,it,ct,ot,Ce,je]);const xt=qe[0]?.src||"",gt={"--plex-accent-color":B};return e.jsxs("div",{ref:ce,className:`plex-video-player ${Ce?"plex-video-player--fullscreen":""} ${x}`,style:{...gt,width:v,...g},onMouseMove:mt,onMouseLeave:()=>de&&Te(!1),tabIndex:0,children:[e.jsxs("video",{ref:ie,className:"plex-video-player__video",src:xt,poster:i,autoPlay:c,muted:o,loop:u,preload:Q,playsInline:!0,onClick:ht,children:[qe.slice(1).map((t,r)=>e.jsx("source",{src:t.src,type:t.type},r)),V.map((t,r)=>e.jsx("track",{kind:t.kind,src:t.src,srcLang:t.srclang,label:t.label,default:t.default},r))]}),e.jsx(z,{visible:Ne&&!Se}),Se&&e.jsx(M,{error:Se,onRetry:vt}),He&&Be&&e.jsx(P,{ad:Be,timeRemaining:De,canSkip:$e,onSkip:Oe,onClick:Qe}),p&&!He&&e.jsxs("div",{className:"plex-video-player__controls "+(ze?"plex-video-player__controls--visible":""),children:[T&&e.jsx(d,{currentTime:me,duration:ve,buffered:ge,onSeek:We}),e.jsxs("div",{className:"plex-video-player__controls-bottom",children:[e.jsxs("div",{className:"plex-video-player__controls-left",children:[e.jsx("button",{className:"plex-video-player__btn",onClick:Je,"aria-label":de?"Pause":"Play",type:"button",children:de?e.jsx(h,{}):e.jsx(m,{})}),j&&e.jsx(_,{volume:fe,muted:ke,onVolumeChange:Ye,onToggleMute:tt}),F&&e.jsxs("div",{className:"plex-video-player__time",children:[e.jsx("span",{children:r(me)}),e.jsx("span",{children:" / "}),e.jsx("span",{children:r(ve)})]})]}),e.jsxs("div",{className:"plex-video-player__controls-right",children:[C&&e.jsx(N,{playbackRate:Le,playbackSpeeds:E,onPlaybackRateChange:ct,qualityEnabled:A&&qe.length>1,sources:qe,currentQuality:Ae,onQualityChange:ot,captionsEnabled:V.length>0,textTracks:V,currentTrack:Re??void 0,onTrackChange:ut}),y&&document.pictureInPictureEnabled&&e.jsx("button",{className:"plex-video-player__btn",onClick:it,"aria-label":je?"Exit Picture in Picture":"Picture in Picture",type:"button",children:e.jsx(k,{})}),w&&e.jsx("button",{className:"plex-video-player__btn",onClick:st,"aria-label":Ce?"Exit Fullscreen":"Fullscreen",type:"button",children:Ce?e.jsx(b,{}):e.jsx(f,{})})]})]})]}),!de&&!Ne&&!Se&&!He&&e.jsx("button",{className:"plex-video-player__big-play",onClick:Je,"aria-label":"Play",type:"button",children:e.jsx(m,{})})]})});D.displayName="PlexVideoPlayer";const $={isPlaying:!1,isPaused:!0,isEnded:!1,isBuffering:!1,isSeeking:!1,isFullscreen:!1,isPip:!1,isMuted:!1,isAdPlaying:!1,volume:1,currentTime:0,duration:0,buffered:0,playbackRate:1,currentQuality:null,error:null};exports.AdOverlay=P,exports.CaptionsIcon=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("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"})}),exports.CheckIcon=j,exports.ErrorDisplay=M,exports.ErrorIcon=C,exports.ExternalLinkIcon=E,exports.ForwardIcon=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("path",{d:"M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z"})}),exports.FullscreenExitIcon=b,exports.FullscreenIcon=f,exports.Loader=z,exports.PauseIcon=h,exports.PipExitIcon=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("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"})}),exports.PipIcon=k,exports.PlayIcon=m,exports.PlexVideoPlayer=D,exports.ProgressBar=d,exports.QualityIcon=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("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"})}),exports.RewindIcon=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("path",{d:"M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z"})}),exports.SettingsIcon=w,exports.SettingsMenu=N,exports.SkipNextIcon=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("path",{d:"M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"})}),exports.SkipPrevIcon=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("path",{d:"M6 6h2v12H6zm3.5 6l8.5 6V6z"})}),exports.SpeedIcon=({className:t,size:r=24})=>e.jsx("svg",{className:t,width:r,height:r,viewBox:"0 0 24 24",fill:"currentColor",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("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"})}),exports.VolumeControl=_,exports.VolumeHighIcon=v,exports.VolumeLowIcon=g,exports.VolumeMediumIcon=x,exports.VolumeMuteIcon=y,exports.canPlayType=e=>{const t=document.createElement("video").canPlayType(e);return"probably"===t||"maybe"===t},exports.clamp=s,exports.convertToAdInfo=B,exports.debounce=(e,t)=>{let r=null;return(...n)=>{r&&clearTimeout(r),r=setTimeout(()=>e(...n),t)}},exports.default=D,exports.detectVideoType=e=>{const t=e.split("?")[0].split(".").pop()?.toLowerCase();return{mp4:"video/mp4",webm:"video/webm",ogg:"video/ogg",ogv:"video/ogg",m3u8:"application/x-mpegURL",mpd:"application/dash+xml",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska"}[t||""]||"video/mp4"},exports.exitFullscreen=o,exports.fetchVastAd=V,exports.fireTrackingPixel=I,exports.fireTrackingPixels=q,exports.formatTime=r,exports.generateId=()=>`plex-${Math.random().toString(36).substring(2,11)}`,exports.getBufferedEnd=u,exports.getFullscreenElement=i,exports.isFullscreenSupported=()=>!!(document.fullscreenEnabled||document.webkitFullscreenEnabled||document.mozFullScreenEnabled||document.msFullscreenEnabled),exports.isHlsNativelySupported=()=>""!==document.createElement("video").canPlayType("application/vnd.apple.mpegurl"),exports.isMobile=()=>/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),exports.isPipSupported=a,exports.isTouchDevice=()=>"ontouchstart"in window||navigator.maxTouchPoints>0,exports.parseTime=e=>{const t=e.split(":").map(Number);return 3===t.length?3600*t[0]+60*t[1]+t[2]:2===t.length?60*t[0]+t[1]:t[0]||0},exports.parseVastXml=F,exports.percentage=n,exports.requestFullscreen=c,exports.selectBestMediaFile=R,exports.throttle=l,exports.useKeyboard=L,exports.usePlayer=(e={})=>{const{autoPlay:r=!1,muted:n=!1,loop:s=!1,volume:l=1,playbackRate:d=1}=e,p=t.useRef(null),m=t.useRef(null),[h,v]=t.useState({...$,volume:l,isMuted:n,playbackRate:d}),x=t.useCallback(e=>{v(t=>({...t,...e}))},[]),g=t.useCallback(async()=>{const e=p.current;if(e)try{await e.play(),x({isPlaying:!0,isPaused:!1,isEnded:!1})}catch(e){}},[x]),y=t.useCallback(()=>{const e=p.current;e&&(e.pause(),x({isPlaying:!1,isPaused:!0}))},[x]),f=t.useCallback(()=>{h.isPlaying?y():g()},[h.isPlaying,g,y]),b=t.useCallback(e=>{const t=p.current;if(!t)return;const r=Math.max(0,Math.min(e,t.duration||0));t.currentTime=r,x({currentTime:r})},[x]),k=t.useCallback(e=>{const t=p.current;if(!t)return;const r=Math.max(0,Math.min(1,e));t.volume=r,t.muted=0===r,x({volume:r,isMuted:0===r})},[x]),w=t.useCallback(()=>{const e=p.current;e&&(e.muted=!e.muted,x({isMuted:e.muted}))},[x]),C=t.useCallback(e=>{const t=p.current;t&&(t.playbackRate=e,x({playbackRate:e}))},[x]),E=t.useCallback(async()=>{const e=m.current;if(e)try{await c(e),x({isFullscreen:!0})}catch(e){}},[x]),j=t.useCallback(async()=>{try{await o(),x({isFullscreen:!1})}catch(e){}},[x]),_=t.useCallback(async()=>{h.isFullscreen?await j():await E()},[h.isFullscreen,E,j]),N=t.useCallback(async()=>{const e=p.current;if(e&&a())try{await e.requestPictureInPicture(),x({isPip:!0})}catch(e){}},[x]),P=t.useCallback(async()=>{if(document.pictureInPictureElement)try{await document.exitPictureInPicture(),x({isPip:!1})}catch(e){}},[x]),S=t.useCallback(async()=>{h.isPip?await P():await N()},[h.isPip,N,P]);return t.useEffect(()=>{const e=p.current;if(!e)return;e.muted=n,e.volume=l,e.playbackRate=d,e.loop=s;const t={loadedmetadata:()=>{x({duration:e.duration})},timeupdate:()=>{x({currentTime:e.currentTime,buffered:u(e)})},play:()=>{x({isPlaying:!0,isPaused:!1,isEnded:!1})},pause:()=>{x({isPlaying:!1,isPaused:!0})},ended:()=>{x({isPlaying:!1,isPaused:!0,isEnded:!0})},waiting:()=>{x({isBuffering:!0})},canplay:()=>{x({isBuffering:!1})},seeking:()=>{x({isSeeking:!0})},seeked:()=>{x({isSeeking:!1})},volumechange:()=>{x({volume:e.volume,isMuted:e.muted})},ratechange:()=>{x({playbackRate:e.playbackRate})},error:()=>{x({error:e.error})},enterpictureinpicture:()=>{x({isPip:!0})},leavepictureinpicture:()=>{x({isPip:!1})}};return Object.entries(t).forEach(([t,r])=>{e.addEventListener(t,r)}),r&&g(),()=>{Object.entries(t).forEach(([t,r])=>{e.removeEventListener(t,r)})}},[r,n,s,l,d,g,x]),t.useEffect(()=>{const e=()=>{const e=!!i();x({isFullscreen:e})};return document.addEventListener("fullscreenchange",e),document.addEventListener("webkitfullscreenchange",e),document.addEventListener("mozfullscreenchange",e),document.addEventListener("MSFullscreenChange",e),()=>{document.removeEventListener("fullscreenchange",e),document.removeEventListener("webkitfullscreenchange",e),document.removeEventListener("mozfullscreenchange",e),document.removeEventListener("MSFullscreenChange",e)}},[x]),{state:h,videoRef:p,containerRef:m,play:g,pause:y,togglePlay:f,seek:b,setVolume:k,toggleMute:w,setPlaybackRate:C,enterFullscreen:E,exitFullscreen:j,toggleFullscreen:_,enterPip:N,exitPip:P,togglePip:S}},exports.useVast=H;
|
|
2
|
+
//# sourceMappingURL=index.js.map
|