@frameset/plex-player 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.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","percentage","value","total","min","max","clamp","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","split","pop","toLowerCase","mp4","webm","ogg","ogv","m3u8","mpd","mov","avi","mkv","getBufferedEnd","video","buffered","length","currentTime","i","start","end","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","time","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","type","width","parseInt","height","bitrate","videoClicks","ct","tracking","impressionUrls","impression","includes","parts","parseFloat","fetchVastAd","config","response","fetch","method","headers","Accept","ok","text","result","ad","skipDelay","selectBestMediaFile","createElement","supportedFiles","filter","file","canPlayType","sort","a","b","resA","resB","test","navigator","userAgent","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","window","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","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","map","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","clearTimeout","setTimeout","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","func","wait","timeout","args","random","substring","maxTouchPoints","Number","limit","inThrottle"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/utils/helpers.ts","../src/components/ProgressBar.tsx","../src/components/Icons.tsx","../src/components/VolumeControl.tsx","../src/components/SettingsMenu.tsx","../src/components/AdOverlay.tsx","../src/components/ErrorDisplay.tsx","../src/components/Loader.tsx","../src/hooks/useKeyboard.ts","../src/utils/vast.ts","../src/hooks/useVast.ts","../src/components/PlexVideoPlayer.tsx","../src/hooks/usePlayer.ts"],"sourcesContent":["// Utility functions for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\n/**\r\n * Format time in seconds to MM:SS or HH:MM:SS format\r\n */\r\nexport const formatTime = (seconds: number): string => {\r\n if (!isFinite(seconds) || isNaN(seconds)) return '0:00';\r\n\r\n const hrs = Math.floor(seconds / 3600);\r\n const mins = Math.floor((seconds % 3600) / 60);\r\n const secs = Math.floor(seconds % 60);\r\n\r\n if (hrs > 0) {\r\n return `${hrs}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\r\n }\r\n\r\n return `${mins}:${secs.toString().padStart(2, '0')}`;\r\n};\r\n\r\n/**\r\n * Parse time string to seconds\r\n */\r\nexport const parseTime = (time: string): number => {\r\n const parts = time.split(':').map(Number);\r\n if (parts.length === 3) {\r\n return parts[0] * 3600 + parts[1] * 60 + parts[2];\r\n }\r\n if (parts.length === 2) {\r\n return parts[0] * 60 + parts[1];\r\n }\r\n return parts[0] || 0;\r\n};\r\n\r\n/**\r\n * Calculate percentage\r\n */\r\nexport const percentage = (value: number, total: number): number => {\r\n if (total === 0) return 0;\r\n return Math.min(100, Math.max(0, (value / total) * 100));\r\n};\r\n\r\n/**\r\n * Clamp a value between min and max\r\n */\r\nexport const clamp = (value: number, min: number, max: number): number => {\r\n return Math.min(max, Math.max(min, value));\r\n};\r\n\r\n/**\r\n * Throttle function execution\r\n */\r\nexport const throttle = <T extends (...args: unknown[]) => unknown>(\r\n func: T,\r\n limit: number\r\n): ((...args: Parameters<T>) => void) => {\r\n let inThrottle = false;\r\n return (...args: Parameters<T>) => {\r\n if (!inThrottle) {\r\n func(...args);\r\n inThrottle = true;\r\n setTimeout(() => (inThrottle = false), limit);\r\n }\r\n };\r\n};\r\n\r\n/**\r\n * Debounce function execution\r\n */\r\nexport const debounce = <T extends (...args: unknown[]) => unknown>(\r\n func: T,\r\n wait: number\r\n): ((...args: Parameters<T>) => void) => {\r\n let timeout: ReturnType<typeof setTimeout> | null = null;\r\n return (...args: Parameters<T>) => {\r\n if (timeout) clearTimeout(timeout);\r\n timeout = setTimeout(() => func(...args), wait);\r\n };\r\n};\r\n\r\n/**\r\n * Check if fullscreen is supported\r\n */\r\nexport const isFullscreenSupported = (): boolean => {\r\n return !!(\r\n document.fullscreenEnabled ||\r\n (document as Document & { webkitFullscreenEnabled?: boolean }).webkitFullscreenEnabled ||\r\n (document as Document & { mozFullScreenEnabled?: boolean }).mozFullScreenEnabled ||\r\n (document as Document & { msFullscreenEnabled?: boolean }).msFullscreenEnabled\r\n );\r\n};\r\n\r\n/**\r\n * Check if Picture-in-Picture is supported\r\n */\r\nexport const isPipSupported = (): boolean => {\r\n return 'pictureInPictureEnabled' in document && document.pictureInPictureEnabled;\r\n};\r\n\r\n/**\r\n * Get current fullscreen element\r\n */\r\nexport const getFullscreenElement = (): Element | null => {\r\n return (\r\n document.fullscreenElement ||\r\n (document as Document & { webkitFullscreenElement?: Element }).webkitFullscreenElement ||\r\n (document as Document & { mozFullScreenElement?: Element }).mozFullScreenElement ||\r\n (document as Document & { msFullscreenElement?: Element }).msFullscreenElement ||\r\n null\r\n );\r\n};\r\n\r\n/**\r\n * Request fullscreen on element\r\n */\r\nexport const requestFullscreen = async (element: HTMLElement): Promise<void> => {\r\n if (element.requestFullscreen) {\r\n await element.requestFullscreen();\r\n } else if ((element as HTMLElement & { webkitRequestFullscreen?: () => Promise<void> }).webkitRequestFullscreen) {\r\n await (element as HTMLElement & { webkitRequestFullscreen: () => Promise<void> }).webkitRequestFullscreen();\r\n } else if ((element as HTMLElement & { mozRequestFullScreen?: () => Promise<void> }).mozRequestFullScreen) {\r\n await (element as HTMLElement & { mozRequestFullScreen: () => Promise<void> }).mozRequestFullScreen();\r\n } else if ((element as HTMLElement & { msRequestFullscreen?: () => Promise<void> }).msRequestFullscreen) {\r\n await (element as HTMLElement & { msRequestFullscreen: () => Promise<void> }).msRequestFullscreen();\r\n }\r\n};\r\n\r\n/**\r\n * Exit fullscreen\r\n */\r\nexport const exitFullscreen = async (): Promise<void> => {\r\n if (document.exitFullscreen) {\r\n await document.exitFullscreen();\r\n } else if ((document as Document & { webkitExitFullscreen?: () => Promise<void> }).webkitExitFullscreen) {\r\n await (document as Document & { webkitExitFullscreen: () => Promise<void> }).webkitExitFullscreen();\r\n } else if ((document as Document & { mozCancelFullScreen?: () => Promise<void> }).mozCancelFullScreen) {\r\n await (document as Document & { mozCancelFullScreen: () => Promise<void> }).mozCancelFullScreen();\r\n } else if ((document as Document & { msExitFullscreen?: () => Promise<void> }).msExitFullscreen) {\r\n await (document as Document & { msExitFullscreen: () => Promise<void> }).msExitFullscreen();\r\n }\r\n};\r\n\r\n/**\r\n * Detect video type from URL\r\n */\r\nexport const detectVideoType = (url: string): string => {\r\n const extension = url.split('?')[0].split('.').pop()?.toLowerCase();\r\n \r\n const mimeTypes: Record<string, string> = {\r\n mp4: 'video/mp4',\r\n webm: 'video/webm',\r\n ogg: 'video/ogg',\r\n ogv: 'video/ogg',\r\n m3u8: 'application/x-mpegURL',\r\n mpd: 'application/dash+xml',\r\n mov: 'video/quicktime',\r\n avi: 'video/x-msvideo',\r\n mkv: 'video/x-matroska',\r\n };\r\n\r\n return mimeTypes[extension || ''] || 'video/mp4';\r\n};\r\n\r\n/**\r\n * Check if HLS is natively supported\r\n */\r\nexport const isHlsNativelySupported = (): boolean => {\r\n const video = document.createElement('video');\r\n return video.canPlayType('application/vnd.apple.mpegurl') !== '';\r\n};\r\n\r\n/**\r\n * Generate unique ID\r\n */\r\nexport const generateId = (): string => {\r\n return `plex-${Math.random().toString(36).substring(2, 11)}`;\r\n};\r\n\r\n/**\r\n * Check if device is mobile\r\n */\r\nexport const isMobile = (): boolean => {\r\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(\r\n navigator.userAgent\r\n );\r\n};\r\n\r\n/**\r\n * Check if device is touch-enabled\r\n */\r\nexport const isTouchDevice = (): boolean => {\r\n return 'ontouchstart' in window || navigator.maxTouchPoints > 0;\r\n};\r\n\r\n/**\r\n * Parse buffered time ranges\r\n */\r\nexport const getBufferedEnd = (video: HTMLVideoElement): number => {\r\n if (video.buffered.length === 0) return 0;\r\n \r\n const currentTime = video.currentTime;\r\n for (let i = 0; i < video.buffered.length; i++) {\r\n if (video.buffered.start(i) <= currentTime && video.buffered.end(i) >= currentTime) {\r\n return video.buffered.end(i);\r\n }\r\n }\r\n \r\n return video.buffered.end(video.buffered.length - 1);\r\n};\r\n\r\n/**\r\n * Check browser support for specific video format\r\n */\r\nexport const canPlayType = (type: string): boolean => {\r\n const video = document.createElement('video');\r\n const result = video.canPlayType(type);\r\n return result === 'probably' || result === 'maybe';\r\n};\r\n","// Progress Bar Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React, { useRef, useState, useCallback, useEffect } from 'react';\r\nimport { formatTime, percentage, clamp } from '../utils/helpers';\r\n\r\ninterface ProgressBarProps {\r\n currentTime: number;\r\n duration: number;\r\n buffered: number;\r\n onSeek: (time: number) => void;\r\n thumbnailPreview?: {\r\n enabled: boolean;\r\n sprites?: string;\r\n interval?: number;\r\n width?: number;\r\n height?: number;\r\n };\r\n disabled?: boolean;\r\n}\r\n\r\nexport const ProgressBar: React.FC<ProgressBarProps> = ({\r\n currentTime,\r\n duration,\r\n buffered,\r\n onSeek,\r\n thumbnailPreview,\r\n disabled = false,\r\n}) => {\r\n const progressRef = useRef<HTMLDivElement>(null);\r\n const [isDragging, setIsDragging] = useState(false);\r\n const [hoverTime, setHoverTime] = useState<number | null>(null);\r\n const [hoverPosition, setHoverPosition] = useState(0);\r\n\r\n const playedPercent = percentage(currentTime, duration);\r\n const bufferedPercent = percentage(buffered, duration);\r\n\r\n const getTimeFromPosition = useCallback(\r\n (clientX: number): number => {\r\n if (!progressRef.current) return 0;\r\n const rect = progressRef.current.getBoundingClientRect();\r\n const percent = clamp((clientX - rect.left) / rect.width, 0, 1);\r\n return percent * duration;\r\n },\r\n [duration]\r\n );\r\n\r\n const handleMouseMove = useCallback(\r\n (e: MouseEvent | React.MouseEvent) => {\r\n if (!progressRef.current) return;\r\n const rect = progressRef.current.getBoundingClientRect();\r\n const clientX = 'clientX' in e ? e.clientX : 0;\r\n const position = clamp(clientX - rect.left, 0, rect.width);\r\n const time = getTimeFromPosition(clientX);\r\n\r\n setHoverPosition(position);\r\n setHoverTime(time);\r\n\r\n if (isDragging) {\r\n onSeek(time);\r\n }\r\n },\r\n [isDragging, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleMouseDown = useCallback(\r\n (e: React.MouseEvent) => {\r\n if (disabled) return;\r\n e.preventDefault();\r\n setIsDragging(true);\r\n const time = getTimeFromPosition(e.clientX);\r\n onSeek(time);\r\n },\r\n [disabled, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleMouseUp = useCallback(() => {\r\n setIsDragging(false);\r\n }, []);\r\n\r\n const handleMouseEnter = useCallback(\r\n (e: React.MouseEvent) => {\r\n const time = getTimeFromPosition(e.clientX);\r\n setHoverTime(time);\r\n },\r\n [getTimeFromPosition]\r\n );\r\n\r\n const handleMouseLeave = useCallback(() => {\r\n setHoverTime(null);\r\n }, []);\r\n\r\n // Global mouse events for dragging\r\n useEffect(() => {\r\n if (isDragging) {\r\n const mouseMoveHandler = (e: MouseEvent) => handleMouseMove(e);\r\n window.addEventListener('mousemove', mouseMoveHandler);\r\n window.addEventListener('mouseup', handleMouseUp);\r\n\r\n return () => {\r\n window.removeEventListener('mousemove', mouseMoveHandler);\r\n window.removeEventListener('mouseup', handleMouseUp);\r\n };\r\n }\r\n return undefined;\r\n }, [isDragging, handleMouseMove, handleMouseUp]);\r\n\r\n // Touch support\r\n const handleTouchStart = useCallback(\r\n (e: React.TouchEvent) => {\r\n if (disabled) return;\r\n e.preventDefault();\r\n const touch = e.touches[0];\r\n const time = getTimeFromPosition(touch.clientX);\r\n setIsDragging(true);\r\n onSeek(time);\r\n },\r\n [disabled, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleTouchMove = useCallback(\r\n (e: React.TouchEvent) => {\r\n if (!isDragging) return;\r\n const touch = e.touches[0];\r\n const time = getTimeFromPosition(touch.clientX);\r\n onSeek(time);\r\n },\r\n [isDragging, getTimeFromPosition, onSeek]\r\n );\r\n\r\n const handleTouchEnd = useCallback(() => {\r\n setIsDragging(false);\r\n }, []);\r\n\r\n return (\r\n <div\r\n ref={progressRef}\r\n className=\"plex-video-player__progress-container\"\r\n onMouseDown={handleMouseDown}\r\n onMouseEnter={handleMouseEnter}\r\n onMouseLeave={handleMouseLeave}\r\n onMouseMove={(e) => handleMouseMove(e.nativeEvent)}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n role=\"slider\"\r\n aria-label=\"Video progress\"\r\n aria-valuemin={0}\r\n aria-valuemax={duration}\r\n aria-valuenow={currentTime}\r\n aria-valuetext={formatTime(currentTime)}\r\n tabIndex={0}\r\n >\r\n <div className=\"plex-video-player__progress\">\r\n <div\r\n className=\"plex-video-player__progress-buffered\"\r\n style={{ width: `${bufferedPercent}%` }}\r\n />\r\n <div\r\n className=\"plex-video-player__progress-played\"\r\n style={{ width: `${playedPercent}%` }}\r\n />\r\n <div\r\n className=\"plex-video-player__progress-handle\"\r\n style={{ left: `${playedPercent}%` }}\r\n />\r\n </div>\r\n\r\n {/* Thumbnail Preview */}\r\n {thumbnailPreview?.enabled && hoverTime !== null && (\r\n <div\r\n className=\"plex-video-player__thumbnail-preview\"\r\n style={{\r\n left: `${hoverPosition}px`,\r\n width: thumbnailPreview.width || 160,\r\n height: thumbnailPreview.height || 90,\r\n }}\r\n >\r\n {thumbnailPreview.sprites && (\r\n <div\r\n style={{\r\n width: '100%',\r\n height: '100%',\r\n backgroundImage: `url(${thumbnailPreview.sprites})`,\r\n backgroundPosition: calculateSpritePosition(\r\n hoverTime,\r\n duration,\r\n thumbnailPreview.interval || 10,\r\n thumbnailPreview.width || 160,\r\n thumbnailPreview.height || 90\r\n ),\r\n backgroundSize: 'cover',\r\n }}\r\n />\r\n )}\r\n <div className=\"plex-video-player__thumbnail-time\">\r\n {formatTime(hoverTime)}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Time tooltip */}\r\n {hoverTime !== null && !thumbnailPreview?.enabled && (\r\n <div\r\n className=\"plex-video-player__thumbnail-preview\"\r\n style={{\r\n left: `${hoverPosition}px`,\r\n width: 'auto',\r\n height: 'auto',\r\n padding: '4px 8px',\r\n }}\r\n >\r\n <span style={{ color: 'white', fontSize: '12px' }}>\r\n {formatTime(hoverTime)}\r\n </span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\n// Calculate sprite background position for thumbnail preview\r\nconst calculateSpritePosition = (\r\n time: number,\r\n _duration: number,\r\n interval: number,\r\n width: number,\r\n height: number\r\n): string => {\r\n const index = Math.floor(time / interval);\r\n const columns = 10; // Assuming 10 columns per row in sprite\r\n const row = Math.floor(index / columns);\r\n const col = index % columns;\r\n\r\n return `-${col * width}px -${row * height}px`;\r\n};\r\n\r\nexport default ProgressBar;\r\n","// Icon components for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\n\r\ninterface IconProps {\r\n className?: string;\r\n size?: number;\r\n}\r\n\r\nexport const PlayIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n);\r\n\r\nexport const PauseIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M6 19h4V5H6v14zm8-14v14h4V5h-4z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeHighIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeMediumIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeLowIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M7 9v6h4l5 5V4l-5 5H7z\" />\r\n </svg>\r\n);\r\n\r\nexport const VolumeMuteIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\" />\r\n </svg>\r\n);\r\n\r\nexport const FullscreenIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\" />\r\n </svg>\r\n);\r\n\r\nexport const FullscreenExitIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\" />\r\n </svg>\r\n);\r\n\r\nexport const PipIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z\" />\r\n </svg>\r\n);\r\n\r\nexport const PipExitIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zM5 7h8v6H5z\" />\r\n </svg>\r\n);\r\n\r\nexport const SettingsIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z\" />\r\n </svg>\r\n);\r\n\r\nexport const CaptionsIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z\" />\r\n </svg>\r\n);\r\n\r\nexport const SpeedIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M20.38 8.57l-1.23 1.85a8 8 0 0 1-.22 7.58H5.07A8 8 0 0 1 15.58 6.85l1.85-1.23A10 10 0 0 0 3.35 19a2 2 0 0 0 1.72 1h13.85a2 2 0 0 0 1.74-1 10 10 0 0 0-.27-10.44zm-9.79 6.84a2 2 0 0 0 2.83 0l5.66-8.49-8.49 5.66a2 2 0 0 0 0 2.83z\" />\r\n </svg>\r\n);\r\n\r\nexport const QualityIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zM7.5 13h2v2H7.5zm4.5 0h2v2h-2zm4.5 0h2v2h-2zM7.5 9h2v2H7.5zm4.5 0h2v2h-2zm4.5 0h2v2h-2z\" />\r\n </svg>\r\n);\r\n\r\nexport const ForwardIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z\" />\r\n </svg>\r\n);\r\n\r\nexport const RewindIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z\" />\r\n </svg>\r\n);\r\n\r\nexport const SkipNextIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z\" />\r\n </svg>\r\n);\r\n\r\nexport const SkipPrevIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M6 6h2v12H6zm3.5 6l8.5 6V6z\" />\r\n </svg>\r\n);\r\n\r\nexport const ErrorIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\" />\r\n </svg>\r\n);\r\n\r\nexport const ExternalLinkIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z\" />\r\n </svg>\r\n);\r\n\r\nexport const CheckIcon: React.FC<IconProps> = ({ className, size = 24 }) => (\r\n <svg\r\n className={className}\r\n width={size}\r\n height={size}\r\n viewBox=\"0 0 24 24\"\r\n fill=\"currentColor\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\" />\r\n </svg>\r\n);\r\n","// Volume Control Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React, { useCallback } from 'react';\r\nimport {\r\n VolumeHighIcon,\r\n VolumeMediumIcon,\r\n VolumeLowIcon,\r\n VolumeMuteIcon,\r\n} from './Icons';\r\n\r\ninterface VolumeControlProps {\r\n volume: number;\r\n muted: boolean;\r\n onVolumeChange: (volume: number) => void;\r\n onToggleMute: () => void;\r\n}\r\n\r\nexport const VolumeControl: React.FC<VolumeControlProps> = ({\r\n volume,\r\n muted,\r\n onVolumeChange,\r\n onToggleMute,\r\n}) => {\r\n const handleSliderChange = useCallback(\r\n (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const newVolume = parseFloat(e.target.value);\r\n onVolumeChange(newVolume);\r\n },\r\n [onVolumeChange]\r\n );\r\n\r\n const getVolumeIcon = () => {\r\n if (muted || volume === 0) {\r\n return <VolumeMuteIcon />;\r\n }\r\n if (volume < 0.33) {\r\n return <VolumeLowIcon />;\r\n }\r\n if (volume < 0.66) {\r\n return <VolumeMediumIcon />;\r\n }\r\n return <VolumeHighIcon />;\r\n };\r\n\r\n return (\r\n <div className=\"plex-video-player__volume-container\">\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={onToggleMute}\r\n aria-label={muted ? 'Unmute' : 'Mute'}\r\n title={muted ? 'Unmute (M)' : 'Mute (M)'}\r\n type=\"button\"\r\n >\r\n {getVolumeIcon()}\r\n </button>\r\n <div className=\"plex-video-player__volume-slider-container\">\r\n <input\r\n type=\"range\"\r\n className=\"plex-video-player__volume-slider\"\r\n min={0}\r\n max={1}\r\n step={0.01}\r\n value={muted ? 0 : volume}\r\n onChange={handleSliderChange}\r\n aria-label=\"Volume\"\r\n style={{\r\n background: `linear-gradient(to right, var(--plex-primary) 0%, var(--plex-primary) ${\r\n (muted ? 0 : volume) * 100\r\n }%, var(--plex-progress-bg) ${(muted ? 0 : volume) * 100}%, var(--plex-progress-bg) 100%)`,\r\n }}\r\n />\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\nexport default VolumeControl;\r\n","// Settings Menu Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React, { useState, useRef, useEffect, useCallback } from 'react';\r\nimport { SettingsIcon, CheckIcon } from './Icons';\r\nimport { VideoSource, TextTrack } from '../types';\r\n\r\ninterface SettingsMenuProps {\r\n playbackRate: number;\r\n playbackSpeeds: number[];\r\n onPlaybackRateChange: (rate: number) => void;\r\n qualityEnabled: boolean;\r\n sources?: VideoSource[];\r\n currentQuality?: string;\r\n onQualityChange?: (quality: string) => void;\r\n captionsEnabled?: boolean;\r\n textTracks?: TextTrack[];\r\n currentTrack?: string;\r\n onTrackChange?: (track: string | null) => void;\r\n}\r\n\r\ntype MenuView = 'main' | 'speed' | 'quality' | 'captions';\r\n\r\nexport const SettingsMenu: React.FC<SettingsMenuProps> = ({\r\n playbackRate,\r\n playbackSpeeds,\r\n onPlaybackRateChange,\r\n qualityEnabled,\r\n sources,\r\n currentQuality,\r\n onQualityChange,\r\n captionsEnabled,\r\n textTracks,\r\n currentTrack,\r\n onTrackChange,\r\n}) => {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [currentView, setCurrentView] = useState<MenuView>('main');\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n // Close menu when clicking outside\r\n useEffect(() => {\r\n const handleClickOutside = (e: MouseEvent) => {\r\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\r\n setIsOpen(false);\r\n setCurrentView('main');\r\n }\r\n };\r\n\r\n if (isOpen) {\r\n document.addEventListener('mousedown', handleClickOutside);\r\n return () => document.removeEventListener('mousedown', handleClickOutside);\r\n }\r\n return undefined;\r\n }, [isOpen]);\r\n\r\n const handleToggle = useCallback(() => {\r\n setIsOpen((prev) => !prev);\r\n setCurrentView('main');\r\n }, []);\r\n\r\n const handleSpeedSelect = useCallback(\r\n (speed: number) => {\r\n onPlaybackRateChange(speed);\r\n setCurrentView('main');\r\n },\r\n [onPlaybackRateChange]\r\n );\r\n\r\n const handleQualitySelect = useCallback(\r\n (quality: string) => {\r\n onQualityChange?.(quality);\r\n setCurrentView('main');\r\n },\r\n [onQualityChange]\r\n );\r\n\r\n const handleTrackSelect = useCallback(\r\n (track: string | null) => {\r\n onTrackChange?.(track);\r\n setCurrentView('main');\r\n },\r\n [onTrackChange]\r\n );\r\n\r\n const renderMainMenu = () => (\r\n <>\r\n <div className=\"plex-video-player__settings-title\">Settings</div>\r\n <button\r\n className=\"plex-video-player__settings-item\"\r\n onClick={() => setCurrentView('speed')}\r\n type=\"button\"\r\n >\r\n <span>Playback Speed</span>\r\n <span>{playbackRate === 1 ? 'Normal' : `${playbackRate}x`}</span>\r\n </button>\r\n {qualityEnabled && sources && sources.length > 1 && (\r\n <button\r\n className=\"plex-video-player__settings-item\"\r\n onClick={() => setCurrentView('quality')}\r\n type=\"button\"\r\n >\r\n <span>Quality</span>\r\n <span>{currentQuality || 'Auto'}</span>\r\n </button>\r\n )}\r\n {captionsEnabled && textTracks && textTracks.length > 0 && (\r\n <button\r\n className=\"plex-video-player__settings-item\"\r\n onClick={() => setCurrentView('captions')}\r\n type=\"button\"\r\n >\r\n <span>Captions</span>\r\n <span>{currentTrack || 'Off'}</span>\r\n </button>\r\n )}\r\n </>\r\n );\r\n\r\n const renderSpeedMenu = () => (\r\n <>\r\n <button\r\n className=\"plex-video-player__settings-title\"\r\n onClick={() => setCurrentView('main')}\r\n style={{ cursor: 'pointer', border: 'none', background: 'transparent', width: '100%', textAlign: 'left' }}\r\n type=\"button\"\r\n >\r\n ← Playback Speed\r\n </button>\r\n <div className=\"plex-video-player__speed-menu\">\r\n {playbackSpeeds.map((speed) => (\r\n <button\r\n key={speed}\r\n className={`plex-video-player__speed-btn ${\r\n playbackRate === speed ? 'plex-video-player__speed-btn--active' : ''\r\n }`}\r\n onClick={() => handleSpeedSelect(speed)}\r\n type=\"button\"\r\n >\r\n {speed === 1 ? 'Normal' : `${speed}x`}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n );\r\n\r\n const renderQualityMenu = () => (\r\n <>\r\n <button\r\n className=\"plex-video-player__settings-title\"\r\n onClick={() => setCurrentView('main')}\r\n style={{ cursor: 'pointer', border: 'none', background: 'transparent', width: '100%', textAlign: 'left' }}\r\n type=\"button\"\r\n >\r\n ← Quality\r\n </button>\r\n <div className=\"plex-video-player__quality-menu\">\r\n {sources?.map((source) => (\r\n <button\r\n key={source.quality || source.src}\r\n className={`plex-video-player__settings-item ${\r\n currentQuality === source.quality ? 'plex-video-player__settings-item--active' : ''\r\n }`}\r\n onClick={() => handleQualitySelect(source.quality || source.src)}\r\n type=\"button\"\r\n >\r\n <span>{source.label || source.quality || 'Unknown'}</span>\r\n {currentQuality === source.quality && <CheckIcon size={16} />}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n );\r\n\r\n const renderCaptionsMenu = () => (\r\n <>\r\n <button\r\n className=\"plex-video-player__settings-title\"\r\n onClick={() => setCurrentView('main')}\r\n style={{ cursor: 'pointer', border: 'none', background: 'transparent', width: '100%', textAlign: 'left' }}\r\n type=\"button\"\r\n >\r\n ← Captions\r\n </button>\r\n <div className=\"plex-video-player__quality-menu\">\r\n <button\r\n className={`plex-video-player__settings-item ${\r\n !currentTrack ? 'plex-video-player__settings-item--active' : ''\r\n }`}\r\n onClick={() => handleTrackSelect(null)}\r\n type=\"button\"\r\n >\r\n <span>Off</span>\r\n {!currentTrack && <CheckIcon size={16} />}\r\n </button>\r\n {textTracks?.map((track) => (\r\n <button\r\n key={track.srclang}\r\n className={`plex-video-player__settings-item ${\r\n currentTrack === track.srclang ? 'plex-video-player__settings-item--active' : ''\r\n }`}\r\n onClick={() => handleTrackSelect(track.srclang)}\r\n type=\"button\"\r\n >\r\n <span>{track.label}</span>\r\n {currentTrack === track.srclang && <CheckIcon size={16} />}\r\n </button>\r\n ))}\r\n </div>\r\n </>\r\n );\r\n\r\n return (\r\n <div className=\"plex-video-player__settings-container\" ref={containerRef}>\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={handleToggle}\r\n aria-label=\"Settings\"\r\n aria-expanded={isOpen}\r\n title=\"Settings\"\r\n type=\"button\"\r\n >\r\n <SettingsIcon />\r\n </button>\r\n {isOpen && (\r\n <div\r\n className={`plex-video-player__settings-menu ${\r\n isOpen ? 'plex-video-player__settings-menu--open' : ''\r\n }`}\r\n >\r\n {currentView === 'main' && renderMainMenu()}\r\n {currentView === 'speed' && renderSpeedMenu()}\r\n {currentView === 'quality' && renderQualityMenu()}\r\n {currentView === 'captions' && renderCaptionsMenu()}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\nexport default SettingsMenu;\r\n","// Ad Overlay Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\nimport { VastAdInfo } from '../types';\r\nimport { formatTime } from '../utils/helpers';\r\nimport { ExternalLinkIcon } from './Icons';\r\n\r\ninterface AdOverlayProps {\r\n ad: VastAdInfo;\r\n timeRemaining: number;\r\n canSkip: boolean;\r\n onSkip: () => void;\r\n onClick: () => void;\r\n}\r\n\r\nexport const AdOverlay: React.FC<AdOverlayProps> = ({\r\n ad,\r\n timeRemaining,\r\n canSkip,\r\n onSkip,\r\n onClick,\r\n}) => {\r\n const skipText = canSkip\r\n ? 'Skip Ad'\r\n : `Skip in ${Math.ceil(timeRemaining)}s`;\r\n\r\n return (\r\n <div className=\"plex-video-player__ad-overlay\" onClick={onClick}>\r\n {/* Ad Info Badge */}\r\n <div className=\"plex-video-player__ad-info\">\r\n <span className=\"plex-video-player__ad-badge\">Ad</span>\r\n <span>{formatTime(timeRemaining)} remaining</span>\r\n </div>\r\n\r\n {/* Learn More Button */}\r\n {ad.clickThrough && (\r\n <button\r\n className=\"plex-video-player__ad-learn-more\"\r\n onClick={(e) => {\r\n e.stopPropagation();\r\n onClick();\r\n }}\r\n type=\"button\"\r\n >\r\n <ExternalLinkIcon size={16} />\r\n Learn More\r\n </button>\r\n )}\r\n\r\n {/* Skip Button */}\r\n <button\r\n className=\"plex-video-player__ad-skip\"\r\n onClick={(e) => {\r\n e.stopPropagation();\r\n if (canSkip) onSkip();\r\n }}\r\n disabled={!canSkip}\r\n type=\"button\"\r\n >\r\n {skipText}\r\n </button>\r\n </div>\r\n );\r\n};\r\n\r\nexport default AdOverlay;\r\n","// Error Display Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\nimport { ErrorIcon } from './Icons';\r\n\r\ninterface ErrorDisplayProps {\r\n error: MediaError | null;\r\n onRetry: () => void;\r\n}\r\n\r\nconst ERROR_MESSAGES: Record<number, string> = {\r\n 1: 'The video playback was aborted.',\r\n 2: 'A network error occurred while loading the video.',\r\n 3: 'The video format is not supported or cannot be decoded.',\r\n 4: 'The video source is not supported.',\r\n};\r\n\r\nexport const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ error, onRetry }) => {\r\n const errorCode = error?.code || 0;\r\n const message = ERROR_MESSAGES[errorCode] || 'An unknown error occurred.';\r\n\r\n return (\r\n <div className=\"plex-video-player__error\">\r\n <ErrorIcon className=\"plex-video-player__error-icon\" size={60} />\r\n <div className=\"plex-video-player__error-message\">{message}</div>\r\n {errorCode > 0 && (\r\n <div className=\"plex-video-player__error-code\">Error Code: {errorCode}</div>\r\n )}\r\n <button\r\n className=\"plex-video-player__error-retry\"\r\n onClick={onRetry}\r\n type=\"button\"\r\n >\r\n Try Again\r\n </button>\r\n </div>\r\n );\r\n};\r\n\r\nexport default ErrorDisplay;\r\n","// Loading Spinner Component for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport React from 'react';\r\n\r\ninterface LoaderProps {\r\n visible: boolean;\r\n}\r\n\r\nexport const Loader: React.FC<LoaderProps> = ({ visible }) => {\r\n if (!visible) return null;\r\n\r\n return (\r\n <div className=\"plex-video-player__loader\">\r\n <div className=\"plex-video-player__loader-spinner\" />\r\n </div>\r\n );\r\n};\r\n\r\nexport default Loader;\r\n","// useKeyboard Hook for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { useEffect, useCallback } from 'react';\r\nimport { HotkeyConfig } from '../types';\r\n\r\nconst DEFAULT_HOTKEYS: Required<HotkeyConfig> = {\r\n play: 'Space',\r\n mute: 'm',\r\n fullscreen: 'f',\r\n pip: 'p',\r\n seekForward: 'ArrowRight',\r\n seekBackward: 'ArrowLeft',\r\n volumeUp: 'ArrowUp',\r\n volumeDown: 'ArrowDown',\r\n};\r\n\r\ninterface UseKeyboardOptions {\r\n enabled: boolean;\r\n hotkeys?: HotkeyConfig;\r\n onPlay: () => void;\r\n onMute: () => void;\r\n onFullscreen: () => void;\r\n onPip: () => void;\r\n onSeek: (delta: number) => void;\r\n onVolume: (delta: number) => void;\r\n containerRef: React.RefObject<HTMLDivElement>;\r\n}\r\n\r\nexport const useKeyboard = ({\r\n enabled,\r\n hotkeys = {},\r\n onPlay,\r\n onMute,\r\n onFullscreen,\r\n onPip,\r\n onSeek,\r\n onVolume,\r\n containerRef,\r\n}: UseKeyboardOptions): void => {\r\n const mergedHotkeys = { ...DEFAULT_HOTKEYS, ...hotkeys };\r\n\r\n const handleKeyDown = useCallback(\r\n (event: KeyboardEvent) => {\r\n if (!enabled) return;\r\n\r\n // Ignore if typing in an input\r\n const target = event.target as HTMLElement;\r\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {\r\n return;\r\n }\r\n\r\n // Check if focus is within the player container\r\n const container = containerRef.current;\r\n if (!container || !container.contains(document.activeElement)) {\r\n return;\r\n }\r\n\r\n const key = event.key;\r\n\r\n switch (key) {\r\n case mergedHotkeys.play:\r\n case ' ':\r\n event.preventDefault();\r\n onPlay();\r\n break;\r\n case mergedHotkeys.mute:\r\n case 'M':\r\n event.preventDefault();\r\n onMute();\r\n break;\r\n case mergedHotkeys.fullscreen:\r\n case 'F':\r\n event.preventDefault();\r\n onFullscreen();\r\n break;\r\n case mergedHotkeys.pip:\r\n case 'P':\r\n event.preventDefault();\r\n onPip();\r\n break;\r\n case mergedHotkeys.seekForward:\r\n event.preventDefault();\r\n onSeek(event.shiftKey ? 30 : 10);\r\n break;\r\n case mergedHotkeys.seekBackward:\r\n event.preventDefault();\r\n onSeek(event.shiftKey ? -30 : -10);\r\n break;\r\n case mergedHotkeys.volumeUp:\r\n event.preventDefault();\r\n onVolume(0.1);\r\n break;\r\n case mergedHotkeys.volumeDown:\r\n event.preventDefault();\r\n onVolume(-0.1);\r\n break;\r\n case '0':\r\n case '1':\r\n case '2':\r\n case '3':\r\n case '4':\r\n case '5':\r\n case '6':\r\n case '7':\r\n case '8':\r\n case '9':\r\n event.preventDefault();\r\n // Seek to percentage of video\r\n onSeek(-Infinity); // Will be handled by caller with percent\r\n break;\r\n default:\r\n break;\r\n }\r\n },\r\n [enabled, mergedHotkeys, onPlay, onMute, onFullscreen, onPip, onSeek, onVolume, containerRef]\r\n );\r\n\r\n useEffect(() => {\r\n if (!enabled) return;\r\n\r\n document.addEventListener('keydown', handleKeyDown);\r\n\r\n return () => {\r\n document.removeEventListener('keydown', handleKeyDown);\r\n };\r\n }, [enabled, handleKeyDown]);\r\n};\r\n\r\nexport default useKeyboard;\r\n","// VAST Ads Parser for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { VastAdInfo, VastConfig } from '../types';\r\n\r\ninterface VastMediaFile {\r\n url: string;\r\n type: string;\r\n width: number;\r\n height: number;\r\n bitrate?: number;\r\n}\r\n\r\ninterface VastAd {\r\n id: string;\r\n title: string;\r\n description?: string;\r\n duration: number;\r\n skipOffset?: number;\r\n clickThrough?: string;\r\n clickTracking?: string[];\r\n impressionUrls: string[];\r\n mediaFiles: VastMediaFile[];\r\n trackingEvents: Record<string, string[]>;\r\n}\r\n\r\ninterface VastResponse {\r\n ads: VastAd[];\r\n error?: string;\r\n}\r\n\r\n/**\r\n * Parse VAST XML response\r\n */\r\nexport const parseVastXml = (xmlString: string): VastResponse => {\r\n const parser = new DOMParser();\r\n const doc = parser.parseFromString(xmlString, 'text/xml');\r\n \r\n const parseError = doc.querySelector('parsererror');\r\n if (parseError) {\r\n return { ads: [], error: 'Failed to parse VAST XML' };\r\n }\r\n\r\n const ads: VastAd[] = [];\r\n const adElements = doc.querySelectorAll('Ad');\r\n\r\n adElements.forEach((adElement) => {\r\n const inLine = adElement.querySelector('InLine');\r\n if (!inLine) return;\r\n\r\n const id = adElement.getAttribute('id') || '';\r\n const title = inLine.querySelector('AdTitle')?.textContent || '';\r\n const description = inLine.querySelector('Description')?.textContent || undefined;\r\n\r\n // Parse creatives\r\n const creatives = inLine.querySelectorAll('Creative');\r\n let duration = 0;\r\n let skipOffset: number | undefined;\r\n let clickThrough: string | undefined;\r\n const clickTracking: string[] = [];\r\n const mediaFiles: VastMediaFile[] = [];\r\n const trackingEvents: Record<string, string[]> = {};\r\n\r\n creatives.forEach((creative) => {\r\n const linear = creative.querySelector('Linear');\r\n if (!linear) return;\r\n\r\n // Duration\r\n const durationStr = linear.querySelector('Duration')?.textContent;\r\n if (durationStr) {\r\n duration = parseVastDuration(durationStr);\r\n }\r\n\r\n // Skip offset\r\n const skipOffsetAttr = linear.getAttribute('skipoffset');\r\n if (skipOffsetAttr) {\r\n skipOffset = parseVastDuration(skipOffsetAttr);\r\n }\r\n\r\n // Media files\r\n const mediaFileElements = linear.querySelectorAll('MediaFile');\r\n mediaFileElements.forEach((mediaFile) => {\r\n const url = mediaFile.textContent?.trim();\r\n if (url) {\r\n mediaFiles.push({\r\n url,\r\n type: mediaFile.getAttribute('type') || 'video/mp4',\r\n width: parseInt(mediaFile.getAttribute('width') || '0', 10),\r\n height: parseInt(mediaFile.getAttribute('height') || '0', 10),\r\n bitrate: parseInt(mediaFile.getAttribute('bitrate') || '0', 10) || undefined,\r\n });\r\n }\r\n });\r\n\r\n // Video clicks\r\n const videoClicks = linear.querySelector('VideoClicks');\r\n if (videoClicks) {\r\n clickThrough = videoClicks.querySelector('ClickThrough')?.textContent?.trim();\r\n videoClicks.querySelectorAll('ClickTracking').forEach((ct) => {\r\n const url = ct.textContent?.trim();\r\n if (url) clickTracking.push(url);\r\n });\r\n }\r\n\r\n // Tracking events\r\n const trackingEventsEl = linear.querySelectorAll('TrackingEvents Tracking');\r\n trackingEventsEl.forEach((tracking) => {\r\n const event = tracking.getAttribute('event');\r\n const url = tracking.textContent?.trim();\r\n if (event && url) {\r\n if (!trackingEvents[event]) {\r\n trackingEvents[event] = [];\r\n }\r\n trackingEvents[event].push(url);\r\n }\r\n });\r\n });\r\n\r\n // Impression URLs\r\n const impressionUrls: string[] = [];\r\n inLine.querySelectorAll('Impression').forEach((impression) => {\r\n const url = impression.textContent?.trim();\r\n if (url) impressionUrls.push(url);\r\n });\r\n\r\n if (mediaFiles.length > 0) {\r\n ads.push({\r\n id,\r\n title,\r\n description,\r\n duration,\r\n skipOffset,\r\n clickThrough,\r\n clickTracking,\r\n impressionUrls,\r\n mediaFiles,\r\n trackingEvents,\r\n });\r\n }\r\n });\r\n\r\n return { ads };\r\n};\r\n\r\n/**\r\n * Parse VAST duration string (HH:MM:SS or HH:MM:SS.mmm)\r\n */\r\nconst parseVastDuration = (duration: string): number => {\r\n if (duration.includes('%')) {\r\n return -1; // Percentage-based, handle separately\r\n }\r\n\r\n const parts = duration.split(':');\r\n if (parts.length !== 3) return 0;\r\n\r\n const hours = parseInt(parts[0], 10);\r\n const minutes = parseInt(parts[1], 10);\r\n const seconds = parseFloat(parts[2]);\r\n\r\n return hours * 3600 + minutes * 60 + seconds;\r\n};\r\n\r\n/**\r\n * Fetch and parse VAST ad\r\n */\r\nexport const fetchVastAd = async (config: VastConfig): Promise<VastAd | null> => {\r\n try {\r\n const response = await fetch(config.url, {\r\n method: 'GET',\r\n headers: {\r\n 'Accept': 'application/xml',\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n console.error('VAST fetch failed:', response.status);\r\n return null;\r\n }\r\n\r\n const xmlString = await response.text();\r\n const result = parseVastXml(xmlString);\r\n\r\n if (result.error || result.ads.length === 0) {\r\n console.error('VAST parse error:', result.error || 'No ads found');\r\n return null;\r\n }\r\n\r\n const ad = result.ads[0];\r\n \r\n // Apply skip delay from config if not in VAST\r\n if (config.skipDelay !== undefined && ad.skipOffset === undefined) {\r\n ad.skipOffset = config.skipDelay;\r\n }\r\n\r\n return ad;\r\n } catch (error) {\r\n console.error('VAST fetch error:', error);\r\n return null;\r\n }\r\n};\r\n\r\n/**\r\n * Select best media file based on device capabilities\r\n */\r\nexport const selectBestMediaFile = (mediaFiles: VastMediaFile[]): VastMediaFile | null => {\r\n if (mediaFiles.length === 0) return null;\r\n\r\n const video = document.createElement('video');\r\n const supportedFiles = mediaFiles.filter((file) => {\r\n return video.canPlayType(file.type) !== '';\r\n });\r\n\r\n if (supportedFiles.length === 0) return null;\r\n\r\n // Sort by resolution (prefer higher) and bitrate\r\n supportedFiles.sort((a, b) => {\r\n const resA = a.width * a.height;\r\n const resB = b.width * b.height;\r\n if (resA !== resB) return resB - resA;\r\n return (b.bitrate || 0) - (a.bitrate || 0);\r\n });\r\n\r\n // On mobile, prefer lower resolution\r\n const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);\r\n if (isMobile && supportedFiles.length > 1) {\r\n return supportedFiles[supportedFiles.length - 1];\r\n }\r\n\r\n return supportedFiles[0];\r\n};\r\n\r\n/**\r\n * Fire tracking pixel\r\n */\r\nexport const fireTrackingPixel = (url: string): void => {\r\n const img = new Image();\r\n img.src = url;\r\n};\r\n\r\n/**\r\n * Fire multiple tracking pixels\r\n */\r\nexport const fireTrackingPixels = (urls: string[]): void => {\r\n urls.forEach(fireTrackingPixel);\r\n};\r\n\r\n/**\r\n * Convert VAST ad to player-friendly format\r\n */\r\nexport const convertToAdInfo = (ad: VastAd): VastAdInfo => {\r\n return {\r\n id: ad.id,\r\n title: ad.title,\r\n duration: ad.duration,\r\n skipOffset: ad.skipOffset,\r\n clickThrough: ad.clickThrough,\r\n };\r\n};\r\n\r\nexport type { VastAd, VastMediaFile, VastResponse };\r\n","// useVast Hook for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { useState, useCallback, useRef, useEffect } from 'react';\r\nimport { VastConfig, VastAdInfo } from '../types';\r\nimport {\r\n fetchVastAd,\r\n selectBestMediaFile,\r\n fireTrackingPixels,\r\n convertToAdInfo,\r\n VastAd,\r\n} from '../utils/vast';\r\n\r\ninterface UseVastOptions {\r\n vastConfig?: VastConfig | VastConfig[];\r\n videoRef: React.RefObject<HTMLVideoElement>;\r\n onAdStart?: (ad: VastAdInfo) => void;\r\n onAdEnd?: () => void;\r\n onAdSkip?: () => void;\r\n onAdError?: (error: Error) => void;\r\n}\r\n\r\ninterface UseVastReturn {\r\n isAdPlaying: boolean;\r\n currentAd: VastAdInfo | null;\r\n adTimeRemaining: number;\r\n canSkip: boolean;\r\n skipAd: () => void;\r\n handleAdClick: () => void;\r\n checkForAd: (currentTime: number, duration: number) => void;\r\n}\r\n\r\nexport const useVast = ({\r\n vastConfig,\r\n videoRef,\r\n onAdStart,\r\n onAdEnd,\r\n onAdSkip,\r\n onAdError,\r\n}: UseVastOptions): UseVastReturn => {\r\n const [isAdPlaying, setIsAdPlaying] = useState(false);\r\n const [currentAd, setCurrentAd] = useState<VastAdInfo | null>(null);\r\n const [adTimeRemaining, setAdTimeRemaining] = useState(0);\r\n const [canSkip, setCanSkip] = useState(false);\r\n\r\n const currentVastAd = useRef<VastAd | null>(null);\r\n const originalSrc = useRef<string>('');\r\n const originalTime = useRef<number>(0);\r\n const playedPositions = useRef<Set<string>>(new Set());\r\n const adIntervalRef = useRef<number | null>(null);\r\n\r\n // Normalize config to array\r\n const vastConfigs = Array.isArray(vastConfig) ? vastConfig : vastConfig ? [vastConfig] : [];\r\n\r\n // Play ad\r\n const playAd = useCallback(\r\n async (config: VastConfig) => {\r\n const video = videoRef.current;\r\n if (!video || isAdPlaying) return;\r\n\r\n try {\r\n const ad = await fetchVastAd(config);\r\n if (!ad) {\r\n onAdError?.(new Error('Failed to fetch VAST ad'));\r\n return;\r\n }\r\n\r\n const mediaFile = selectBestMediaFile(ad.mediaFiles);\r\n if (!mediaFile) {\r\n onAdError?.(new Error('No compatible media file found'));\r\n return;\r\n }\r\n\r\n // Store original video state\r\n originalSrc.current = video.currentSrc;\r\n originalTime.current = video.currentTime;\r\n\r\n // Fire impression pixels\r\n fireTrackingPixels(ad.impressionUrls);\r\n\r\n // Set up ad\r\n currentVastAd.current = ad;\r\n setCurrentAd(convertToAdInfo(ad));\r\n setIsAdPlaying(true);\r\n setAdTimeRemaining(ad.duration);\r\n setCanSkip(ad.skipOffset === undefined || ad.skipOffset === 0);\r\n\r\n // Change video source to ad\r\n video.src = mediaFile.url;\r\n video.currentTime = 0;\r\n await video.play();\r\n\r\n // Fire start tracking\r\n if (ad.trackingEvents.start) {\r\n fireTrackingPixels(ad.trackingEvents.start);\r\n }\r\n\r\n onAdStart?.(convertToAdInfo(ad));\r\n\r\n // Start ad timer\r\n adIntervalRef.current = window.setInterval(() => {\r\n const remaining = Math.max(0, ad.duration - video.currentTime);\r\n setAdTimeRemaining(remaining);\r\n\r\n // Check if can skip\r\n if (ad.skipOffset && video.currentTime >= ad.skipOffset) {\r\n setCanSkip(true);\r\n }\r\n\r\n // Track quartiles\r\n const percent = (video.currentTime / ad.duration) * 100;\r\n if (percent >= 25 && ad.trackingEvents.firstQuartile) {\r\n fireTrackingPixels(ad.trackingEvents.firstQuartile);\r\n }\r\n if (percent >= 50 && ad.trackingEvents.midpoint) {\r\n fireTrackingPixels(ad.trackingEvents.midpoint);\r\n }\r\n if (percent >= 75 && ad.trackingEvents.thirdQuartile) {\r\n fireTrackingPixels(ad.trackingEvents.thirdQuartile);\r\n }\r\n }, 250);\r\n\r\n // Handle ad completion\r\n const handleAdEnd = () => {\r\n if (ad.trackingEvents.complete) {\r\n fireTrackingPixels(ad.trackingEvents.complete);\r\n }\r\n endAd();\r\n };\r\n\r\n video.addEventListener('ended', handleAdEnd, { once: true });\r\n } catch (error) {\r\n onAdError?.(error instanceof Error ? error : new Error('Ad playback failed'));\r\n endAd();\r\n }\r\n },\r\n [videoRef, isAdPlaying, onAdStart, onAdError]\r\n );\r\n\r\n // End ad and restore original video\r\n const endAd = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n // Clear interval\r\n if (adIntervalRef.current) {\r\n clearInterval(adIntervalRef.current);\r\n adIntervalRef.current = null;\r\n }\r\n\r\n // Restore original video\r\n if (originalSrc.current) {\r\n video.src = originalSrc.current;\r\n video.currentTime = originalTime.current;\r\n video.play().catch(() => {});\r\n }\r\n\r\n setIsAdPlaying(false);\r\n setCurrentAd(null);\r\n setAdTimeRemaining(0);\r\n setCanSkip(false);\r\n currentVastAd.current = null;\r\n\r\n onAdEnd?.();\r\n }, [videoRef, onAdEnd]);\r\n\r\n // Skip ad\r\n const skipAd = useCallback(() => {\r\n if (!canSkip || !currentVastAd.current) return;\r\n\r\n const ad = currentVastAd.current;\r\n if (ad.trackingEvents.skip) {\r\n fireTrackingPixels(ad.trackingEvents.skip);\r\n }\r\n\r\n onAdSkip?.();\r\n endAd();\r\n }, [canSkip, endAd, onAdSkip]);\r\n\r\n // Handle ad click\r\n const handleAdClick = useCallback(() => {\r\n if (!currentVastAd.current) return;\r\n\r\n const ad = currentVastAd.current;\r\n if (ad.clickThrough) {\r\n window.open(ad.clickThrough, '_blank');\r\n }\r\n if (ad.clickTracking) {\r\n fireTrackingPixels(ad.clickTracking);\r\n }\r\n }, []);\r\n\r\n // Check for ad at position\r\n const checkForAd = useCallback(\r\n (currentTime: number, duration: number) => {\r\n if (isAdPlaying || vastConfigs.length === 0) return;\r\n\r\n vastConfigs.forEach((config) => {\r\n const position = config.position || 'preroll';\r\n const key = `${position}-${config.midrollTime || 0}`;\r\n\r\n if (playedPositions.current.has(key)) return;\r\n\r\n let shouldPlay = false;\r\n\r\n switch (position) {\r\n case 'preroll':\r\n shouldPlay = currentTime === 0;\r\n break;\r\n case 'midroll':\r\n if (config.midrollTime && currentTime >= config.midrollTime) {\r\n shouldPlay = true;\r\n }\r\n break;\r\n case 'postroll':\r\n shouldPlay = currentTime >= duration - 0.5;\r\n break;\r\n }\r\n\r\n if (shouldPlay) {\r\n playedPositions.current.add(key);\r\n playAd(config);\r\n }\r\n });\r\n },\r\n [vastConfigs, isAdPlaying, playAd]\r\n );\r\n\r\n // Cleanup on unmount\r\n useEffect(() => {\r\n return () => {\r\n if (adIntervalRef.current) {\r\n clearInterval(adIntervalRef.current);\r\n }\r\n };\r\n }, []);\r\n\r\n return {\r\n isAdPlaying,\r\n currentAd,\r\n adTimeRemaining,\r\n canSkip,\r\n skipAd,\r\n handleAdClick,\r\n checkForAd,\r\n };\r\n};\r\n\r\nexport default useVast;\r\n","// PlexVideo Player - Main Component\r\n// © FRAMESET STUDIO\r\n\r\nimport React, {\r\n useRef,\r\n useState,\r\n useEffect,\r\n useCallback,\r\n forwardRef,\r\n useImperativeHandle,\r\n useMemo,\r\n} from 'react';\r\nimport { ProgressBar } from './ProgressBar';\r\nimport { VolumeControl } from './VolumeControl';\r\nimport { SettingsMenu } from './SettingsMenu';\r\nimport { AdOverlay } from './AdOverlay';\r\nimport { ErrorDisplay } from './ErrorDisplay';\r\nimport { Loader } from './Loader';\r\nimport {\r\n PlayIcon,\r\n PauseIcon,\r\n FullscreenIcon,\r\n FullscreenExitIcon,\r\n PipIcon,\r\n} from './Icons';\r\nimport { useKeyboard } from '../hooks/useKeyboard';\r\nimport { useVast } from '../hooks/useVast';\r\nimport { formatTime, throttle } from '../utils/helpers';\r\nimport type {\r\n PlexVideoPlayerProps,\r\n PlexVideoPlayerRef,\r\n VideoSource,\r\n} from '../types';\r\n\r\nexport const PlexVideoPlayer = forwardRef<PlexVideoPlayerRef, PlexVideoPlayerProps>(\r\n (props, ref) => {\r\n const {\r\n src,\r\n poster,\r\n autoPlay = false,\r\n muted = false,\r\n loop = false,\r\n controls = true,\r\n width = '100%',\r\n className = '',\r\n style = {},\r\n pip = true,\r\n fullscreen = true,\r\n playbackSpeed = true,\r\n playbackSpeeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2],\r\n volume: volumeEnabled = true,\r\n initialVolume = 1,\r\n progressBar = true,\r\n timeDisplay = true,\r\n qualitySelector = true,\r\n textTracks = [],\r\n vast,\r\n keyboard = true,\r\n hotkeys,\r\n accentColor = '#e50914',\r\n controlsTimeout = 3000,\r\n doubleClickFullscreen = true,\r\n clickToPlay = true,\r\n preload = 'metadata',\r\n onPlay,\r\n onPause,\r\n onEnded,\r\n onTimeUpdate,\r\n onProgress,\r\n onVolumeChange,\r\n onFullscreenChange,\r\n onPipChange,\r\n onQualityChange,\r\n onError,\r\n onAdStart,\r\n onAdEnd,\r\n onAdSkip,\r\n onAdError,\r\n onReady,\r\n } = props;\r\n\r\n // Refs\r\n const videoRef = useRef<HTMLVideoElement>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const controlsTimeoutRef = useRef<NodeJS.Timeout | null>(null);\r\n const lastClickRef = useRef<number>(0);\r\n\r\n // State\r\n const [isPlaying, setIsPlaying] = useState(false);\r\n const [currentTime, setCurrentTime] = useState(0);\r\n const [duration, setDuration] = useState(0);\r\n const [buffered, setBuffered] = useState(0);\r\n const [volumeState, setVolumeState] = useState(muted ? 0 : initialVolume);\r\n const [isMuted, setIsMuted] = useState(muted);\r\n const [isFullscreen, setIsFullscreen] = useState(false);\r\n const [isPipActive, setIsPipActive] = useState(false);\r\n const [isLoading, setIsLoading] = useState(true);\r\n const [error, setError] = useState<MediaError | null>(null);\r\n const [showControls, setShowControls] = useState(true);\r\n const [playbackRate, setPlaybackRateState] = useState(1);\r\n const [currentQuality, setCurrentQuality] = useState<string>('auto');\r\n const [currentTrackLabel, setCurrentTrackLabel] = useState<string | null>(null);\r\n\r\n // Parse sources\r\n const sources = useMemo<VideoSource[]>(() => {\r\n if (typeof src === 'string') {\r\n return [{ src, type: 'video/mp4' }];\r\n }\r\n return src as VideoSource[];\r\n }, [src]);\r\n\r\n // VAST ads hook\r\n const {\r\n currentAd,\r\n isAdPlaying,\r\n adTimeRemaining,\r\n canSkip,\r\n skipAd,\r\n handleAdClick,\r\n checkForAd,\r\n } = useVast({\r\n vastConfig: vast,\r\n videoRef,\r\n onAdStart,\r\n onAdEnd,\r\n onAdSkip,\r\n onAdError,\r\n });\r\n\r\n // Player control functions\r\n const play = useCallback(async () => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n try {\r\n await video.play();\r\n } catch (err) {\r\n console.error('Play error:', err);\r\n }\r\n }, []);\r\n\r\n const pause = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.pause();\r\n }, []);\r\n\r\n const stop = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.pause();\r\n video.currentTime = 0;\r\n }, []);\r\n\r\n const togglePlay = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n if (video.paused) {\r\n play();\r\n } else {\r\n pause();\r\n }\r\n }, [play, pause]);\r\n\r\n const seek = useCallback((time: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.currentTime = Math.max(0, Math.min(time, video.duration || 0));\r\n }, []);\r\n\r\n const setVolume = useCallback((value: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n const newVolume = Math.max(0, Math.min(1, value));\r\n video.volume = newVolume;\r\n video.muted = newVolume === 0;\r\n setVolumeState(newVolume);\r\n setIsMuted(newVolume === 0);\r\n }, []);\r\n\r\n const mute = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.muted = true;\r\n setIsMuted(true);\r\n }, []);\r\n\r\n const unmute = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.muted = false;\r\n if (video.volume === 0) {\r\n video.volume = 0.5;\r\n setVolumeState(0.5);\r\n }\r\n setIsMuted(false);\r\n }, []);\r\n\r\n const toggleMute = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n if (video.muted) {\r\n unmute();\r\n } else {\r\n mute();\r\n }\r\n }, [mute, unmute]);\r\n\r\n const enterFullscreen = useCallback(async () => {\r\n const container = containerRef.current;\r\n if (!container) return;\r\n try {\r\n await container.requestFullscreen();\r\n setIsFullscreen(true);\r\n } catch (err) {\r\n console.error('Fullscreen error:', err);\r\n }\r\n }, []);\r\n\r\n const exitFullscreen = useCallback(async () => {\r\n try {\r\n if (document.fullscreenElement) {\r\n await document.exitFullscreen();\r\n setIsFullscreen(false);\r\n }\r\n } catch (err) {\r\n console.error('Exit fullscreen error:', err);\r\n }\r\n }, []);\r\n\r\n const toggleFullscreen = useCallback(async () => {\r\n if (document.fullscreenElement) {\r\n await exitFullscreen();\r\n } else {\r\n await enterFullscreen();\r\n }\r\n }, [enterFullscreen, exitFullscreen]);\r\n\r\n const enterPip = useCallback(async () => {\r\n const video = videoRef.current;\r\n if (!video || !document.pictureInPictureEnabled) return;\r\n try {\r\n await video.requestPictureInPicture();\r\n setIsPipActive(true);\r\n } catch (err) {\r\n console.error('PiP error:', err);\r\n }\r\n }, []);\r\n\r\n const exitPip = useCallback(async () => {\r\n try {\r\n if (document.pictureInPictureElement) {\r\n await document.exitPictureInPicture();\r\n setIsPipActive(false);\r\n }\r\n } catch (err) {\r\n console.error('Exit PiP error:', err);\r\n }\r\n }, []);\r\n\r\n const togglePip = useCallback(async () => {\r\n if (document.pictureInPictureElement) {\r\n await exitPip();\r\n } else {\r\n await enterPip();\r\n }\r\n }, [enterPip, exitPip]);\r\n\r\n const setPlaybackRate = useCallback((rate: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n video.playbackRate = rate;\r\n setPlaybackRateState(rate);\r\n }, []);\r\n\r\n const setQuality = useCallback((qualityLabel: string) => {\r\n const source = sources.find(s => s.label === qualityLabel || s.quality === qualityLabel);\r\n if (source && videoRef.current) {\r\n const video = videoRef.current;\r\n const wasPlaying = !video.paused;\r\n const time = video.currentTime;\r\n video.src = source.src;\r\n video.currentTime = time;\r\n if (wasPlaying) {\r\n video.play();\r\n }\r\n setCurrentQuality(qualityLabel);\r\n onQualityChange?.(qualityLabel);\r\n }\r\n }, [sources, onQualityChange]);\r\n\r\n const setTextTrack = useCallback((trackLabel: string | null) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n for (let i = 0; i < video.textTracks.length; i++) {\r\n const track = video.textTracks[i];\r\n track.mode = track.label === trackLabel ? 'showing' : 'hidden';\r\n }\r\n setCurrentTrackLabel(trackLabel);\r\n }, []);\r\n\r\n // Hide controls logic\r\n const hideControls = useCallback(() => {\r\n if (isPlaying) {\r\n setShowControls(false);\r\n }\r\n }, [isPlaying]);\r\n\r\n const showControlsTemporarily = useCallback(() => {\r\n setShowControls(true);\r\n if (controlsTimeoutRef.current) {\r\n clearTimeout(controlsTimeoutRef.current);\r\n }\r\n if (isPlaying) {\r\n controlsTimeoutRef.current = setTimeout(hideControls, controlsTimeout);\r\n }\r\n }, [isPlaying, controlsTimeout, hideControls]);\r\n\r\n const handleMouseMove = useMemo(\r\n () => throttle(showControlsTemporarily, 200),\r\n [showControlsTemporarily]\r\n );\r\n\r\n // Video click handler\r\n const handleVideoClick = useCallback(() => {\r\n if (!clickToPlay) return;\r\n \r\n const now = Date.now();\r\n if (doubleClickFullscreen && now - lastClickRef.current < 300) {\r\n toggleFullscreen();\r\n } else {\r\n togglePlay();\r\n }\r\n lastClickRef.current = now;\r\n }, [clickToPlay, doubleClickFullscreen, togglePlay, toggleFullscreen]);\r\n\r\n // Video event handlers\r\n useEffect(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const handlePlay = () => {\r\n setIsPlaying(true);\r\n onPlay?.();\r\n };\r\n\r\n const handlePause = () => {\r\n setIsPlaying(false);\r\n onPause?.();\r\n };\r\n\r\n const handleEnded = () => {\r\n setIsPlaying(false);\r\n onEnded?.();\r\n };\r\n\r\n const handleTimeUpdate = () => {\r\n setCurrentTime(video.currentTime);\r\n onTimeUpdate?.(video.currentTime);\r\n checkForAd(video.currentTime, video.duration);\r\n };\r\n\r\n const handleDurationChange = () => {\r\n setDuration(video.duration);\r\n };\r\n\r\n const handleProgressEvent = () => {\r\n if (video.buffered.length > 0) {\r\n const bufferedEnd = video.buffered.end(video.buffered.length - 1);\r\n const bufferedPercent = (bufferedEnd / video.duration) * 100;\r\n setBuffered(bufferedPercent);\r\n onProgress?.(bufferedPercent);\r\n }\r\n };\r\n\r\n const handleVolumeChangeEvent = () => {\r\n setVolumeState(video.volume);\r\n setIsMuted(video.muted);\r\n onVolumeChange?.(video.volume, video.muted);\r\n };\r\n\r\n const handleLoadedMetadata = () => {\r\n setDuration(video.duration);\r\n setIsLoading(false);\r\n onReady?.();\r\n };\r\n\r\n const handleWaiting = () => {\r\n setIsLoading(true);\r\n };\r\n\r\n const handlePlaying = () => {\r\n setIsLoading(false);\r\n };\r\n\r\n const handleCanPlay = () => {\r\n setIsLoading(false);\r\n };\r\n\r\n const handleErrorEvent = () => {\r\n setError(video.error);\r\n setIsLoading(false);\r\n onError?.(video.error);\r\n };\r\n\r\n video.addEventListener('play', handlePlay);\r\n video.addEventListener('pause', handlePause);\r\n video.addEventListener('ended', handleEnded);\r\n video.addEventListener('timeupdate', handleTimeUpdate);\r\n video.addEventListener('durationchange', handleDurationChange);\r\n video.addEventListener('progress', handleProgressEvent);\r\n video.addEventListener('volumechange', handleVolumeChangeEvent);\r\n video.addEventListener('loadedmetadata', handleLoadedMetadata);\r\n video.addEventListener('waiting', handleWaiting);\r\n video.addEventListener('playing', handlePlaying);\r\n video.addEventListener('canplay', handleCanPlay);\r\n video.addEventListener('error', handleErrorEvent);\r\n\r\n return () => {\r\n video.removeEventListener('play', handlePlay);\r\n video.removeEventListener('pause', handlePause);\r\n video.removeEventListener('ended', handleEnded);\r\n video.removeEventListener('timeupdate', handleTimeUpdate);\r\n video.removeEventListener('durationchange', handleDurationChange);\r\n video.removeEventListener('progress', handleProgressEvent);\r\n video.removeEventListener('volumechange', handleVolumeChangeEvent);\r\n video.removeEventListener('loadedmetadata', handleLoadedMetadata);\r\n video.removeEventListener('waiting', handleWaiting);\r\n video.removeEventListener('playing', handlePlaying);\r\n video.removeEventListener('canplay', handleCanPlay);\r\n video.removeEventListener('error', handleErrorEvent);\r\n };\r\n }, [onPlay, onPause, onEnded, onTimeUpdate, onProgress, onVolumeChange, onReady, onError, checkForAd]);\r\n\r\n // Fullscreen change listener\r\n useEffect(() => {\r\n const handleFullscreenChange = () => {\r\n const isFs = !!document.fullscreenElement;\r\n setIsFullscreen(isFs);\r\n onFullscreenChange?.(isFs);\r\n };\r\n\r\n document.addEventListener('fullscreenchange', handleFullscreenChange);\r\n return () => {\r\n document.removeEventListener('fullscreenchange', handleFullscreenChange);\r\n };\r\n }, [onFullscreenChange]);\r\n\r\n // PiP change listener\r\n useEffect(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const handleEnterPip = () => {\r\n setIsPipActive(true);\r\n onPipChange?.(true);\r\n };\r\n\r\n const handleLeavePip = () => {\r\n setIsPipActive(false);\r\n onPipChange?.(false);\r\n };\r\n\r\n video.addEventListener('enterpictureinpicture', handleEnterPip);\r\n video.addEventListener('leavepictureinpicture', handleLeavePip);\r\n\r\n return () => {\r\n video.removeEventListener('enterpictureinpicture', handleEnterPip);\r\n video.removeEventListener('leavepictureinpicture', handleLeavePip);\r\n };\r\n }, [onPipChange]);\r\n\r\n // Keyboard shortcuts\r\n useKeyboard({\r\n enabled: keyboard,\r\n containerRef,\r\n hotkeys,\r\n onPlay: togglePlay,\r\n onMute: toggleMute,\r\n onFullscreen: toggleFullscreen,\r\n onPip: togglePip,\r\n onSeek: (delta: number) => seek(currentTime + delta),\r\n onVolume: (delta: number) => setVolume(volumeState + delta),\r\n });\r\n\r\n // Cleanup timeout on unmount\r\n useEffect(() => {\r\n return () => {\r\n if (controlsTimeoutRef.current) {\r\n clearTimeout(controlsTimeoutRef.current);\r\n }\r\n };\r\n }, []);\r\n\r\n // Retry handler\r\n const handleRetry = useCallback(() => {\r\n setError(null);\r\n videoRef.current?.load();\r\n }, []);\r\n\r\n // Imperative handle for external control\r\n useImperativeHandle(\r\n ref,\r\n () => ({\r\n play,\r\n pause,\r\n stop,\r\n seek,\r\n setVolume,\r\n mute,\r\n unmute,\r\n toggleMute,\r\n enterFullscreen,\r\n exitFullscreen,\r\n toggleFullscreen,\r\n enterPip,\r\n exitPip,\r\n togglePip,\r\n setPlaybackRate,\r\n setQuality,\r\n getCurrentTime: () => videoRef.current?.currentTime || 0,\r\n getDuration: () => videoRef.current?.duration || 0,\r\n getVolume: () => videoRef.current?.volume || 0,\r\n isMuted: () => videoRef.current?.muted ?? true,\r\n isPlaying: () => !videoRef.current?.paused,\r\n isFullscreen: () => isFullscreen,\r\n isPip: () => isPipActive,\r\n getVideoElement: () => videoRef.current,\r\n }),\r\n [\r\n play,\r\n pause,\r\n stop,\r\n seek,\r\n setVolume,\r\n mute,\r\n unmute,\r\n toggleMute,\r\n enterFullscreen,\r\n exitFullscreen,\r\n toggleFullscreen,\r\n enterPip,\r\n exitPip,\r\n togglePip,\r\n setPlaybackRate,\r\n setQuality,\r\n isFullscreen,\r\n isPipActive,\r\n ]\r\n );\r\n\r\n // Get video source\r\n const videoSrc = sources[0]?.src || '';\r\n\r\n // CSS variables for accent color\r\n const cssVariables = {\r\n '--plex-accent-color': accentColor,\r\n } as React.CSSProperties;\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={`plex-video-player ${isFullscreen ? 'plex-video-player--fullscreen' : ''} ${className}`}\r\n style={{ ...cssVariables, width, ...style }}\r\n onMouseMove={handleMouseMove}\r\n onMouseLeave={() => isPlaying && setShowControls(false)}\r\n tabIndex={0}\r\n >\r\n {/* Video Element */}\r\n <video\r\n ref={videoRef}\r\n className=\"plex-video-player__video\"\r\n src={videoSrc}\r\n poster={poster}\r\n autoPlay={autoPlay}\r\n muted={muted}\r\n loop={loop}\r\n preload={preload}\r\n playsInline\r\n onClick={handleVideoClick}\r\n >\r\n {sources.slice(1).map((source, index) => (\r\n <source key={index} src={source.src} type={source.type} />\r\n ))}\r\n {textTracks.map((track, index) => (\r\n <track\r\n key={index}\r\n kind={track.kind}\r\n src={track.src}\r\n srcLang={track.srclang}\r\n label={track.label}\r\n default={track.default}\r\n />\r\n ))}\r\n </video>\r\n\r\n {/* Loading Indicator */}\r\n <Loader visible={isLoading && !error} />\r\n\r\n {/* Error Display */}\r\n {error && <ErrorDisplay error={error} onRetry={handleRetry} />}\r\n\r\n {/* Ad Overlay */}\r\n {isAdPlaying && currentAd && (\r\n <AdOverlay\r\n ad={currentAd}\r\n timeRemaining={adTimeRemaining}\r\n canSkip={canSkip}\r\n onSkip={skipAd}\r\n onClick={handleAdClick}\r\n />\r\n )}\r\n\r\n {/* Controls */}\r\n {controls && !isAdPlaying && (\r\n <div\r\n className={`plex-video-player__controls ${showControls ? 'plex-video-player__controls--visible' : ''}`}\r\n >\r\n {/* Progress Bar */}\r\n {progressBar && (\r\n <ProgressBar\r\n currentTime={currentTime}\r\n duration={duration}\r\n buffered={buffered}\r\n onSeek={seek}\r\n />\r\n )}\r\n\r\n {/* Bottom Controls */}\r\n <div className=\"plex-video-player__controls-bottom\">\r\n {/* Left Controls */}\r\n <div className=\"plex-video-player__controls-left\">\r\n {/* Play/Pause Button */}\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={togglePlay}\r\n aria-label={isPlaying ? 'Pause' : 'Play'}\r\n type=\"button\"\r\n >\r\n {isPlaying ? <PauseIcon /> : <PlayIcon />}\r\n </button>\r\n\r\n {/* Volume Control */}\r\n {volumeEnabled && (\r\n <VolumeControl\r\n volume={volumeState}\r\n muted={isMuted}\r\n onVolumeChange={setVolume}\r\n onToggleMute={toggleMute}\r\n />\r\n )}\r\n\r\n {/* Time Display */}\r\n {timeDisplay && (\r\n <div className=\"plex-video-player__time\">\r\n <span>{formatTime(currentTime)}</span>\r\n <span> / </span>\r\n <span>{formatTime(duration)}</span>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Right Controls */}\r\n <div className=\"plex-video-player__controls-right\">\r\n {/* Settings Menu */}\r\n {playbackSpeed && (\r\n <SettingsMenu\r\n playbackRate={playbackRate}\r\n playbackSpeeds={playbackSpeeds}\r\n onPlaybackRateChange={setPlaybackRate}\r\n qualityEnabled={qualitySelector && sources.length > 1}\r\n sources={sources}\r\n currentQuality={currentQuality}\r\n onQualityChange={setQuality}\r\n captionsEnabled={textTracks.length > 0}\r\n textTracks={textTracks}\r\n currentTrack={currentTrackLabel ?? undefined}\r\n onTrackChange={setTextTrack}\r\n />\r\n )}\r\n\r\n {/* PiP Button */}\r\n {pip && document.pictureInPictureEnabled && (\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={togglePip}\r\n aria-label={isPipActive ? 'Exit Picture in Picture' : 'Picture in Picture'}\r\n type=\"button\"\r\n >\r\n <PipIcon />\r\n </button>\r\n )}\r\n\r\n {/* Fullscreen Button */}\r\n {fullscreen && (\r\n <button\r\n className=\"plex-video-player__btn\"\r\n onClick={toggleFullscreen}\r\n aria-label={isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}\r\n type=\"button\"\r\n >\r\n {isFullscreen ? <FullscreenExitIcon /> : <FullscreenIcon />}\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Big Play Button */}\r\n {!isPlaying && !isLoading && !error && !isAdPlaying && (\r\n <button\r\n className=\"plex-video-player__big-play\"\r\n onClick={togglePlay}\r\n aria-label=\"Play\"\r\n type=\"button\"\r\n >\r\n <PlayIcon />\r\n </button>\r\n )}\r\n </div>\r\n );\r\n }\r\n);\r\n\r\nPlexVideoPlayer.displayName = 'PlexVideoPlayer';\r\n\r\nexport default PlexVideoPlayer;\r\n","// usePlayer Hook for PlexVideo Player\r\n// © FRAMESET STUDIO\r\n\r\nimport { useState, useRef, useCallback, useEffect } from 'react';\r\nimport {\r\n PlayerState,\r\n UsePlayerOptions,\r\n UsePlayerReturn,\r\n} from '../types';\r\nimport {\r\n requestFullscreen,\r\n exitFullscreen,\r\n getFullscreenElement,\r\n getBufferedEnd,\r\n isPipSupported,\r\n} from '../utils/helpers';\r\n\r\nconst initialState: PlayerState = {\r\n isPlaying: false,\r\n isPaused: true,\r\n isEnded: false,\r\n isBuffering: false,\r\n isSeeking: false,\r\n isFullscreen: false,\r\n isPip: false,\r\n isMuted: false,\r\n isAdPlaying: false,\r\n volume: 1,\r\n currentTime: 0,\r\n duration: 0,\r\n buffered: 0,\r\n playbackRate: 1,\r\n currentQuality: null,\r\n error: null,\r\n};\r\n\r\nexport const usePlayer = (options: UsePlayerOptions = {}): UsePlayerReturn => {\r\n const {\r\n autoPlay = false,\r\n muted = false,\r\n loop = false,\r\n volume: initialVolume = 1,\r\n playbackRate: initialRate = 1,\r\n } = options;\r\n\r\n const videoRef = useRef<HTMLVideoElement>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const [state, setState] = useState<PlayerState>({\r\n ...initialState,\r\n volume: initialVolume,\r\n isMuted: muted,\r\n playbackRate: initialRate,\r\n });\r\n\r\n // Update state helper\r\n const updateState = useCallback((updates: Partial<PlayerState>) => {\r\n setState((prev) => ({ ...prev, ...updates }));\r\n }, []);\r\n\r\n // Play\r\n const play = useCallback(async () => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n try {\r\n await video.play();\r\n updateState({ isPlaying: true, isPaused: false, isEnded: false });\r\n } catch (error) {\r\n console.error('Play failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Pause\r\n const pause = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n video.pause();\r\n updateState({ isPlaying: false, isPaused: true });\r\n }, [updateState]);\r\n\r\n // Toggle play/pause\r\n const togglePlay = useCallback(() => {\r\n if (state.isPlaying) {\r\n pause();\r\n } else {\r\n play();\r\n }\r\n }, [state.isPlaying, play, pause]);\r\n\r\n // Seek\r\n const seek = useCallback((time: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const clampedTime = Math.max(0, Math.min(time, video.duration || 0));\r\n video.currentTime = clampedTime;\r\n updateState({ currentTime: clampedTime });\r\n }, [updateState]);\r\n\r\n // Set volume\r\n const setVolume = useCallback((volume: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n const clampedVolume = Math.max(0, Math.min(1, volume));\r\n video.volume = clampedVolume;\r\n video.muted = clampedVolume === 0;\r\n updateState({\r\n volume: clampedVolume,\r\n isMuted: clampedVolume === 0,\r\n });\r\n }, [updateState]);\r\n\r\n // Toggle mute\r\n const toggleMute = useCallback(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n video.muted = !video.muted;\r\n updateState({ isMuted: video.muted });\r\n }, [updateState]);\r\n\r\n // Set playback rate\r\n const setPlaybackRate = useCallback((rate: number) => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n video.playbackRate = rate;\r\n updateState({ playbackRate: rate });\r\n }, [updateState]);\r\n\r\n // Enter fullscreen\r\n const enterFullscreen = useCallback(async () => {\r\n const container = containerRef.current;\r\n if (!container) return;\r\n\r\n try {\r\n await requestFullscreen(container);\r\n updateState({ isFullscreen: true });\r\n } catch (error) {\r\n console.error('Fullscreen failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Exit fullscreen\r\n const exitFullscreenFn = useCallback(async () => {\r\n try {\r\n await exitFullscreen();\r\n updateState({ isFullscreen: false });\r\n } catch (error) {\r\n console.error('Exit fullscreen failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Toggle fullscreen\r\n const toggleFullscreen = useCallback(async () => {\r\n if (state.isFullscreen) {\r\n await exitFullscreenFn();\r\n } else {\r\n await enterFullscreen();\r\n }\r\n }, [state.isFullscreen, enterFullscreen, exitFullscreenFn]);\r\n\r\n // Enter Picture-in-Picture\r\n const enterPip = useCallback(async () => {\r\n const video = videoRef.current;\r\n if (!video || !isPipSupported()) return;\r\n\r\n try {\r\n await video.requestPictureInPicture();\r\n updateState({ isPip: true });\r\n } catch (error) {\r\n console.error('PiP failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Exit Picture-in-Picture\r\n const exitPipFn = useCallback(async () => {\r\n if (!document.pictureInPictureElement) return;\r\n\r\n try {\r\n await document.exitPictureInPicture();\r\n updateState({ isPip: false });\r\n } catch (error) {\r\n console.error('Exit PiP failed:', error);\r\n }\r\n }, [updateState]);\r\n\r\n // Toggle Picture-in-Picture\r\n const togglePip = useCallback(async () => {\r\n if (state.isPip) {\r\n await exitPipFn();\r\n } else {\r\n await enterPip();\r\n }\r\n }, [state.isPip, enterPip, exitPipFn]);\r\n\r\n // Video event handlers\r\n useEffect(() => {\r\n const video = videoRef.current;\r\n if (!video) return;\r\n\r\n // Set initial values\r\n video.muted = muted;\r\n video.volume = initialVolume;\r\n video.playbackRate = initialRate;\r\n video.loop = loop;\r\n\r\n const handlers = {\r\n loadedmetadata: () => {\r\n updateState({ duration: video.duration });\r\n },\r\n timeupdate: () => {\r\n updateState({\r\n currentTime: video.currentTime,\r\n buffered: getBufferedEnd(video),\r\n });\r\n },\r\n play: () => {\r\n updateState({ isPlaying: true, isPaused: false, isEnded: false });\r\n },\r\n pause: () => {\r\n updateState({ isPlaying: false, isPaused: true });\r\n },\r\n ended: () => {\r\n updateState({ isPlaying: false, isPaused: true, isEnded: true });\r\n },\r\n waiting: () => {\r\n updateState({ isBuffering: true });\r\n },\r\n canplay: () => {\r\n updateState({ isBuffering: false });\r\n },\r\n seeking: () => {\r\n updateState({ isSeeking: true });\r\n },\r\n seeked: () => {\r\n updateState({ isSeeking: false });\r\n },\r\n volumechange: () => {\r\n updateState({\r\n volume: video.volume,\r\n isMuted: video.muted,\r\n });\r\n },\r\n ratechange: () => {\r\n updateState({ playbackRate: video.playbackRate });\r\n },\r\n error: () => {\r\n updateState({ error: video.error });\r\n },\r\n enterpictureinpicture: () => {\r\n updateState({ isPip: true });\r\n },\r\n leavepictureinpicture: () => {\r\n updateState({ isPip: false });\r\n },\r\n };\r\n\r\n // Add event listeners\r\n Object.entries(handlers).forEach(([event, handler]) => {\r\n video.addEventListener(event, handler);\r\n });\r\n\r\n // Autoplay\r\n if (autoPlay) {\r\n play();\r\n }\r\n\r\n return () => {\r\n Object.entries(handlers).forEach(([event, handler]) => {\r\n video.removeEventListener(event, handler);\r\n });\r\n };\r\n }, [autoPlay, muted, loop, initialVolume, initialRate, play, updateState]);\r\n\r\n // Fullscreen change handler\r\n useEffect(() => {\r\n const handleFullscreenChange = () => {\r\n const isFs = !!getFullscreenElement();\r\n updateState({ isFullscreen: isFs });\r\n };\r\n\r\n document.addEventListener('fullscreenchange', handleFullscreenChange);\r\n document.addEventListener('webkitfullscreenchange', handleFullscreenChange);\r\n document.addEventListener('mozfullscreenchange', handleFullscreenChange);\r\n document.addEventListener('MSFullscreenChange', handleFullscreenChange);\r\n\r\n return () => {\r\n document.removeEventListener('fullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('webkitfullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('mozfullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('MSFullscreenChange', handleFullscreenChange);\r\n };\r\n }, [updateState]);\r\n\r\n return {\r\n state,\r\n videoRef,\r\n containerRef,\r\n play,\r\n pause,\r\n togglePlay,\r\n seek,\r\n setVolume,\r\n toggleMute,\r\n setPlaybackRate,\r\n enterFullscreen,\r\n exitFullscreen: exitFullscreenFn,\r\n toggleFullscreen,\r\n enterPip,\r\n exitPip: exitPipFn,\r\n togglePip,\r\n };\r\n};\r\n\r\nexport default usePlayer;\r\n"],"names":["formatTime","seconds","isFinite","isNaN","hrs","Math","floor","mins","secs","toString","padStart","percentage","value","total","min","max","clamp","throttle","func","limit","inThrottle","args","setTimeout","isPipSupported","document","pictureInPictureEnabled","getFullscreenElement","fullscreenElement","webkitFullscreenElement","mozFullScreenElement","msFullscreenElement","requestFullscreen","async","element","webkitRequestFullscreen","mozRequestFullScreen","msRequestFullscreen","exitFullscreen","webkitExitFullscreen","mozCancelFullScreen","msExitFullscreen","getBufferedEnd","video","buffered","length","currentTime","i","start","end","ProgressBar","duration","onSeek","thumbnailPreview","disabled","progressRef","useRef","isDragging","setIsDragging","useState","hoverTime","setHoverTime","hoverPosition","setHoverPosition","playedPercent","bufferedPercent","getTimeFromPosition","useCallback","clientX","current","rect","getBoundingClientRect","left","width","handleMouseMove","e","position","time","handleMouseDown","preventDefault","handleMouseUp","handleMouseEnter","handleMouseLeave","useEffect","mouseMoveHandler","window","addEventListener","removeEventListener","handleTouchStart","touch","touches","handleTouchMove","handleTouchEnd","_jsxs","ref","className","onMouseDown","onMouseEnter","onMouseLeave","onMouseMove","nativeEvent","onTouchStart","onTouchMove","onTouchEnd","role","tabIndex","children","_jsx","style","enabled","height","sprites","backgroundImage","backgroundPosition","calculateSpritePosition","interval","backgroundSize","padding","color","fontSize","_duration","index","PlayIcon","size","viewBox","fill","xmlns","d","PauseIcon","VolumeHighIcon","VolumeMediumIcon","VolumeLowIcon","VolumeMuteIcon","FullscreenIcon","FullscreenExitIcon","PipIcon","SettingsIcon","ErrorIcon","ExternalLinkIcon","CheckIcon","VolumeControl","volume","muted","onVolumeChange","onToggleMute","handleSliderChange","newVolume","parseFloat","target","onClick","title","type","step","onChange","background","SettingsMenu","playbackRate","playbackSpeeds","onPlaybackRateChange","qualityEnabled","sources","currentQuality","onQualityChange","captionsEnabled","textTracks","currentTrack","onTrackChange","isOpen","setIsOpen","currentView","setCurrentView","containerRef","handleClickOutside","contains","handleToggle","prev","handleSpeedSelect","speed","handleQualitySelect","quality","handleTrackSelect","track","_Fragment","cursor","border","textAlign","map","source","src","label","srclang","AdOverlay","ad","timeRemaining","canSkip","onSkip","skipText","ceil","clickThrough","stopPropagation","ERROR_MESSAGES","ErrorDisplay","error","onRetry","errorCode","code","message","Loader","visible","DEFAULT_HOTKEYS","play","mute","fullscreen","pip","seekForward","seekBackward","volumeUp","volumeDown","useKeyboard","hotkeys","onPlay","onMute","onFullscreen","onPip","onVolume","mergedHotkeys","handleKeyDown","event","tagName","isContentEditable","container","activeElement","key","shiftKey","Infinity","parseVastXml","xmlString","doc","DOMParser","parseFromString","querySelector","ads","querySelectorAll","forEach","adElement","inLine","id","getAttribute","textContent","description","undefined","creatives","skipOffset","clickTracking","mediaFiles","trackingEvents","creative","linear","durationStr","parseVastDuration","skipOffsetAttr","mediaFile","url","trim","push","parseInt","bitrate","videoClicks","ct","tracking","impressionUrls","impression","includes","parts","split","fetchVastAd","config","response","fetch","method","headers","Accept","ok","text","result","skipDelay","selectBestMediaFile","createElement","supportedFiles","filter","file","canPlayType","sort","a","b","resA","resB","test","navigator","userAgent","fireTrackingPixel","Image","fireTrackingPixels","urls","convertToAdInfo","useVast","vastConfig","videoRef","onAdStart","onAdEnd","onAdSkip","onAdError","isAdPlaying","setIsAdPlaying","currentAd","setCurrentAd","adTimeRemaining","setAdTimeRemaining","setCanSkip","currentVastAd","originalSrc","originalTime","playedPositions","Set","adIntervalRef","vastConfigs","Array","isArray","playAd","Error","currentSrc","setInterval","remaining","percent","firstQuartile","midpoint","thirdQuartile","handleAdEnd","complete","endAd","once","clearInterval","catch","skipAd","skip","handleAdClick","open","checkForAd","midrollTime","has","shouldPlay","add","PlexVideoPlayer","forwardRef","props","poster","autoPlay","loop","controls","playbackSpeed","volumeEnabled","initialVolume","progressBar","timeDisplay","qualitySelector","vast","keyboard","accentColor","controlsTimeout","doubleClickFullscreen","clickToPlay","preload","onPause","onEnded","onTimeUpdate","onProgress","onFullscreenChange","onPipChange","onError","onReady","controlsTimeoutRef","lastClickRef","isPlaying","setIsPlaying","setCurrentTime","setDuration","setBuffered","volumeState","setVolumeState","isMuted","setIsMuted","isFullscreen","setIsFullscreen","isPipActive","setIsPipActive","isLoading","setIsLoading","setError","showControls","setShowControls","setPlaybackRateState","setCurrentQuality","currentTrackLabel","setCurrentTrackLabel","useMemo","err","pause","stop","togglePlay","paused","seek","setVolume","unmute","toggleMute","enterFullscreen","toggleFullscreen","enterPip","requestPictureInPicture","exitPip","pictureInPictureElement","exitPictureInPicture","togglePip","setPlaybackRate","rate","setQuality","qualityLabel","find","s","wasPlaying","setTextTrack","trackLabel","mode","hideControls","showControlsTemporarily","clearTimeout","handleVideoClick","now","Date","handlePlay","handlePause","handleEnded","handleTimeUpdate","handleDurationChange","handleProgressEvent","handleVolumeChangeEvent","handleLoadedMetadata","handleWaiting","handlePlaying","handleCanPlay","handleErrorEvent","handleFullscreenChange","isFs","handleEnterPip","handleLeavePip","delta","handleRetry","load","useImperativeHandle","getCurrentTime","getDuration","getVolume","isPip","getVideoElement","videoSrc","cssVariables","playsInline","slice","kind","srcLang","default","displayName","initialState","isPaused","isEnded","isBuffering","isSeeking","wait","timeout","extension","pop","toLowerCase","mp4","webm","ogg","ogv","m3u8","mpd","mov","avi","mkv","random","substring","fullscreenEnabled","webkitFullscreenEnabled","mozFullScreenEnabled","msFullscreenEnabled","maxTouchPoints","Number","options","initialRate","state","setState","updateState","updates","clampedTime","clampedVolume","exitFullscreenFn","exitPipFn","handlers","loadedmetadata","timeupdate","ended","waiting","canplay","seeking","seeked","volumechange","ratechange","enterpictureinpicture","leavepictureinpicture","Object","entries","handler"],"mappings":"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"}