@frameset/plex-player 1.0.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":["../src/utils/helpers.ts","../src/hooks/usePlayer.ts","../src/hooks/useKeyboard.ts","../src/utils/vast.ts","../src/hooks/useVast.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/components/PlexVideoPlayer.tsx"],"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","// 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","// 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","// 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","// PlexVideo Player - Main Component\r\n// © FRAMESET STUDIO\r\n\r\nimport React, {\r\n forwardRef,\r\n useImperativeHandle,\r\n useRef,\r\n useState,\r\n useCallback,\r\n useEffect,\r\n useMemo,\r\n} from 'react';\r\nimport {\r\n PlexVideoPlayerProps,\r\n PlexVideoPlayerRef,\r\n VideoSource,\r\n} from '../types';\r\nimport { usePlayer } from '../hooks/usePlayer';\r\nimport { useKeyboard } from '../hooks/useKeyboard';\r\nimport { useVast } from '../hooks/useVast';\r\nimport {\r\n formatTime,\r\n detectVideoType,\r\n isPipSupported,\r\n isFullscreenSupported,\r\n clamp,\r\n} from '../utils/helpers';\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 PipExitIcon,\r\n} from './Icons';\r\nimport '../styles/player.css';\r\n\r\nconst DEFAULT_PLAYBACK_SPEEDS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];\r\nconst CONTROLS_HIDE_DELAY = 3000;\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 preload = 'metadata',\r\n width = '100%',\r\n height = 'auto',\r\n controls = true,\r\n pip = true,\r\n fullscreen = true,\r\n playbackSpeed = true,\r\n playbackSpeeds = DEFAULT_PLAYBACK_SPEEDS,\r\n volume: volumeControl = 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 className = '',\r\n style,\r\n accentColor,\r\n theme = 'dark',\r\n controlsTimeout = CONTROLS_HIDE_DELAY,\r\n doubleClickFullscreen = true,\r\n clickToPlay = true,\r\n thumbnailPreview,\r\n onPlay,\r\n onPause,\r\n onEnded,\r\n onTimeUpdate,\r\n onProgress,\r\n onVolumeChange,\r\n onSeeking,\r\n onSeeked,\r\n onRateChange,\r\n onQualityChange,\r\n onFullscreenChange,\r\n onPipChange,\r\n onError,\r\n onReady,\r\n onAdStart,\r\n onAdEnd,\r\n onAdSkip,\r\n onAdError,\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<number | null>(null);\r\n\r\n // State\r\n const [, setIsReady] = useState(false);\r\n const [showPoster, setShowPoster] = useState(!!poster && !autoPlay);\r\n const [controlsVisible, setControlsVisible] = useState(true);\r\n const [currentQuality, setCurrentQuality] = useState<string | null>(null);\r\n const [currentTrack, setCurrentTrack] = useState<string | null>(null);\r\n\r\n // Player hook\r\n const {\r\n state,\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,\r\n toggleFullscreen,\r\n enterPip,\r\n exitPip,\r\n togglePip,\r\n } = usePlayer({\r\n autoPlay,\r\n muted,\r\n loop,\r\n volume: initialVolume,\r\n playbackRate: 1,\r\n });\r\n\r\n // VAST hook\r\n const vastHook = 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 // Normalize sources\r\n const sources: VideoSource[] = useMemo(() => {\r\n if (typeof src === 'string') {\r\n return [{ src, type: detectVideoType(src) }];\r\n }\r\n return src.map((s) => ({\r\n ...s,\r\n type: s.type || detectVideoType(s.src),\r\n }));\r\n }, [src]);\r\n\r\n // Current source\r\n const currentSource = useMemo(() => {\r\n if (!currentQuality) return sources[0];\r\n return sources.find((s) => s.quality === currentQuality) || sources[0];\r\n }, [sources, currentQuality]);\r\n\r\n // Hide controls after timeout\r\n const hideControls = useCallback(() => {\r\n if (state.isPlaying && !vastHook.isAdPlaying) {\r\n setControlsVisible(false);\r\n }\r\n }, [state.isPlaying, vastHook.isAdPlaying]);\r\n\r\n const resetControlsTimeout = useCallback(() => {\r\n setControlsVisible(true);\r\n if (controlsTimeoutRef.current) {\r\n clearTimeout(controlsTimeoutRef.current);\r\n }\r\n controlsTimeoutRef.current = window.setTimeout(hideControls, controlsTimeout);\r\n }, [hideControls, controlsTimeout]);\r\n\r\n // Event handlers\r\n const handleVideoClick = useCallback(() => {\r\n if (clickToPlay && !vastHook.isAdPlaying) {\r\n togglePlay();\r\n }\r\n resetControlsTimeout();\r\n }, [clickToPlay, vastHook.isAdPlaying, togglePlay, resetControlsTimeout]);\r\n\r\n const handleDoubleClick = useCallback(() => {\r\n if (doubleClickFullscreen && !vastHook.isAdPlaying) {\r\n toggleFullscreen();\r\n }\r\n }, [doubleClickFullscreen, vastHook.isAdPlaying, toggleFullscreen]);\r\n\r\n const handleMouseMove = useCallback(() => {\r\n resetControlsTimeout();\r\n }, [resetControlsTimeout]);\r\n\r\n const handleMouseLeave = useCallback(() => {\r\n if (state.isPlaying) {\r\n hideControls();\r\n }\r\n }, [state.isPlaying, hideControls]);\r\n\r\n const handleSeek = useCallback(\r\n (time: number) => {\r\n seek(time);\r\n onSeeking?.(time);\r\n },\r\n [seek, onSeeking]\r\n );\r\n\r\n const handleQualityChange = useCallback(\r\n (quality: string) => {\r\n const source = sources.find((s) => s.quality === quality);\r\n if (source && videoRef.current) {\r\n const currentTime = videoRef.current.currentTime;\r\n const wasPlaying = !videoRef.current.paused;\r\n \r\n setCurrentQuality(quality);\r\n videoRef.current.src = source.src;\r\n videoRef.current.currentTime = currentTime;\r\n \r\n if (wasPlaying) {\r\n videoRef.current.play();\r\n }\r\n \r\n onQualityChange?.(quality);\r\n }\r\n },\r\n [sources, onQualityChange]\r\n );\r\n\r\n const handleTrackChange = useCallback(\r\n (track: string | null) => {\r\n setCurrentTrack(track);\r\n if (videoRef.current) {\r\n const tracks = videoRef.current.textTracks;\r\n for (let i = 0; i < tracks.length; i++) {\r\n tracks[i].mode = tracks[i].language === track ? 'showing' : 'hidden';\r\n }\r\n }\r\n },\r\n []\r\n );\r\n\r\n const handleRetry = useCallback(() => {\r\n if (videoRef.current) {\r\n videoRef.current.load();\r\n play();\r\n }\r\n }, [play]);\r\n\r\n const handlePosterClick = useCallback(() => {\r\n setShowPoster(false);\r\n play();\r\n }, [play]);\r\n\r\n // Keyboard shortcuts\r\n useKeyboard({\r\n enabled: keyboard,\r\n hotkeys,\r\n onPlay: togglePlay,\r\n onMute: toggleMute,\r\n onFullscreen: toggleFullscreen,\r\n onPip: togglePip,\r\n onSeek: (delta) => {\r\n if (videoRef.current) {\r\n const newTime = clamp(\r\n videoRef.current.currentTime + delta,\r\n 0,\r\n videoRef.current.duration\r\n );\r\n seek(newTime);\r\n }\r\n },\r\n onVolume: (delta) => {\r\n setVolume(clamp(state.volume + delta, 0, 1));\r\n },\r\n containerRef,\r\n });\r\n\r\n // Video event listeners\r\n useEffect(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const handlers = {\r\n loadedmetadata: () => {\r\n setIsReady(true);\r\n onReady?.();\r\n },\r\n play: () => {\r\n setShowPoster(false);\r\n onPlay?.();\r\n },\r\n pause: () => onPause?.(),\r\n ended: () => onEnded?.(),\r\n timeupdate: () => {\r\n onTimeUpdate?.(video.currentTime);\r\n vastHook.checkForAd(video.currentTime, video.duration);\r\n },\r\n progress: () => {\r\n if (video.buffered.length > 0) {\r\n onProgress?.(video.buffered.end(video.buffered.length - 1));\r\n }\r\n },\r\n volumechange: () => {\r\n onVolumeChange?.(video.volume, video.muted);\r\n },\r\n seeking: () => onSeeking?.(video.currentTime),\r\n seeked: () => onSeeked?.(video.currentTime),\r\n ratechange: () => onRateChange?.(video.playbackRate),\r\n error: () => onError?.(video.error),\r\n enterpictureinpicture: () => onPipChange?.(true),\r\n leavepictureinpicture: () => onPipChange?.(false),\r\n };\r\n\r\n Object.entries(handlers).forEach(([event, handler]) => {\r\n video.addEventListener(event, handler);\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 }, [\r\n onPlay,\r\n onPause,\r\n onEnded,\r\n onTimeUpdate,\r\n onProgress,\r\n onVolumeChange,\r\n onSeeking,\r\n onSeeked,\r\n onRateChange,\r\n onError,\r\n onReady,\r\n onPipChange,\r\n vastHook,\r\n ]);\r\n\r\n // Fullscreen change handler\r\n useEffect(() => {\r\n const handleFullscreenChange = () => {\r\n const isFs = !!document.fullscreenElement;\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 // 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 // Expose imperative methods\r\n useImperativeHandle(ref, () => ({\r\n play: async () => {\r\n await play();\r\n },\r\n pause: () => {\r\n pause();\r\n },\r\n stop: () => {\r\n pause();\r\n if (videoRef.current) {\r\n videoRef.current.currentTime = 0;\r\n }\r\n },\r\n seek: (time: number) => {\r\n seek(time);\r\n },\r\n setVolume: (vol: number) => {\r\n setVolume(vol);\r\n },\r\n mute: () => {\r\n if (videoRef.current) {\r\n videoRef.current.muted = true;\r\n }\r\n },\r\n unmute: () => {\r\n if (videoRef.current) {\r\n videoRef.current.muted = false;\r\n }\r\n },\r\n toggleMute: () => {\r\n toggleMute();\r\n },\r\n enterFullscreen: async () => {\r\n await enterFullscreen();\r\n },\r\n exitFullscreen: async () => {\r\n await exitFullscreen();\r\n },\r\n toggleFullscreen: async () => {\r\n await toggleFullscreen();\r\n },\r\n enterPip: async () => {\r\n await enterPip();\r\n },\r\n exitPip: async () => {\r\n await exitPip();\r\n },\r\n togglePip: async () => {\r\n await togglePip();\r\n },\r\n setPlaybackRate: (rate: number) => {\r\n setPlaybackRate(rate);\r\n },\r\n setQuality: (quality: string) => {\r\n handleQualityChange(quality);\r\n },\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 || false,\r\n isPlaying: () => !videoRef.current?.paused,\r\n isFullscreen: () => state.isFullscreen,\r\n isPip: () => state.isPip,\r\n getVideoElement: () => videoRef.current,\r\n }));\r\n\r\n // CSS custom properties for accent color\r\n const customStyle = useMemo(() => {\r\n const styles: React.CSSProperties = {\r\n width,\r\n height,\r\n ...style,\r\n };\r\n if (accentColor) {\r\n (styles as Record<string, string>)['--plex-primary'] = accentColor;\r\n }\r\n return styles;\r\n }, [width, height, style, accentColor]);\r\n\r\n const containerClassName = [\r\n 'plex-video-player',\r\n `plex-video-player--theme-${theme}`,\r\n state.isFullscreen && 'plex-video-player--fullscreen',\r\n controlsVisible && 'plex-video-player--controls-visible',\r\n vastHook.isAdPlaying && 'plex-video-player--ad-playing',\r\n className,\r\n ]\r\n .filter(Boolean)\r\n .join(' ');\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={containerClassName}\r\n style={customStyle}\r\n onMouseMove={handleMouseMove}\r\n onMouseLeave={handleMouseLeave}\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={currentSource?.src}\r\n poster={showPoster ? undefined : poster}\r\n preload={preload}\r\n loop={loop}\r\n muted={muted}\r\n playsInline\r\n onClick={handleVideoClick}\r\n onDoubleClick={handleDoubleClick}\r\n >\r\n {sources.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 src={track.src}\r\n kind={track.kind}\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 {/* Poster Overlay */}\r\n {showPoster && poster && (\r\n <div\r\n className=\"plex-video-player__poster\"\r\n style={{ backgroundImage: `url(${poster})` }}\r\n onClick={handlePosterClick}\r\n />\r\n )}\r\n\r\n {/* Loading Indicator */}\r\n <Loader visible={state.isBuffering && !showPoster} />\r\n\r\n {/* Error Display */}\r\n {state.error && <ErrorDisplay error={state.error} onRetry={handleRetry} />}\r\n\r\n {/* Ad Overlay */}\r\n {vastHook.isAdPlaying && vastHook.currentAd && (\r\n <AdOverlay\r\n ad={vastHook.currentAd}\r\n timeRemaining={vastHook.adTimeRemaining}\r\n canSkip={vastHook.canSkip}\r\n onSkip={vastHook.skipAd}\r\n onClick={vastHook.handleAdClick}\r\n />\r\n )}\r\n\r\n {/* Controls */}\r\n {controls && !showPoster && !state.error && (\r\n <div className=\"plex-video-player__controls\">\r\n {/* Progress Bar */}\r\n {progressBar && (\r\n <ProgressBar\r\n currentTime={state.currentTime}\r\n duration={state.duration}\r\n buffered={state.buffered}\r\n onSeek={handleSeek}\r\n thumbnailPreview={thumbnailPreview}\r\n disabled={vastHook.isAdPlaying}\r\n />\r\n )}\r\n\r\n {/* Controls Row */}\r\n <div className=\"plex-video-player__controls-row\">\r\n {/* Left Controls */}\r\n <div className=\"plex-video-player__controls-left\">\r\n {/* Play/Pause */}\r\n <button\r\n className=\"plex-video-player__btn plex-video-player__btn--play\"\r\n onClick={togglePlay}\r\n aria-label={state.isPlaying ? 'Pause' : 'Play'}\r\n title={state.isPlaying ? 'Pause (Space)' : 'Play (Space)'}\r\n disabled={vastHook.isAdPlaying}\r\n type=\"button\"\r\n >\r\n {state.isPlaying ? <PauseIcon /> : <PlayIcon />}\r\n </button>\r\n\r\n {/* Volume */}\r\n {volumeControl && (\r\n <VolumeControl\r\n volume={state.volume}\r\n muted={state.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(state.currentTime)}</span>\r\n <span className=\"plex-video-player__time-separator\">/</span>\r\n <span>{formatTime(state.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 */}\r\n {(playbackSpeed || qualitySelector) && (\r\n <SettingsMenu\r\n playbackRate={state.playbackRate}\r\n playbackSpeeds={playbackSpeeds}\r\n onPlaybackRateChange={setPlaybackRate}\r\n qualityEnabled={qualitySelector}\r\n sources={sources.length > 1 ? sources : undefined}\r\n currentQuality={currentQuality || undefined}\r\n onQualityChange={handleQualityChange}\r\n captionsEnabled={!!textTracks && textTracks.length > 0}\r\n textTracks={textTracks}\r\n currentTrack={currentTrack || undefined}\r\n onTrackChange={handleTrackChange}\r\n />\r\n )}\r\n\r\n {/* Picture-in-Picture */}\r\n {pip && isPipSupported() && (\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={togglePip}\r\n aria-label={state.isPip ? 'Exit Picture-in-Picture' : 'Picture-in-Picture'}\r\n title={state.isPip ? 'Exit PiP (P)' : 'Picture-in-Picture (P)'}\r\n disabled={vastHook.isAdPlaying}\r\n type=\"button\"\r\n >\r\n {state.isPip ? <PipExitIcon /> : <PipIcon />}\r\n </button>\r\n )}\r\n\r\n {/* Fullscreen */}\r\n {fullscreen && isFullscreenSupported() && (\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={toggleFullscreen}\r\n aria-label={state.isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}\r\n title={state.isFullscreen ? 'Exit Fullscreen (F)' : 'Fullscreen (F)'}\r\n type=\"button\"\r\n >\r\n {state.isFullscreen ? <FullscreenExitIcon /> : <FullscreenIcon />}\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n </div>\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"],"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","initialState","isPlaying","isPaused","isEnded","isBuffering","isSeeking","isFullscreen","isPip","isMuted","isAdPlaying","volume","duration","playbackRate","currentQuality","error","usePlayer","options","autoPlay","muted","loop","initialVolume","initialRate","videoRef","useRef","containerRef","state","setState","useState","updateState","useCallback","updates","prev","play","current","pause","togglePlay","seek","clampedTime","setVolume","clampedVolume","toggleMute","setPlaybackRate","rate","enterFullscreen","container","exitFullscreenFn","toggleFullscreen","enterPip","requestPictureInPicture","exitPipFn","pictureInPictureElement","exitPictureInPicture","togglePip","useEffect","handlers","loadedmetadata","timeupdate","ended","waiting","canplay","seeking","seeked","volumechange","ratechange","enterpictureinpicture","leavepictureinpicture","Object","entries","forEach","event","handler","addEventListener","removeEventListener","handleFullscreenChange","isFs","exitPip","DEFAULT_HOTKEYS","mute","fullscreen","pip","seekForward","seekBackward","volumeUp","volumeDown","useKeyboard","enabled","hotkeys","onPlay","onMute","onFullscreen","onPip","onSeek","onVolume","mergedHotkeys","handleKeyDown","target","tagName","isContentEditable","contains","activeElement","key","preventDefault","shiftKey","Infinity","parseVastXml","xmlString","doc","DOMParser","parseFromString","querySelector","ads","querySelectorAll","adElement","inLine","id","getAttribute","title","textContent","description","undefined","creatives","skipOffset","clickThrough","clickTracking","mediaFiles","trackingEvents","creative","linear","durationStr","parseVastDuration","skipOffsetAttr","mediaFile","trim","push","width","parseInt","height","bitrate","videoClicks","ct","tracking","impressionUrls","impression","includes","parseFloat","fetchVastAd","config","response","fetch","method","headers","Accept","ok","text","ad","skipDelay","selectBestMediaFile","supportedFiles","filter","file","sort","a","b","resA","resB","fireTrackingPixel","Image","src","fireTrackingPixels","urls","convertToAdInfo","useVast","vastConfig","onAdStart","onAdEnd","onAdSkip","onAdError","setIsAdPlaying","currentAd","setCurrentAd","adTimeRemaining","setAdTimeRemaining","canSkip","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","position","midrollTime","has","shouldPlay","add","ProgressBar","thumbnailPreview","disabled","progressRef","isDragging","setIsDragging","hoverTime","setHoverTime","hoverPosition","setHoverPosition","playedPercent","bufferedPercent","getTimeFromPosition","clientX","rect","getBoundingClientRect","left","handleMouseMove","e","handleMouseDown","handleMouseUp","handleMouseEnter","handleMouseLeave","mouseMoveHandler","handleTouchStart","touch","touches","handleTouchMove","handleTouchEnd","_jsxs","ref","className","onMouseDown","onMouseEnter","onMouseLeave","onMouseMove","nativeEvent","onTouchStart","onTouchMove","onTouchEnd","role","tabIndex","children","_jsx","style","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","onVolumeChange","onToggleMute","handleSliderChange","newVolume","onClick","step","onChange","background","SettingsMenu","playbackSpeeds","onPlaybackRateChange","qualityEnabled","sources","onQualityChange","captionsEnabled","textTracks","currentTrack","onTrackChange","isOpen","setIsOpen","currentView","setCurrentView","handleClickOutside","handleToggle","handleSpeedSelect","speed","handleQualitySelect","quality","handleTrackSelect","track","_Fragment","cursor","border","textAlign","source","label","srclang","AdOverlay","timeRemaining","onSkip","skipText","ceil","stopPropagation","ERROR_MESSAGES","ErrorDisplay","onRetry","errorCode","code","message","Loader","visible","DEFAULT_PLAYBACK_SPEEDS","CONTROLS_HIDE_DELAY","PlexVideoPlayer","forwardRef","props","poster","preload","controls","playbackSpeed","volumeControl","progressBar","timeDisplay","qualitySelector","vast","keyboard","accentColor","theme","controlsTimeout","doubleClickFullscreen","clickToPlay","onPause","onEnded","onTimeUpdate","onProgress","onSeeking","onSeeked","onRateChange","onFullscreenChange","onPipChange","onError","onReady","controlsTimeoutRef","setIsReady","showPoster","setShowPoster","controlsVisible","setControlsVisible","setCurrentQuality","setCurrentTrack","vastHook","useMemo","s","currentSource","find","hideControls","resetControlsTimeout","handleVideoClick","handleDoubleClick","handleSeek","handleQualityChange","wasPlaying","paused","handleTrackChange","tracks","mode","language","handleRetry","load","handlePosterClick","delta","newTime","progress","useImperativeHandle","stop","vol","unmute","setQuality","getCurrentTime","getDuration","getVolume","getVideoElement","customStyle","styles","containerClassName","Boolean","join","playsInline","onDoubleClick","kind","srcLang","default","displayName"],"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),s=Math.floor(e%60);return t>0?`${t}:${r.toString().padStart(2,"0")}:${s.toString().padStart(2,"0")}`:`${r}:${s.toString().padStart(2,"0")}`},s=(e,t)=>0===t?0:Math.min(100,Math.max(0,e/t*100)),n=(e,t,r)=>Math.min(r,Math.max(t,e)),l=()=>!!(document.fullscreenEnabled||document.webkitFullscreenEnabled||document.mozFullScreenEnabled||document.msFullscreenEnabled),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=>{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"},d=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)},p={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},m=(e={})=>{const{autoPlay:r=!1,muted:s=!1,loop:n=!1,volume:l=1,playbackRate:u=1}=e,m=t.useRef(null),h=t.useRef(null),[v,x]=t.useState({...p,volume:l,isMuted:s,playbackRate:u}),g=t.useCallback(e=>{x(t=>({...t,...e}))},[]),y=t.useCallback(async()=>{const e=m.current;if(e)try{await e.play(),g({isPlaying:!0,isPaused:!1,isEnded:!1})}catch(e){}},[g]),f=t.useCallback(()=>{const e=m.current;e&&(e.pause(),g({isPlaying:!1,isPaused:!0}))},[g]),w=t.useCallback(()=>{v.isPlaying?f():y()},[v.isPlaying,y,f]),k=t.useCallback(e=>{const t=m.current;if(!t)return;const r=Math.max(0,Math.min(e,t.duration||0));t.currentTime=r,g({currentTime:r})},[g]),b=t.useCallback(e=>{const t=m.current;if(!t)return;const r=Math.max(0,Math.min(1,e));t.volume=r,t.muted=0===r,g({volume:r,isMuted:0===r})},[g]),j=t.useCallback(()=>{const e=m.current;e&&(e.muted=!e.muted,g({isMuted:e.muted}))},[g]),C=t.useCallback(e=>{const t=m.current;t&&(t.playbackRate=e,g({playbackRate:e}))},[g]),P=t.useCallback(async()=>{const e=h.current;if(e)try{await c(e),g({isFullscreen:!0})}catch(e){}},[g]),E=t.useCallback(async()=>{try{await o(),g({isFullscreen:!1})}catch(e){}},[g]),_=t.useCallback(async()=>{v.isFullscreen?await E():await P()},[v.isFullscreen,P,E]),N=t.useCallback(async()=>{const e=m.current;if(e&&a())try{await e.requestPictureInPicture(),g({isPip:!0})}catch(e){}},[g]),S=t.useCallback(async()=>{if(document.pictureInPictureElement)try{await document.exitPictureInPicture(),g({isPip:!1})}catch(e){}},[g]),M=t.useCallback(async()=>{v.isPip?await S():await N()},[v.isPip,N,S]);return t.useEffect(()=>{const e=m.current;if(!e)return;e.muted=s,e.volume=l,e.playbackRate=u,e.loop=n;const t={loadedmetadata:()=>{g({duration:e.duration})},timeupdate:()=>{g({currentTime:e.currentTime,buffered:d(e)})},play:()=>{g({isPlaying:!0,isPaused:!1,isEnded:!1})},pause:()=>{g({isPlaying:!1,isPaused:!0})},ended:()=>{g({isPlaying:!1,isPaused:!0,isEnded:!0})},waiting:()=>{g({isBuffering:!0})},canplay:()=>{g({isBuffering:!1})},seeking:()=>{g({isSeeking:!0})},seeked:()=>{g({isSeeking:!1})},volumechange:()=>{g({volume:e.volume,isMuted:e.muted})},ratechange:()=>{g({playbackRate:e.playbackRate})},error:()=>{g({error:e.error})},enterpictureinpicture:()=>{g({isPip:!0})},leavepictureinpicture:()=>{g({isPip:!1})}};return Object.entries(t).forEach(([t,r])=>{e.addEventListener(t,r)}),r&&y(),()=>{Object.entries(t).forEach(([t,r])=>{e.removeEventListener(t,r)})}},[r,s,n,l,u,y,g]),t.useEffect(()=>{const e=()=>{const e=!!i();g({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)}},[g]),{state:v,videoRef:m,containerRef:h,play:y,pause:f,togglePlay:w,seek:k,setVolume:b,toggleMute:j,setPlaybackRate:C,enterFullscreen:P,exitFullscreen:E,toggleFullscreen:_,enterPip:N,exitPip:S,togglePip:M}},h={play:"Space",mute:"m",fullscreen:"f",pip:"p",seekForward:"ArrowRight",seekBackward:"ArrowLeft",volumeUp:"ArrowUp",volumeDown:"ArrowDown"},v=({enabled:e,hotkeys:r={},onPlay:s,onMute:n,onFullscreen:l,onPip:a,onSeek:i,onVolume:c,containerRef:o})=>{const u={...h,...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(),s();break;case u.mute:case"M":t.preventDefault(),n();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,s,n,l,a,i,c,o]);t.useEffect(()=>{if(e)return document.addEventListener("keydown",d),()=>{document.removeEventListener("keydown",d)}},[e,d])},x=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 s=e.getAttribute("id")||"",n=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=g(r));const s=t.getAttribute("skipoffset");s&&(i=g(s));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 n=t.querySelector("VideoClicks");n&&(c=n.querySelector("ClickThrough")?.textContent?.trim(),n.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:s,title:n,description:l,duration:o,skipOffset:i,clickThrough:c,clickTracking:u,impressionUrls:m,mediaFiles:d,trackingEvents:p})}),{ads:r}},g=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])},y=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(),s=x(r);if(s.error||0===s.ads.length)return null;const n=s.ads[0];return void 0!==e.skipDelay&&void 0===n.skipOffset&&(n.skipOffset=e.skipDelay),n}catch(e){return null}},f=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,s=t.width*t.height;return r!==s?s-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]},w=e=>{(new Image).src=e},k=e=>{e.forEach(w)},b=e=>({id:e.id,title:e.title,duration:e.duration,skipOffset:e.skipOffset,clickThrough:e.clickThrough}),j=({vastConfig:e,videoRef:r,onAdStart:s,onAdEnd:n,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),w=t.useRef(new Set),j=t.useRef(null),C=Array.isArray(e)?e:e?[e]:[],P=t.useCallback(async e=>{const t=r.current;if(t&&!i)try{const r=await y(e);if(!r)return void a?.(new Error("Failed to fetch VAST ad"));const n=f(r.mediaFiles);if(!n)return void a?.(new Error("No compatible media file found"));x.current=t.currentSrc,g.current=t.currentTime,k(r.impressionUrls),v.current=r,u(b(r)),c(!0),p(r.duration),h(void 0===r.skipOffset||0===r.skipOffset),t.src=n.url,t.currentTime=0,await t.play(),r.trackingEvents.start&&k(r.trackingEvents.start),s?.(b(r)),j.current=window.setInterval(()=>{const e=Math.max(0,r.duration-t.currentTime);p(e),r.skipOffset&&t.currentTime>=r.skipOffset&&h(!0);const s=t.currentTime/r.duration*100;s>=25&&r.trackingEvents.firstQuartile&&k(r.trackingEvents.firstQuartile),s>=50&&r.trackingEvents.midpoint&&k(r.trackingEvents.midpoint),s>=75&&r.trackingEvents.thirdQuartile&&k(r.trackingEvents.thirdQuartile)},250);const l=()=>{r.trackingEvents.complete&&k(r.trackingEvents.complete),E()};t.addEventListener("ended",l,{once:!0})}catch(e){a?.(e instanceof Error?e:new Error("Ad playback failed")),E()}},[r,i,s,a]),E=t.useCallback(()=>{const e=r.current;e&&(j.current&&(clearInterval(j.current),j.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,n?.())},[r,n]),_=t.useCallback(()=>{if(!m||!v.current)return;const e=v.current;e.trackingEvents.skip&&k(e.trackingEvents.skip),l?.(),E()},[m,E,l]),N=t.useCallback(()=>{if(!v.current)return;const e=v.current;e.clickThrough&&window.open(e.clickThrough,"_blank"),e.clickTracking&&k(e.clickTracking)},[]),S=t.useCallback((e,t)=>{i||0===C.length||C.forEach(r=>{const s=r.position||"preroll",n=`${s}-${r.midrollTime||0}`;if(w.current.has(n))return;let l=!1;switch(s){case"preroll":l=0===e;break;case"midroll":r.midrollTime&&e>=r.midrollTime&&(l=!0);break;case"postroll":l=e>=t-.5}l&&(w.current.add(n),P(r))})},[C,i,P]);return t.useEffect(()=>()=>{j.current&&clearInterval(j.current)},[]),{isAdPlaying:i,currentAd:o,adTimeRemaining:d,canSkip:m,skipAd:_,handleAdClick:N,checkForAd:S}},C=({currentTime:l,duration:a,buffered:i,onSeek:c,thumbnailPreview:o,disabled:u=!1})=>{const d=t.useRef(null),[p,m]=t.useState(!1),[h,v]=t.useState(null),[x,g]=t.useState(0),y=s(l,a),f=s(i,a),w=t.useCallback(e=>{if(!d.current)return 0;const t=d.current.getBoundingClientRect();return n((e-t.left)/t.width,0,1)*a},[a]),k=t.useCallback(e=>{if(!d.current)return;const t=d.current.getBoundingClientRect(),r="clientX"in e?e.clientX:0,s=n(r-t.left,0,t.width),l=w(r);g(s),v(l),p&&c(l)},[p,w,c]),b=t.useCallback(e=>{if(u)return;e.preventDefault(),m(!0);const t=w(e.clientX);c(t)},[u,w,c]),j=t.useCallback(()=>{m(!1)},[]),C=t.useCallback(e=>{const t=w(e.clientX);v(t)},[w]),E=t.useCallback(()=>{v(null)},[]);t.useEffect(()=>{if(p){const e=e=>k(e);return window.addEventListener("mousemove",e),window.addEventListener("mouseup",j),()=>{window.removeEventListener("mousemove",e),window.removeEventListener("mouseup",j)}}},[p,k,j]);const _=t.useCallback(e=>{if(u)return;e.preventDefault();const t=e.touches[0],r=w(t.clientX);m(!0),c(r)},[u,w,c]),N=t.useCallback(e=>{if(!p)return;const t=e.touches[0],r=w(t.clientX);c(r)},[p,w,c]),S=t.useCallback(()=>{m(!1)},[]);return e.jsxs("div",{ref:d,className:"plex-video-player__progress-container",onMouseDown:b,onMouseEnter:C,onMouseLeave:E,onMouseMove:e=>k(e.nativeEvent),onTouchStart:_,onTouchMove:N,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:`${f}%`}}),e.jsx("div",{className:"plex-video-player__progress-played",style:{width:`${y}%`}}),e.jsx("div",{className:"plex-video-player__progress-handle",style:{left:`${y}%`}})]}),o?.enabled&&null!==h&&e.jsxs("div",{className:"plex-video-player__thumbnail-preview",style:{left:`${x}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(h,a,o.interval||10,o.width||160,o.height||90),backgroundSize:"cover"}}),e.jsx("div",{className:"plex-video-player__thumbnail-time",children:r(h)})]}),null!==h&&!o?.enabled&&e.jsx("div",{className:"plex-video-player__thumbnail-preview",style:{left:`${x}px`,width:"auto",height:"auto",padding:"4px 8px"},children:e.jsx("span",{style:{color:"white",fontSize:"12px"},children:r(h)})})]})},P=(e,t,r,s,n)=>{const l=Math.floor(e/r);return`-${l%10*s}px -${Math.floor(l/10)*n}px`},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:"M8 5v14l11-7z"})}),_=({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"})}),N=({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"})}),S=({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"})}),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:"M7 9v6h4l5 5V4l-5 5H7z"})}),z=({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"})}),T=({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"})}),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:"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"})}),A=({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"})}),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:"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=({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"})}),L=({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"})}),I=({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"})}),q=({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"})}),B=({volume:r,muted:s,onVolumeChange:n,onToggleMute:l})=>{const a=t.useCallback(e=>{const t=parseFloat(e.target.value);n(t)},[n]);return e.jsxs("div",{className:"plex-video-player__volume-container",children:[e.jsx("button",{className:"plex-video-player__btn",onClick:l,"aria-label":s?"Unmute":"Mute",title:s?"Unmute (M)":"Mute (M)",type:"button",children:s||0===r?e.jsx(z,{}):r<.33?e.jsx(M,{}):r<.66?e.jsx(S,{}):e.jsx(N,{})}),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:s?0:r,onChange:a,"aria-label":"Volume",style:{background:`linear-gradient(to right, var(--plex-primary) 0%, var(--plex-primary) ${100*(s?0:r)}%, var(--plex-progress-bg) ${100*(s?0:r)}%, var(--plex-progress-bg) 100%)`}})})]})},H=({playbackRate:r,playbackSpeeds:s,onPlaybackRateChange:n,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=>{n(e),x("main")},[n]),w=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(R,{})}),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:s.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:()=>w(t.quality||t.src),type:"button",children:[e.jsx("span",{children:t.label||t.quality||"Unknown"}),i===t.quality&&e.jsx(q,{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(q,{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(q,{size:16})]},t.srclang))]})]})]})]})},D=({ad:t,timeRemaining:s,canSkip:n,onSkip:l,onClick:a})=>{const i=n?"Skip Ad":`Skip in ${Math.ceil(s)}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(s)," remaining"]})]}),t.clickThrough&&e.jsxs("button",{className:"plex-video-player__ad-learn-more",onClick:e=>{e.stopPropagation(),a()},type:"button",children:[e.jsx(I,{size:16}),"Learn More"]}),e.jsx("button",{className:"plex-video-player__ad-skip",onClick:e=>{e.stopPropagation(),n&&l()},disabled:!n,type:"button",children:i})]})},$={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."},O=({error:t,onRetry:r})=>{const s=t?.code||0,n=$[s]||"An unknown error occurred.";return e.jsxs("div",{className:"plex-video-player__error",children:[e.jsx(L,{className:"plex-video-player__error-icon",size:60}),e.jsx("div",{className:"plex-video-player__error-message",children:n}),s>0&&e.jsxs("div",{className:"plex-video-player__error-code",children:["Error Code: ",s]}),e.jsx("button",{className:"plex-video-player__error-retry",onClick:r,type:"button",children:"Try Again"})]})},Q=({visible:t})=>t?e.jsx("div",{className:"plex-video-player__loader",children:e.jsx("div",{className:"plex-video-player__loader-spinner"})}):null,U=[.25,.5,.75,1,1.25,1.5,1.75,2],X=3e3,K=t.forwardRef((s,i)=>{const{src:c,poster:o,autoPlay:d=!1,muted:p=!1,loop:h=!1,preload:x="metadata",width:g="100%",height:y="auto",controls:f=!0,pip:w=!0,fullscreen:k=!0,playbackSpeed:b=!0,playbackSpeeds:P=U,volume:N=!0,initialVolume:S=1,progressBar:M=!0,timeDisplay:z=!0,qualitySelector:R=!0,textTracks:L,vast:I,keyboard:q=!0,hotkeys:$,className:K="",style:G,accentColor:J,theme:W="dark",controlsTimeout:Y=X,doubleClickFullscreen:Z=!0,clickToPlay:ee=!0,thumbnailPreview:te,onPlay:re,onPause:se,onEnded:ne,onTimeUpdate:le,onProgress:ae,onVolumeChange:ie,onSeeking:ce,onSeeked:oe,onRateChange:ue,onQualityChange:de,onFullscreenChange:pe,onPipChange:me,onError:he,onReady:ve,onAdStart:xe,onAdEnd:ge,onAdSkip:ye,onAdError:fe}=s,we=t.useRef(null),ke=t.useRef(null),be=t.useRef(null),[,je]=t.useState(!1),[Ce,Pe]=t.useState(!!o&&!d),[Ee,_e]=t.useState(!0),[Ne,Se]=t.useState(null),[Me,ze]=t.useState(null),{state:Te,play:Fe,pause:Ae,togglePlay:Ve,seek:Re,setVolume:Le,toggleMute:Ie,setPlaybackRate:qe,enterFullscreen:Be,exitFullscreen:He,toggleFullscreen:De,enterPip:$e,exitPip:Oe,togglePip:Qe}=m({autoPlay:d,muted:p,loop:h,volume:S,playbackRate:1}),Ue=j({vastConfig:I,videoRef:we,onAdStart:xe,onAdEnd:ge,onAdSkip:ye,onAdError:fe}),Xe=t.useMemo(()=>"string"==typeof c?[{src:c,type:u(c)}]:c.map(e=>({...e,type:e.type||u(e.src)})),[c]),Ke=t.useMemo(()=>Ne&&Xe.find(e=>e.quality===Ne)||Xe[0],[Xe,Ne]),Ge=t.useCallback(()=>{Te.isPlaying&&!Ue.isAdPlaying&&_e(!1)},[Te.isPlaying,Ue.isAdPlaying]),Je=t.useCallback(()=>{_e(!0),be.current&&clearTimeout(be.current),be.current=window.setTimeout(Ge,Y)},[Ge,Y]),We=t.useCallback(()=>{ee&&!Ue.isAdPlaying&&Ve(),Je()},[ee,Ue.isAdPlaying,Ve,Je]),Ye=t.useCallback(()=>{Z&&!Ue.isAdPlaying&&De()},[Z,Ue.isAdPlaying,De]),Ze=t.useCallback(()=>{Je()},[Je]),et=t.useCallback(()=>{Te.isPlaying&&Ge()},[Te.isPlaying,Ge]),tt=t.useCallback(e=>{Re(e),ce?.(e)},[Re,ce]),rt=t.useCallback(e=>{const t=Xe.find(t=>t.quality===e);if(t&&we.current){const r=we.current.currentTime,s=!we.current.paused;Se(e),we.current.src=t.src,we.current.currentTime=r,s&&we.current.play(),de?.(e)}},[Xe,de]),st=t.useCallback(e=>{if(ze(e),we.current){const t=we.current.textTracks;for(let r=0;r<t.length;r++)t[r].mode=t[r].language===e?"showing":"hidden"}},[]),nt=t.useCallback(()=>{we.current&&(we.current.load(),Fe())},[Fe]),lt=t.useCallback(()=>{Pe(!1),Fe()},[Fe]);v({enabled:q,hotkeys:$,onPlay:Ve,onMute:Ie,onFullscreen:De,onPip:Qe,onSeek:e=>{if(we.current){const t=n(we.current.currentTime+e,0,we.current.duration);Re(t)}},onVolume:e=>{Le(n(Te.volume+e,0,1))},containerRef:ke}),t.useEffect(()=>{const e=we.current;if(!e)return;const t={loadedmetadata:()=>{je(!0),ve?.()},play:()=>{Pe(!1),re?.()},pause:()=>se?.(),ended:()=>ne?.(),timeupdate:()=>{le?.(e.currentTime),Ue.checkForAd(e.currentTime,e.duration)},progress:()=>{e.buffered.length>0&&ae?.(e.buffered.end(e.buffered.length-1))},volumechange:()=>{ie?.(e.volume,e.muted)},seeking:()=>ce?.(e.currentTime),seeked:()=>oe?.(e.currentTime),ratechange:()=>ue?.(e.playbackRate),error:()=>he?.(e.error),enterpictureinpicture:()=>me?.(!0),leavepictureinpicture:()=>me?.(!1)};return Object.entries(t).forEach(([t,r])=>{e.addEventListener(t,r)}),()=>{Object.entries(t).forEach(([t,r])=>{e.removeEventListener(t,r)})}},[re,se,ne,le,ae,ie,ce,oe,ue,he,ve,me,Ue]),t.useEffect(()=>{const e=()=>{const e=!!document.fullscreenElement;pe?.(e)};return document.addEventListener("fullscreenchange",e),()=>{document.removeEventListener("fullscreenchange",e)}},[pe]),t.useEffect(()=>()=>{be.current&&clearTimeout(be.current)},[]),t.useImperativeHandle(i,()=>({play:async()=>{await Fe()},pause:()=>{Ae()},stop:()=>{Ae(),we.current&&(we.current.currentTime=0)},seek:e=>{Re(e)},setVolume:e=>{Le(e)},mute:()=>{we.current&&(we.current.muted=!0)},unmute:()=>{we.current&&(we.current.muted=!1)},toggleMute:()=>{Ie()},enterFullscreen:async()=>{await Be()},exitFullscreen:async()=>{await He()},toggleFullscreen:async()=>{await De()},enterPip:async()=>{await $e()},exitPip:async()=>{await Oe()},togglePip:async()=>{await Qe()},setPlaybackRate:e=>{qe(e)},setQuality:e=>{rt(e)},getCurrentTime:()=>we.current?.currentTime||0,getDuration:()=>we.current?.duration||0,getVolume:()=>we.current?.volume||0,isMuted:()=>we.current?.muted||!1,isPlaying:()=>!we.current?.paused,isFullscreen:()=>Te.isFullscreen,isPip:()=>Te.isPip,getVideoElement:()=>we.current}));const at=t.useMemo(()=>{const e={width:g,height:y,...G};return J&&(e["--plex-primary"]=J),e},[g,y,G,J]),it=["plex-video-player",`plex-video-player--theme-${W}`,Te.isFullscreen&&"plex-video-player--fullscreen",Ee&&"plex-video-player--controls-visible",Ue.isAdPlaying&&"plex-video-player--ad-playing",K].filter(Boolean).join(" ");return e.jsxs("div",{ref:ke,className:it,style:at,onMouseMove:Ze,onMouseLeave:et,tabIndex:0,children:[e.jsxs("video",{ref:we,className:"plex-video-player__video",src:Ke?.src,poster:Ce?void 0:o,preload:x,loop:h,muted:p,playsInline:!0,onClick:We,onDoubleClick:Ye,children:[Xe.map((t,r)=>e.jsx("source",{src:t.src,type:t.type},r)),L?.map((t,r)=>e.jsx("track",{src:t.src,kind:t.kind,srcLang:t.srclang,label:t.label,default:t.default},r))]}),Ce&&o&&e.jsx("div",{className:"plex-video-player__poster",style:{backgroundImage:`url(${o})`},onClick:lt}),e.jsx(Q,{visible:Te.isBuffering&&!Ce}),Te.error&&e.jsx(O,{error:Te.error,onRetry:nt}),Ue.isAdPlaying&&Ue.currentAd&&e.jsx(D,{ad:Ue.currentAd,timeRemaining:Ue.adTimeRemaining,canSkip:Ue.canSkip,onSkip:Ue.skipAd,onClick:Ue.handleAdClick}),f&&!Ce&&!Te.error&&e.jsxs("div",{className:"plex-video-player__controls",children:[M&&e.jsx(C,{currentTime:Te.currentTime,duration:Te.duration,buffered:Te.buffered,onSeek:tt,thumbnailPreview:te,disabled:Ue.isAdPlaying}),e.jsxs("div",{className:"plex-video-player__controls-row",children:[e.jsxs("div",{className:"plex-video-player__controls-left",children:[e.jsx("button",{className:"plex-video-player__btn plex-video-player__btn--play",onClick:Ve,"aria-label":Te.isPlaying?"Pause":"Play",title:Te.isPlaying?"Pause (Space)":"Play (Space)",disabled:Ue.isAdPlaying,type:"button",children:Te.isPlaying?e.jsx(_,{}):e.jsx(E,{})}),N&&e.jsx(B,{volume:Te.volume,muted:Te.isMuted,onVolumeChange:Le,onToggleMute:Ie}),z&&e.jsxs("div",{className:"plex-video-player__time",children:[e.jsx("span",{children:r(Te.currentTime)}),e.jsx("span",{className:"plex-video-player__time-separator",children:"/"}),e.jsx("span",{children:r(Te.duration)})]})]}),e.jsxs("div",{className:"plex-video-player__controls-right",children:[(b||R)&&e.jsx(H,{playbackRate:Te.playbackRate,playbackSpeeds:P,onPlaybackRateChange:qe,qualityEnabled:R,sources:Xe.length>1?Xe:void 0,currentQuality:Ne||void 0,onQualityChange:rt,captionsEnabled:!!L&&L.length>0,textTracks:L,currentTrack:Me||void 0,onTrackChange:st}),w&&a()&&e.jsx("button",{className:"plex-video-player__btn",onClick:Qe,"aria-label":Te.isPip?"Exit Picture-in-Picture":"Picture-in-Picture",title:Te.isPip?"Exit PiP (P)":"Picture-in-Picture (P)",disabled:Ue.isAdPlaying,type:"button",children:Te.isPip?e.jsx(V,{}):e.jsx(A,{})}),k&&l()&&e.jsx("button",{className:"plex-video-player__btn",onClick:De,"aria-label":Te.isFullscreen?"Exit Fullscreen":"Fullscreen",title:Te.isFullscreen?"Exit Fullscreen (F)":"Fullscreen (F)",type:"button",children:Te.isFullscreen?e.jsx(F,{}):e.jsx(T,{})})]})]})]})]})});K.displayName="PlexVideoPlayer",exports.AdOverlay=D,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=q,exports.ErrorDisplay=O,exports.ErrorIcon=L,exports.ExternalLinkIcon=I,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=F,exports.FullscreenIcon=T,exports.Loader=Q,exports.PauseIcon=_,exports.PipExitIcon=V,exports.PipIcon=A,exports.PlayIcon=E,exports.PlexVideoPlayer=K,exports.ProgressBar=C,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=R,exports.SettingsMenu=H,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=B,exports.VolumeHighIcon=N,exports.VolumeLowIcon=M,exports.VolumeMediumIcon=S,exports.VolumeMuteIcon=z,exports.canPlayType=e=>{const t=document.createElement("video").canPlayType(e);return"probably"===t||"maybe"===t},exports.clamp=n,exports.convertToAdInfo=b,exports.debounce=(e,t)=>{let r=null;return(...s)=>{r&&clearTimeout(r),r=setTimeout(()=>e(...s),t)}},exports.default=K,exports.detectVideoType=u,exports.exitFullscreen=o,exports.fetchVastAd=y,exports.fireTrackingPixel=w,exports.fireTrackingPixels=k,exports.formatTime=r,exports.generateId=()=>`plex-${Math.random().toString(36).substring(2,11)}`,exports.getBufferedEnd=d,exports.getFullscreenElement=i,exports.isFullscreenSupported=l,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=x,exports.percentage=s,exports.requestFullscreen=c,exports.selectBestMediaFile=f,exports.throttle=(e,t)=>{let r=!1;return(...s)=>{r||(e(...s),r=!0,setTimeout(()=>r=!1,t))}},exports.useKeyboard=v,exports.usePlayer=m,exports.useVast=j;
2
+ //# sourceMappingURL=index.js.map