@frameset/plex-player 2.0.6 → 2.0.7

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/utils/vast.ts","../src/hooks/useVast.ts","../src/utils/helpers.ts","../src/components/PlexVideoPlayer.tsx","../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/usePlayer.ts","../src/hooks/useKeyboard.ts"],"sourcesContent":["// 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","// 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","// PlexVideo Player - Main Component with Tailwind CSS\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 { 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\n// Icons as inline SVG components\r\nconst PlayIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-6 h-6\">\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n);\r\n\r\nconst PauseIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-6 h-6\">\r\n <path d=\"M6 19h4V5H6v14zm8-14v14h4V5h-4z\" />\r\n </svg>\r\n);\r\n\r\nconst VolumeHighIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\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\nconst VolumeMuteIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\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\nconst FullscreenIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\" />\r\n </svg>\r\n);\r\n\r\nconst FullscreenExitIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\" />\r\n </svg>\r\n);\r\n\r\nconst PipIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\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\nconst SettingsIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\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\nconst RepeatIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-4 h-4\">\r\n <path d=\"M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z\" />\r\n </svg>\r\n);\r\n\r\nconst CopyIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-4 h-4\">\r\n <path d=\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z\" />\r\n </svg>\r\n);\r\n\r\nconst SpeedIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-4 h-4\">\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\nconst PlaylistIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style={{ width: 20, height: 20 }}>\r\n <path d=\"M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z\" />\r\n </svg>\r\n);\r\n\r\nconst CastIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style={{ width: 20, height: 20 }}>\r\n <path d=\"M21 3H3c-1.1 0-2 .9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM1 18v3h3c0-1.66-1.34-3-3-3zm0-4v2c2.76 0 5 2.24 5 5h2c0-3.87-3.13-7-7-7zm0-4v2c4.97 0 9 4.03 9 9h2c0-6.08-4.93-11-11-11z\" />\r\n </svg>\r\n);\r\n\r\nconst CloseIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style={{ width: 20, height: 20 }}>\r\n <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" />\r\n </svg>\r\n);\r\n\r\n// Extended props with playlist support\r\nexport interface PlaylistItem {\r\n id: string;\r\n src: string;\r\n title: string;\r\n poster?: string;\r\n duration?: number;\r\n}\r\n\r\ninterface ExtendedPlayerProps extends PlexVideoPlayerProps {\r\n playlist?: PlaylistItem[];\r\n onPlaylistItemChange?: (index: number, item: PlaylistItem) => void;\r\n}\r\n\r\nexport const PlexVideoPlayer = forwardRef<PlexVideoPlayerRef, ExtendedPlayerProps>(\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.5, 0.75, 1, 1.25, 1.5, 2],\r\n volume: volumeEnabled = true,\r\n initialVolume = 1,\r\n textTracks = [],\r\n vast,\r\n accentColor = '#f5c518',\r\n controlsTimeout = 3000,\r\n doubleClickFullscreen = true,\r\n clickToPlay = true,\r\n preload = 'metadata',\r\n playlist = [],\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 onPlaylistItemChange,\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 progressRef = 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 [isLoop, setIsLoop] = useState(loop);\r\n const [showSettings, setShowSettings] = useState(false);\r\n const [showPlaylist, setShowPlaylist] = useState(false);\r\n const [hoverTime, setHoverTime] = useState<{ time: number; x: number } | null>(null);\r\n const [previewFrame, setPreviewFrame] = useState<string | null>(null);\r\n const previewCanvasRef = useRef<HTMLCanvasElement>(null);\r\n const previewVideoRef = useRef<HTMLVideoElement>(null);\r\n \r\n // Context menu state\r\n const [contextMenu, setContextMenu] = useState({ x: 0, y: 0, visible: false });\r\n \r\n // Playlist state\r\n const [currentPlaylistIndex, setCurrentPlaylistIndex] = useState(0);\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 // Get current video source\r\n const currentVideoSrc = useMemo(() => {\r\n if (playlist.length > 0 && playlist[currentPlaylistIndex]) {\r\n return playlist[currentPlaylistIndex].src;\r\n }\r\n return sources[0]?.src || '';\r\n }, [playlist, currentPlaylistIndex, sources]);\r\n\r\n const currentPoster = useMemo(() => {\r\n if (playlist.length > 0 && playlist[currentPlaylistIndex]?.poster) {\r\n return playlist[currentPlaylistIndex].poster;\r\n }\r\n return poster;\r\n }, [playlist, currentPlaylistIndex, poster]);\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 videoRef.current?.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 if (videoRef.current?.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 if (videoRef.current) {\r\n videoRef.current.muted = true;\r\n setIsMuted(true);\r\n }\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 if (videoRef.current?.muted) {\r\n unmute();\r\n } else {\r\n mute();\r\n }\r\n }, [mute, unmute]);\r\n\r\n const toggleLoop = useCallback(() => {\r\n if (videoRef.current) {\r\n videoRef.current.loop = !videoRef.current.loop;\r\n setIsLoop(videoRef.current.loop);\r\n }\r\n }, []);\r\n\r\n const enterFullscreen = useCallback(async () => {\r\n try {\r\n await containerRef.current?.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 try {\r\n await videoRef.current?.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 if (videoRef.current) {\r\n videoRef.current.playbackRate = rate;\r\n setPlaybackRateState(rate);\r\n setShowSettings(false);\r\n }\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) video.play();\r\n onQualityChange?.(qualityLabel);\r\n }\r\n }, [sources, onQualityChange]);\r\n\r\n // Playlist functions\r\n const playNextInPlaylist = useCallback(() => {\r\n if (playlist.length === 0) return;\r\n const nextIndex = (currentPlaylistIndex + 1) % playlist.length;\r\n setCurrentPlaylistIndex(nextIndex);\r\n onPlaylistItemChange?.(nextIndex, playlist[nextIndex]);\r\n setTimeout(() => play(), 100);\r\n }, [currentPlaylistIndex, playlist, onPlaylistItemChange, play]);\r\n\r\n // Context menu handlers\r\n const handleContextMenu = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n const rect = containerRef.current?.getBoundingClientRect();\r\n if (rect) {\r\n setContextMenu({ \r\n x: e.clientX - rect.left, \r\n y: e.clientY - rect.top, \r\n visible: true \r\n });\r\n }\r\n }, []);\r\n\r\n const closeContextMenu = useCallback(() => {\r\n setContextMenu(prev => ({ ...prev, visible: false }));\r\n }, []);\r\n\r\n const copyVideoUrl = useCallback(() => {\r\n navigator.clipboard.writeText(currentVideoSrc);\r\n closeContextMenu();\r\n }, [currentVideoSrc, closeContextMenu]);\r\n\r\n // Progress bar click handler\r\n const handleProgressClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {\r\n const rect = progressRef.current?.getBoundingClientRect();\r\n if (rect && duration) {\r\n const percent = (e.clientX - rect.left) / rect.width;\r\n seek(percent * duration);\r\n }\r\n }, [duration, seek]);\r\n\r\n // Progress bar hover handler for time preview with frame capture\r\n const handleProgressHover = useCallback((e: React.MouseEvent<HTMLDivElement>) => {\r\n const rect = progressRef.current?.getBoundingClientRect();\r\n if (rect && duration) {\r\n const percent = (e.clientX - rect.left) / rect.width;\r\n const time = Math.max(0, Math.min(duration, percent * duration));\r\n setHoverTime({ time, x: e.clientX - rect.left });\r\n \r\n // Generate frame preview using hidden video element\r\n if (previewVideoRef.current && previewCanvasRef.current) {\r\n const previewVideo = previewVideoRef.current;\r\n const canvas = previewCanvasRef.current;\r\n const ctx = canvas.getContext('2d');\r\n \r\n if (Math.abs(previewVideo.currentTime - time) > 1) {\r\n previewVideo.currentTime = time;\r\n }\r\n \r\n previewVideo.onseeked = () => {\r\n if (ctx) {\r\n canvas.width = 160;\r\n canvas.height = 90;\r\n ctx.drawImage(previewVideo, 0, 0, 160, 90);\r\n setPreviewFrame(canvas.toDataURL('image/jpeg', 0.5));\r\n }\r\n };\r\n }\r\n }\r\n }, [duration]);\r\n\r\n const handleProgressLeave = useCallback(() => {\r\n setHoverTime(null);\r\n setPreviewFrame(null);\r\n }, []);\r\n\r\n // Hide controls logic\r\n const hideControls = useCallback(() => {\r\n if (isPlaying && !showSettings) {\r\n setShowControls(false);\r\n }\r\n }, [isPlaying, showSettings]);\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 closeContextMenu();\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, closeContextMenu]);\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 = () => { setIsPlaying(true); onPlay?.(); };\r\n const handlePause = () => { setIsPlaying(false); onPause?.(); };\r\n const handleEnded = () => {\r\n setIsPlaying(false);\r\n onEnded?.();\r\n if (playlist.length > 0) playNextInPlaylist();\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 const handleDurationChange = () => setDuration(video.duration);\r\n const handleProgressEvent = () => {\r\n if (video.buffered.length > 0) {\r\n const bufferedEnd = video.buffered.end(video.buffered.length - 1);\r\n setBuffered((bufferedEnd / video.duration) * 100);\r\n onProgress?.((bufferedEnd / video.duration) * 100);\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 const handleLoadedMetadata = () => {\r\n setDuration(video.duration);\r\n setIsLoading(false);\r\n onReady?.();\r\n };\r\n const handleWaiting = () => setIsLoading(true);\r\n const handlePlaying = () => setIsLoading(false);\r\n const handleCanPlay = () => setIsLoading(false);\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, playlist.length, playNextInPlaylist]);\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 document.addEventListener('fullscreenchange', handleFullscreenChange);\r\n return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);\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 const handleEnterPip = () => { setIsPipActive(true); onPipChange?.(true); };\r\n const handleLeavePip = () => { setIsPipActive(false); onPipChange?.(false); };\r\n video.addEventListener('enterpictureinpicture', handleEnterPip);\r\n video.addEventListener('leavepictureinpicture', handleLeavePip);\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 useEffect(() => {\r\n const handleKeyDown = (e: KeyboardEvent) => {\r\n if (!containerRef.current?.contains(document.activeElement)) return;\r\n \r\n switch (e.key) {\r\n case ' ':\r\n case 'k':\r\n e.preventDefault();\r\n togglePlay();\r\n break;\r\n case 'm':\r\n e.preventDefault();\r\n toggleMute();\r\n break;\r\n case 'f':\r\n e.preventDefault();\r\n toggleFullscreen();\r\n break;\r\n case 'ArrowLeft':\r\n e.preventDefault();\r\n seek(currentTime - 10);\r\n break;\r\n case 'ArrowRight':\r\n e.preventDefault();\r\n seek(currentTime + 10);\r\n break;\r\n case 'ArrowUp':\r\n e.preventDefault();\r\n setVolume(volumeState + 0.1);\r\n break;\r\n case 'ArrowDown':\r\n e.preventDefault();\r\n setVolume(volumeState - 0.1);\r\n break;\r\n }\r\n };\r\n \r\n document.addEventListener('keydown', handleKeyDown);\r\n return () => document.removeEventListener('keydown', handleKeyDown);\r\n }, [togglePlay, toggleMute, toggleFullscreen, seek, setVolume, currentTime, volumeState]);\r\n\r\n // Cleanup\r\n useEffect(() => {\r\n return () => {\r\n if (controlsTimeoutRef.current) clearTimeout(controlsTimeoutRef.current);\r\n };\r\n }, []);\r\n\r\n // Close context menu on click outside\r\n useEffect(() => {\r\n const handleClick = () => closeContextMenu();\r\n if (contextMenu.visible) {\r\n document.addEventListener('click', handleClick);\r\n return () => document.removeEventListener('click', handleClick);\r\n }\r\n }, [contextMenu.visible, closeContextMenu]);\r\n\r\n // Imperative handle\r\n useImperativeHandle(ref, () => ({\r\n play, pause, stop, seek, setVolume, mute, unmute, toggleMute,\r\n enterFullscreen, exitFullscreen, toggleFullscreen,\r\n enterPip, exitPip, togglePip, setPlaybackRate, 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 }), [play, pause, stop, seek, setVolume, mute, unmute, toggleMute, enterFullscreen, exitFullscreen, toggleFullscreen, enterPip, exitPip, togglePip, setPlaybackRate, setQuality, isFullscreen, isPipActive]);\r\n\r\n const progressPercent = duration ? (currentTime / duration) * 100 : 0;\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={className}\r\n style={{ \r\n position: isFullscreen ? 'fixed' : 'relative',\r\n inset: isFullscreen ? 0 : undefined,\r\n zIndex: isFullscreen ? 9999 : undefined,\r\n width, \r\n aspectRatio: isFullscreen ? 'unset' : '16/9',\r\n height: isFullscreen ? '100%' : undefined,\r\n backgroundColor: '#000',\r\n overflow: 'hidden',\r\n userSelect: 'none',\r\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\r\n ...style \r\n }}\r\n onMouseMove={handleMouseMove}\r\n onMouseLeave={() => isPlaying && setShowControls(false)}\r\n onContextMenu={handleContextMenu}\r\n tabIndex={0}\r\n >\r\n {/* Video Element */}\r\n <video\r\n ref={videoRef}\r\n style={{ width: '100%', height: '100%', objectFit: 'contain' }}\r\n src={currentVideoSrc}\r\n poster={currentPoster}\r\n autoPlay={autoPlay}\r\n muted={muted}\r\n loop={isLoop}\r\n preload={preload}\r\n playsInline\r\n onClick={handleVideoClick}\r\n >\r\n {textTracks.map((track, i) => (\r\n <track key={i} kind={track.kind} src={track.src} srcLang={track.srclang} label={track.label} default={track.default} />\r\n ))}\r\n </video>\r\n\r\n {/* Loading Spinner */}\r\n {isLoading && !error && (\r\n <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none' }}>\r\n <div \r\n style={{ \r\n width: 48, \r\n height: 48, \r\n border: '4px solid transparent', \r\n borderTopColor: accentColor, \r\n borderRadius: '50%',\r\n animation: 'spin 1s linear infinite',\r\n }}\r\n />\r\n </div>\r\n )}\r\n\r\n {/* Keyframes for spinner */}\r\n <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>\r\n\r\n {/* Error Display */}\r\n {error && (\r\n <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.9)', color: '#fff' }}>\r\n <svg style={{ width: 64, height: 64, marginBottom: 16 }} fill={accentColor} viewBox=\"0 0 24 24\">\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 <p style={{ fontSize: 18, marginBottom: 8 }}>Error loading video</p>\r\n <button \r\n onClick={() => { setError(null); videoRef.current?.load(); }}\r\n style={{ \r\n padding: '8px 24px', \r\n borderRadius: 4, \r\n border: 'none',\r\n backgroundColor: accentColor, \r\n color: '#000', \r\n fontWeight: 500,\r\n cursor: 'pointer',\r\n }}\r\n >\r\n Retry\r\n </button>\r\n </div>\r\n )}\r\n\r\n {/* Ad Overlay */}\r\n {isAdPlaying && currentAd && (\r\n <div style={{ position: 'absolute', inset: 0, zIndex: 20 }}>\r\n <div style={{ position: 'absolute', top: 16, left: 16, display: 'flex', alignItems: 'center', gap: 12, padding: '8px 12px', background: 'rgba(0,0,0,0.7)', borderRadius: 4, color: '#fff', fontSize: 14 }}>\r\n <span style={{ padding: '2px 8px', borderRadius: 4, fontSize: 11, fontWeight: 700, backgroundColor: accentColor, color: '#000' }}>AD</span>\r\n <span>{formatTime(adTimeRemaining)} remaining</span>\r\n </div>\r\n <button\r\n onClick={canSkip ? skipAd : undefined}\r\n disabled={!canSkip}\r\n style={{ \r\n position: 'absolute', \r\n bottom: 80, \r\n right: 16, \r\n padding: '8px 20px', \r\n border: '1px solid #fff', \r\n borderRadius: 4, \r\n background: 'transparent',\r\n color: '#fff',\r\n cursor: canSkip ? 'pointer' : 'not-allowed',\r\n opacity: canSkip ? 1 : 0.5,\r\n }}\r\n >\r\n {canSkip ? 'Skip Ad' : `Skip in ${Math.ceil(adTimeRemaining)}s`}\r\n </button>\r\n </div>\r\n )}\r\n\r\n {/* Context Menu */}\r\n {contextMenu.visible && (\r\n <div \r\n style={{ \r\n position: 'absolute', \r\n left: contextMenu.x, \r\n top: contextMenu.y,\r\n zIndex: 50,\r\n minWidth: 200,\r\n background: 'rgba(26,26,26,0.95)',\r\n backdropFilter: 'blur(8px)',\r\n borderRadius: 8,\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.5)',\r\n overflow: 'hidden',\r\n }}\r\n onClick={e => e.stopPropagation()}\r\n >\r\n {/* Play/Pause */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { togglePlay(); closeContextMenu(); }}\r\n >\r\n {isPlaying ? <PauseIcon /> : <PlayIcon />}\r\n <span style={{ flex: 1 }}>{isPlaying ? 'Pause' : 'Play'}</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>Space</span>\r\n </button>\r\n \r\n {/* Mute */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { toggleMute(); closeContextMenu(); }}\r\n >\r\n {isMuted ? <VolumeMuteIcon /> : <VolumeHighIcon />}\r\n <span style={{ flex: 1 }}>{isMuted ? 'Unmute' : 'Mute'}</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>M</span>\r\n </button>\r\n\r\n <div style={{ height: 1, background: 'rgba(255,255,255,0.1)', margin: '4px 0' }} />\r\n\r\n {/* Loop */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { toggleLoop(); closeContextMenu(); }}\r\n >\r\n <RepeatIcon />\r\n <span style={{ flex: 1 }}>{isLoop ? 'Disable Loop' : 'Enable Loop'}</span>\r\n {isLoop && <span style={{ color: accentColor }}>✓</span>}\r\n </button>\r\n\r\n {/* Speed */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => {\r\n const speeds = [0.5, 0.75, 1, 1.25, 1.5, 2];\r\n const idx = speeds.indexOf(playbackRate);\r\n setPlaybackRate(speeds[(idx + 1) % speeds.length]);\r\n closeContextMenu();\r\n }}\r\n >\r\n <SpeedIcon />\r\n <span style={{ flex: 1 }}>Speed: {playbackRate}x</span>\r\n </button>\r\n\r\n <div style={{ height: 1, background: 'rgba(255,255,255,0.1)', margin: '4px 0' }} />\r\n\r\n {/* PiP */}\r\n {pip && document.pictureInPictureEnabled && (\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { togglePip(); closeContextMenu(); }}\r\n >\r\n <PipIcon />\r\n <span style={{ flex: 1 }}>Picture in Picture</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>P</span>\r\n </button>\r\n )}\r\n\r\n {/* Fullscreen */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { toggleFullscreen(); closeContextMenu(); }}\r\n >\r\n <FullscreenIcon />\r\n <span style={{ flex: 1 }}>Fullscreen</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>F</span>\r\n </button>\r\n\r\n <div style={{ height: 1, background: 'rgba(255,255,255,0.1)', margin: '4px 0' }} />\r\n\r\n {/* Copy URL */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={copyVideoUrl}\r\n >\r\n <CopyIcon />\r\n <span style={{ flex: 1 }}>Copy Video URL</span>\r\n </button>\r\n\r\n {/* Footer */}\r\n <div style={{ padding: '8px 16px', fontSize: 10, color: 'rgba(255,255,255,0.4)', textAlign: 'center', borderTop: '1px solid rgba(255,255,255,0.1)' }}>\r\n PlexVideo Player © FRAMESET STUDIO\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Controls */}\r\n {controls && !isAdPlaying && (\r\n <div \r\n style={{\r\n position: 'absolute',\r\n bottom: 0,\r\n left: 0,\r\n right: 0,\r\n background: 'linear-gradient(to top, rgba(0,0,0,0.9) 0%, transparent 100%)',\r\n padding: 16,\r\n transition: 'opacity 0.3s',\r\n opacity: showControls ? 1 : 0,\r\n pointerEvents: showControls ? 'auto' : 'none',\r\n }}\r\n >\r\n {/* Progress Bar Container */}\r\n <div \r\n ref={progressRef}\r\n onClick={handleProgressClick}\r\n onMouseMove={handleProgressHover}\r\n onMouseLeave={handleProgressLeave}\r\n style={{\r\n position: 'relative',\r\n height: 8,\r\n background: 'rgba(255,255,255,0.3)',\r\n borderRadius: 4,\r\n marginBottom: 16,\r\n cursor: 'pointer',\r\n }}\r\n >\r\n {/* Buffered bar */}\r\n <div \r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n height: '100%',\r\n width: `${buffered}%`,\r\n background: 'rgba(255,255,255,0.5)',\r\n borderRadius: 4,\r\n }}\r\n />\r\n {/* Played bar */}\r\n <div \r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n height: '100%',\r\n width: `${progressPercent}%`,\r\n background: accentColor,\r\n borderRadius: 4,\r\n }}\r\n />\r\n {/* Handle */}\r\n <div \r\n style={{\r\n position: 'absolute',\r\n top: '50%',\r\n left: `${progressPercent}%`,\r\n transform: 'translate(-50%, -50%)',\r\n width: 16,\r\n height: 16,\r\n borderRadius: '50%',\r\n background: accentColor,\r\n boxShadow: '0 2px 8px rgba(0,0,0,0.5)',\r\n }}\r\n />\r\n {/* Time Preview Tooltip with Frame */}\r\n {hoverTime && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n bottom: '100%',\r\n left: hoverTime.x,\r\n transform: 'translateX(-50%)',\r\n marginBottom: 12,\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n pointerEvents: 'none',\r\n }}\r\n >\r\n {/* Frame Preview */}\r\n {previewFrame && (\r\n <div\r\n style={{\r\n width: 160,\r\n height: 90,\r\n borderRadius: 6,\r\n overflow: 'hidden',\r\n marginBottom: 4,\r\n boxShadow: '0 4px 16px rgba(0,0,0,0.6)',\r\n border: `2px solid ${accentColor}`,\r\n }}\r\n >\r\n <img src={previewFrame} alt=\"\" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />\r\n </div>\r\n )}\r\n {/* Time Label */}\r\n <div\r\n style={{\r\n padding: '4px 10px',\r\n background: 'rgba(0,0,0,0.9)',\r\n color: '#fff',\r\n fontSize: 12,\r\n fontWeight: 600,\r\n borderRadius: 4,\r\n whiteSpace: 'nowrap',\r\n }}\r\n >\r\n {formatTime(hoverTime.time)}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Control Buttons */}\r\n <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>\r\n {/* Play/Pause */}\r\n <button \r\n onClick={togglePlay}\r\n aria-label={isPlaying ? 'Pause' : 'Play'}\r\n style={{\r\n width: 44,\r\n height: 44,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n borderRadius: '50%',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n }}\r\n >\r\n {isPlaying ? <PauseIcon /> : <PlayIcon />}\r\n </button>\r\n\r\n {/* Volume */}\r\n {volumeEnabled && (\r\n <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>\r\n <button \r\n onClick={toggleMute}\r\n aria-label={isMuted ? 'Unmute' : 'Mute'}\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n }}\r\n >\r\n {isMuted || volumeState === 0 ? <VolumeMuteIcon /> : <VolumeHighIcon />}\r\n </button>\r\n <input\r\n type=\"range\"\r\n min=\"0\"\r\n max=\"1\"\r\n step=\"0.01\"\r\n value={isMuted ? 0 : volumeState}\r\n onChange={e => setVolume(parseFloat(e.target.value))}\r\n style={{ \r\n width: 70,\r\n height: 4,\r\n cursor: 'pointer',\r\n accentColor: accentColor,\r\n }}\r\n />\r\n </div>\r\n )}\r\n\r\n {/* Time */}\r\n <div style={{ color: '#fff', fontSize: 13, fontFamily: 'monospace', marginLeft: 8 }}>\r\n {formatTime(currentTime)} / {formatTime(duration)}\r\n </div>\r\n\r\n {/* Spacer */}\r\n <div style={{ flex: 1 }} />\r\n\r\n {/* Playlist Button */}\r\n {playlist.length > 0 && (\r\n <button \r\n onClick={() => setShowPlaylist(!showPlaylist)}\r\n aria-label=\"Playlist\"\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: showPlaylist ? 'rgba(255,255,255,0.2)' : 'transparent',\r\n border: 'none',\r\n color: accentColor,\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n }}\r\n >\r\n <PlaylistIcon />\r\n </button>\r\n )}\r\n\r\n {/* Cast Button */}\r\n <button \r\n onClick={() => {\r\n // Try to cast via Remote Playback API\r\n const video = videoRef.current;\r\n if (video && 'remote' in video) {\r\n (video as any).remote.prompt().catch(() => {\r\n alert('No casting devices found');\r\n });\r\n } else {\r\n alert('Casting not supported');\r\n }\r\n }}\r\n aria-label=\"Cast\"\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n }}\r\n >\r\n <CastIcon />\r\n </button>\r\n\r\n {/* Settings */}\r\n {playbackSpeed && (\r\n <div style={{ position: 'relative' }}>\r\n <button \r\n onClick={() => setShowSettings(!showSettings)}\r\n aria-label=\"Settings\"\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: showSettings ? 'rgba(255,255,255,0.2)' : 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n }}\r\n >\r\n <SettingsIcon />\r\n </button>\r\n \r\n {showSettings && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n bottom: 48,\r\n right: 0,\r\n minWidth: 160,\r\n background: '#1a1a1a',\r\n borderRadius: 8,\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.5)',\r\n border: '1px solid rgba(255,255,255,0.1)',\r\n zIndex: 100,\r\n overflow: 'hidden',\r\n }}\r\n >\r\n <div style={{ padding: '10px 16px', borderBottom: '1px solid rgba(255,255,255,0.1)', color: 'rgba(255,255,255,0.6)', fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.5 }}>\r\n Playback Speed\r\n </div>\r\n {playbackSpeeds.map(speed => (\r\n <button\r\n key={speed}\r\n onClick={() => setPlaybackRate(speed)}\r\n style={{\r\n width: '100%',\r\n padding: '10px 16px',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'space-between',\r\n background: 'transparent',\r\n border: 'none',\r\n color: playbackRate === speed ? accentColor : '#fff',\r\n cursor: 'pointer',\r\n fontSize: 14,\r\n }}\r\n >\r\n <span>{speed === 1 ? 'Normal' : `${speed}x`}</span>\r\n {playbackRate === speed && <span>✓</span>}\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* PiP */}\r\n {pip && document.pictureInPictureEnabled && (\r\n <button \r\n onClick={togglePip}\r\n aria-label=\"Picture in Picture\"\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n }}\r\n >\r\n <PipIcon />\r\n </button>\r\n )}\r\n\r\n {/* Fullscreen */}\r\n {fullscreen && (\r\n <button \r\n onClick={toggleFullscreen}\r\n aria-label={isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n }}\r\n >\r\n {isFullscreen ? <FullscreenExitIcon /> : <FullscreenIcon />}\r\n </button>\r\n )}\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 onClick={togglePlay}\r\n aria-label=\"Play\"\r\n style={{\r\n position: 'absolute',\r\n top: '50%',\r\n left: '50%',\r\n transform: 'translate(-50%, -50%)',\r\n width: 80,\r\n height: 80,\r\n borderRadius: '50%',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n border: 'none',\r\n cursor: 'pointer',\r\n backgroundColor: accentColor,\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.4)',\r\n transition: 'transform 0.2s',\r\n }}\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"white\" style={{ width: 32, height: 32, marginLeft: 4 }}>\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n </button>\r\n )}\r\n\r\n {/* Playlist Sidebar - slides from right */}\r\n {playlist.length > 0 && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n right: 0,\r\n width: 320,\r\n height: '100%',\r\n background: 'linear-gradient(to left, rgba(0,0,0,0.95) 0%, rgba(0,0,0,0.85) 100%)',\r\n backdropFilter: 'blur(10px)',\r\n transform: showPlaylist ? 'translateX(0)' : 'translateX(100%)',\r\n transition: 'transform 0.3s ease-out',\r\n zIndex: 60,\r\n display: 'flex',\r\n flexDirection: 'column',\r\n borderLeft: '1px solid rgba(255,255,255,0.1)',\r\n }}\r\n >\r\n {/* Header */}\r\n <div style={{ \r\n display: 'flex', \r\n alignItems: 'center', \r\n justifyContent: 'space-between', \r\n padding: '16px 20px', \r\n borderBottom: '1px solid rgba(255,255,255,0.1)',\r\n }}>\r\n <div>\r\n <div style={{ color: accentColor, fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 1, marginBottom: 2 }}>\r\n Playlist\r\n </div>\r\n <div style={{ color: '#fff', fontSize: 16, fontWeight: 600 }}>\r\n {playlist.length} videos\r\n </div>\r\n </div>\r\n <button\r\n onClick={() => setShowPlaylist(false)}\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'rgba(255,255,255,0.1)',\r\n border: 'none',\r\n borderRadius: '50%',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n }}\r\n >\r\n <CloseIcon />\r\n </button>\r\n </div>\r\n \r\n {/* Playlist Items */}\r\n <div style={{ flex: 1, overflowY: 'auto', padding: '8px 0' }}>\r\n {playlist.map((item, index) => (\r\n <button\r\n key={item.id}\r\n onClick={() => {\r\n setCurrentPlaylistIndex(index);\r\n onPlaylistItemChange?.(index, item);\r\n setTimeout(() => play(), 100);\r\n }}\r\n style={{\r\n width: '100%',\r\n padding: '12px 20px',\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: 12,\r\n background: currentPlaylistIndex === index ? `${accentColor}20` : 'transparent',\r\n border: 'none',\r\n borderLeft: currentPlaylistIndex === index ? `3px solid ${accentColor}` : '3px solid transparent',\r\n cursor: 'pointer',\r\n textAlign: 'left',\r\n transition: 'background 0.2s',\r\n }}\r\n >\r\n {/* Thumbnail */}\r\n <div style={{\r\n width: 100,\r\n height: 56,\r\n borderRadius: 6,\r\n overflow: 'hidden',\r\n background: '#333',\r\n flexShrink: 0,\r\n position: 'relative',\r\n }}>\r\n {item.poster ? (\r\n <img src={item.poster} alt=\"\" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />\r\n ) : (\r\n <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'rgba(255,255,255,0.3)' }}>\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style={{ width: 24, height: 24 }}>\r\n <path d=\"M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4h-4z\" />\r\n </svg>\r\n </div>\r\n )}\r\n {/* Playing indicator */}\r\n {currentPlaylistIndex === index && (\r\n <div style={{\r\n position: 'absolute',\r\n inset: 0,\r\n background: 'rgba(0,0,0,0.5)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n }}>\r\n <div style={{ color: accentColor, fontSize: 20 }}>▶</div>\r\n </div>\r\n )}\r\n {/* Duration badge */}\r\n {item.duration && (\r\n <div style={{\r\n position: 'absolute',\r\n bottom: 4,\r\n right: 4,\r\n padding: '2px 6px',\r\n background: 'rgba(0,0,0,0.8)',\r\n borderRadius: 3,\r\n fontSize: 10,\r\n color: '#fff',\r\n fontWeight: 500,\r\n }}>\r\n {formatTime(item.duration)}\r\n </div>\r\n )}\r\n </div>\r\n \r\n {/* Info */}\r\n <div style={{ flex: 1, minWidth: 0 }}>\r\n <div style={{ \r\n color: currentPlaylistIndex === index ? accentColor : '#fff',\r\n fontSize: 14,\r\n fontWeight: currentPlaylistIndex === index ? 600 : 400,\r\n overflow: 'hidden',\r\n textOverflow: 'ellipsis',\r\n whiteSpace: 'nowrap',\r\n marginBottom: 4,\r\n }}>\r\n {item.title}\r\n </div>\r\n <div style={{ color: 'rgba(255,255,255,0.5)', fontSize: 12 }}>\r\n Video {index + 1}\r\n </div>\r\n </div>\r\n </button>\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Hidden video for frame preview */}\r\n <video\r\n ref={previewVideoRef}\r\n src={currentVideoSrc}\r\n style={{ display: 'none' }}\r\n muted\r\n preload=\"metadata\"\r\n />\r\n \r\n {/* Hidden canvas for frame capture */}\r\n <canvas ref={previewCanvasRef} style={{ display: 'none' }} />\r\n </div>\r\n );\r\n }\r\n);\r\n\r\nPlexVideoPlayer.displayName = 'PlexVideoPlayer';\r\n\r\nexport default PlexVideoPlayer;\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\r\nexport const RepeatIcon: 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 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z\" />\r\n </svg>\r\n);\r\n\r\nexport const CopyIcon: 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 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z\" />\r\n </svg>\r\n);\r\n\r\nexport const DownloadIcon: 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 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z\" />\r\n </svg>\r\n);\r\n\r\nexport const ChevronUpIcon: 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 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z\" />\r\n </svg>\r\n);\r\n\r\nexport const ChevronDownIcon: 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.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z\" />\r\n </svg>\r\n);\r\n\r\nexport const CloseIcon: 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 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" />\r\n </svg>\r\n);\r\n\r\nexport const PlaylistIcon: 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 10h12v2H4zm0-4h12v2H4zm0 8h8v2H4zm10 0v6l5-3z\" />\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","// 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"],"names":["parseVastXml","xmlString","doc","DOMParser","parseFromString","querySelector","ads","error","querySelectorAll","forEach","adElement","inLine","id","getAttribute","title","textContent","description","undefined","creatives","skipOffset","clickThrough","duration","clickTracking","mediaFiles","trackingEvents","creative","linear","durationStr","parseVastDuration","skipOffsetAttr","mediaFile","url","trim","push","type","width","parseInt","height","bitrate","videoClicks","ct","tracking","event","impressionUrls","impression","length","includes","parts","split","parseFloat","fetchVastAd","async","config","response","fetch","method","headers","Accept","ok","text","result","ad","skipDelay","selectBestMediaFile","video","document","createElement","supportedFiles","filter","file","canPlayType","sort","a","b","resA","resB","test","navigator","userAgent","fireTrackingPixel","Image","src","fireTrackingPixels","urls","convertToAdInfo","useVast","vastConfig","videoRef","onAdStart","onAdEnd","onAdSkip","onAdError","isAdPlaying","setIsAdPlaying","useState","currentAd","setCurrentAd","adTimeRemaining","setAdTimeRemaining","canSkip","setCanSkip","currentVastAd","useRef","originalSrc","originalTime","playedPositions","Set","adIntervalRef","vastConfigs","Array","isArray","playAd","useCallback","current","Error","currentSrc","currentTime","play","start","window","setInterval","remaining","Math","max","percent","firstQuartile","midpoint","thirdQuartile","handleAdEnd","complete","endAd","addEventListener","once","clearInterval","catch","skipAd","skip","handleAdClick","open","checkForAd","position","key","midrollTime","has","shouldPlay","add","useEffect","formatTime","seconds","isFinite","isNaN","hrs","floor","mins","secs","toString","padStart","parseTime","time","map","Number","percentage","value","total","min","clamp","throttle","func","limit","inThrottle","args","setTimeout","debounce","wait","timeout","clearTimeout","isFullscreenSupported","fullscreenEnabled","webkitFullscreenEnabled","mozFullScreenEnabled","msFullscreenEnabled","isPipSupported","pictureInPictureEnabled","getFullscreenElement","fullscreenElement","webkitFullscreenElement","mozFullScreenElement","msFullscreenElement","requestFullscreen","element","webkitRequestFullscreen","mozRequestFullScreen","msRequestFullscreen","exitFullscreen","webkitExitFullscreen","mozCancelFullScreen","msExitFullscreen","detectVideoType","extension","pop","toLowerCase","mp4","webm","ogg","ogv","m3u8","mpd","mov","avi","mkv","isHlsNativelySupported","generateId","random","substring","isMobile","isTouchDevice","maxTouchPoints","getBufferedEnd","buffered","i","end","PlayIcon","_jsx","viewBox","fill","className","children","d","PauseIcon","VolumeHighIcon","VolumeMuteIcon","FullscreenIcon","FullscreenExitIcon","PipIcon","SettingsIcon","RepeatIcon","CopyIcon","SpeedIcon","PlaylistIcon","style","CastIcon","CloseIcon","PlexVideoPlayer","forwardRef","props","ref","poster","autoPlay","muted","loop","controls","pip","fullscreen","playbackSpeed","playbackSpeeds","volume","volumeEnabled","initialVolume","textTracks","vast","accentColor","controlsTimeout","doubleClickFullscreen","clickToPlay","preload","playlist","onPlay","onPause","onEnded","onTimeUpdate","onProgress","onVolumeChange","onFullscreenChange","onPipChange","onQualityChange","onError","onReady","onPlaylistItemChange","containerRef","progressRef","controlsTimeoutRef","lastClickRef","isPlaying","setIsPlaying","setCurrentTime","setDuration","setBuffered","volumeState","setVolumeState","isMuted","setIsMuted","isFullscreen","setIsFullscreen","isPipActive","setIsPipActive","isLoading","setIsLoading","setError","showControls","setShowControls","playbackRate","setPlaybackRateState","isLoop","setIsLoop","showSettings","setShowSettings","showPlaylist","setShowPlaylist","hoverTime","setHoverTime","previewFrame","setPreviewFrame","previewCanvasRef","previewVideoRef","contextMenu","setContextMenu","x","y","visible","currentPlaylistIndex","setCurrentPlaylistIndex","sources","useMemo","currentVideoSrc","currentPoster","err","pause","stop","togglePlay","paused","seek","setVolume","newVolume","mute","unmute","toggleMute","toggleLoop","enterFullscreen","toggleFullscreen","enterPip","requestPictureInPicture","exitPip","pictureInPictureElement","exitPictureInPicture","togglePip","setPlaybackRate","rate","setQuality","qualityLabel","source","find","s","label","quality","wasPlaying","playNextInPlaylist","nextIndex","handleContextMenu","e","preventDefault","rect","getBoundingClientRect","clientX","left","clientY","top","closeContextMenu","prev","copyVideoUrl","clipboard","writeText","handleProgressClick","handleProgressHover","previewVideo","canvas","ctx","getContext","abs","onseeked","drawImage","toDataURL","handleProgressLeave","hideControls","showControlsTemporarily","handleMouseMove","handleVideoClick","now","Date","handlePlay","handlePause","handleEnded","handleTimeUpdate","handleDurationChange","handleProgressEvent","bufferedEnd","handleVolumeChangeEvent","handleLoadedMetadata","handleWaiting","handlePlaying","handleCanPlay","handleErrorEvent","removeEventListener","handleFullscreenChange","isFs","handleEnterPip","handleLeavePip","handleKeyDown","contains","activeElement","handleClick","useImperativeHandle","getCurrentTime","getDuration","getVolume","isPip","getVideoElement","progressPercent","_jsxs","inset","zIndex","aspectRatio","backgroundColor","overflow","userSelect","fontFamily","onMouseMove","onMouseLeave","onContextMenu","tabIndex","objectFit","playsInline","onClick","track","kind","srcLang","srclang","default","display","alignItems","justifyContent","pointerEvents","border","borderTopColor","borderRadius","animation","flexDirection","background","color","marginBottom","fontSize","load","padding","fontWeight","cursor","gap","disabled","bottom","right","opacity","ceil","minWidth","backdropFilter","boxShadow","stopPropagation","textAlign","flex","margin","speeds","idx","indexOf","borderTop","transition","transform","alt","whiteSpace","step","onChange","target","marginLeft","remote","prompt","alert","borderBottom","textTransform","letterSpacing","speed","borderLeft","overflowY","item","index","flexShrink","textOverflow","displayName","ProgressBar","onSeek","thumbnailPreview","isDragging","setIsDragging","hoverPosition","setHoverPosition","playedPercent","bufferedPercent","getTimeFromPosition","handleMouseDown","handleMouseUp","handleMouseEnter","handleMouseLeave","mouseMoveHandler","handleTouchStart","touch","touches","handleTouchMove","handleTouchEnd","onMouseDown","onMouseEnter","nativeEvent","onTouchStart","onTouchMove","onTouchEnd","role","enabled","sprites","backgroundImage","backgroundPosition","calculateSpritePosition","interval","backgroundSize","_duration","size","xmlns","VolumeMediumIcon","VolumeLowIcon","PipExitIcon","CaptionsIcon","QualityIcon","ForwardIcon","RewindIcon","SkipNextIcon","SkipPrevIcon","ErrorIcon","ExternalLinkIcon","CheckIcon","VolumeControl","onToggleMute","handleSliderChange","SettingsMenu","onPlaybackRateChange","qualityEnabled","currentQuality","captionsEnabled","currentTrack","onTrackChange","isOpen","setIsOpen","currentView","setCurrentView","handleClickOutside","handleToggle","handleSpeedSelect","handleQualitySelect","handleTrackSelect","_Fragment","AdOverlay","timeRemaining","onSkip","skipText","ERROR_MESSAGES","ErrorDisplay","onRetry","errorCode","code","message","Loader","initialState","isPaused","isEnded","isBuffering","isSeeking","usePlayer","options","initialRate","state","setState","updateState","updates","clampedTime","clampedVolume","container","exitFullscreenFn","exitPipFn","handlers","loadedmetadata","timeupdate","ended","waiting","canplay","seeking","seeked","volumechange","ratechange","enterpictureinpicture","leavepictureinpicture","Object","entries","handler","DEFAULT_HOTKEYS","seekForward","seekBackward","volumeUp","volumeDown","useKeyboard","hotkeys","onMute","onFullscreen","onPip","onVolume","mergedHotkeys","tagName","isContentEditable","shiftKey","Infinity"],"mappings":"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"}
1
+ {"version":3,"file":"index.esm.js","sources":["../src/utils/vast.ts","../src/hooks/useVast.ts","../src/utils/helpers.ts","../src/components/PlexVideoPlayer.tsx","../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/usePlayer.ts","../src/hooks/useKeyboard.ts"],"sourcesContent":["// 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","// 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","// PlexVideo Player - Main Component with Tailwind CSS\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 { 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\n// Icons as inline SVG components\r\nconst PlayIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-6 h-6\">\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n);\r\n\r\nconst PauseIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-6 h-6\">\r\n <path d=\"M6 19h4V5H6v14zm8-14v14h4V5h-4z\" />\r\n </svg>\r\n);\r\n\r\nconst VolumeHighIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\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\nconst VolumeMuteIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\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\nconst FullscreenIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\" />\r\n </svg>\r\n);\r\n\r\nconst FullscreenExitIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\r\n <path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\" />\r\n </svg>\r\n);\r\n\r\nconst PipIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\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\nconst SettingsIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-5 h-5\">\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\nconst RepeatIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-4 h-4\">\r\n <path d=\"M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z\" />\r\n </svg>\r\n);\r\n\r\nconst CopyIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-4 h-4\">\r\n <path d=\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z\" />\r\n </svg>\r\n);\r\n\r\nconst SpeedIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className=\"w-4 h-4\">\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\nconst PlaylistIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style={{ width: 20, height: 20 }}>\r\n <path d=\"M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z\" />\r\n </svg>\r\n);\r\n\r\nconst CastIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style={{ width: 20, height: 20 }}>\r\n <path d=\"M21 3H3c-1.1 0-2 .9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM1 18v3h3c0-1.66-1.34-3-3-3zm0-4v2c2.76 0 5 2.24 5 5h2c0-3.87-3.13-7-7-7zm0-4v2c4.97 0 9 4.03 9 9h2c0-6.08-4.93-11-11-11z\" />\r\n </svg>\r\n);\r\n\r\nconst CloseIcon = () => (\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style={{ width: 20, height: 20 }}>\r\n <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" />\r\n </svg>\r\n);\r\n\r\n// Extended props with playlist support\r\nexport interface PlaylistItem {\r\n id: string;\r\n src: string;\r\n title: string;\r\n poster?: string;\r\n duration?: number;\r\n}\r\n\r\ninterface ExtendedPlayerProps extends PlexVideoPlayerProps {\r\n playlist?: PlaylistItem[];\r\n onPlaylistItemChange?: (index: number, item: PlaylistItem) => void;\r\n}\r\n\r\nexport const PlexVideoPlayer = forwardRef<PlexVideoPlayerRef, ExtendedPlayerProps>(\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.5, 0.75, 1, 1.25, 1.5, 2],\r\n volume: volumeEnabled = true,\r\n initialVolume = 1,\r\n textTracks = [],\r\n vast,\r\n accentColor = '#f5c518',\r\n controlsTimeout = 3000,\r\n doubleClickFullscreen = true,\r\n clickToPlay = true,\r\n preload = 'metadata',\r\n playlist = [],\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 onPlaylistItemChange,\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 progressRef = 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 [isLoop, setIsLoop] = useState(loop);\r\n const [showSettings, setShowSettings] = useState(false);\r\n const [showPlaylist, setShowPlaylist] = useState(false);\r\n const [hoverTime, setHoverTime] = useState<{ time: number; x: number } | null>(null);\r\n const [previewFrame, setPreviewFrame] = useState<string | null>(null);\r\n const [isMobile, setIsMobile] = useState(false);\r\n const [isTouching, setIsTouching] = useState(false);\r\n const touchStartRef = useRef<{ x: number; y: number; time: number } | null>(null);\r\n const previewCanvasRef = useRef<HTMLCanvasElement>(null);\r\n const previewVideoRef = useRef<HTMLVideoElement>(null);\r\n \r\n // Detect mobile device\r\n useEffect(() => {\r\n const checkMobile = () => {\r\n const mobile = window.matchMedia('(max-width: 768px)').matches || \r\n window.matchMedia('(pointer: coarse)').matches ||\r\n 'ontouchstart' in window;\r\n setIsMobile(mobile);\r\n };\r\n checkMobile();\r\n window.addEventListener('resize', checkMobile);\r\n return () => window.removeEventListener('resize', checkMobile);\r\n }, []);\r\n \r\n // Context menu state\r\n const [contextMenu, setContextMenu] = useState({ x: 0, y: 0, visible: false });\r\n \r\n // Playlist state\r\n const [currentPlaylistIndex, setCurrentPlaylistIndex] = useState(0);\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 // Get current video source\r\n const currentVideoSrc = useMemo(() => {\r\n if (playlist.length > 0 && playlist[currentPlaylistIndex]) {\r\n return playlist[currentPlaylistIndex].src;\r\n }\r\n return sources[0]?.src || '';\r\n }, [playlist, currentPlaylistIndex, sources]);\r\n\r\n const currentPoster = useMemo(() => {\r\n if (playlist.length > 0 && playlist[currentPlaylistIndex]?.poster) {\r\n return playlist[currentPlaylistIndex].poster;\r\n }\r\n return poster;\r\n }, [playlist, currentPlaylistIndex, poster]);\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 videoRef.current?.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 if (videoRef.current?.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 if (videoRef.current) {\r\n videoRef.current.muted = true;\r\n setIsMuted(true);\r\n }\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 if (videoRef.current?.muted) {\r\n unmute();\r\n } else {\r\n mute();\r\n }\r\n }, [mute, unmute]);\r\n\r\n const toggleLoop = useCallback(() => {\r\n if (videoRef.current) {\r\n videoRef.current.loop = !videoRef.current.loop;\r\n setIsLoop(videoRef.current.loop);\r\n }\r\n }, []);\r\n\r\n const enterFullscreen = useCallback(async () => {\r\n try {\r\n await containerRef.current?.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 try {\r\n await videoRef.current?.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 if (videoRef.current) {\r\n videoRef.current.playbackRate = rate;\r\n setPlaybackRateState(rate);\r\n setShowSettings(false);\r\n }\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) video.play();\r\n onQualityChange?.(qualityLabel);\r\n }\r\n }, [sources, onQualityChange]);\r\n\r\n // Playlist functions\r\n const playNextInPlaylist = useCallback(() => {\r\n if (playlist.length === 0) return;\r\n const nextIndex = (currentPlaylistIndex + 1) % playlist.length;\r\n setCurrentPlaylistIndex(nextIndex);\r\n onPlaylistItemChange?.(nextIndex, playlist[nextIndex]);\r\n setTimeout(() => play(), 100);\r\n }, [currentPlaylistIndex, playlist, onPlaylistItemChange, play]);\r\n\r\n // Context menu handlers\r\n const handleContextMenu = useCallback((e: React.MouseEvent) => {\r\n e.preventDefault();\r\n const rect = containerRef.current?.getBoundingClientRect();\r\n if (rect) {\r\n setContextMenu({ \r\n x: e.clientX - rect.left, \r\n y: e.clientY - rect.top, \r\n visible: true \r\n });\r\n }\r\n }, []);\r\n\r\n const closeContextMenu = useCallback(() => {\r\n setContextMenu(prev => ({ ...prev, visible: false }));\r\n }, []);\r\n\r\n const copyVideoUrl = useCallback(() => {\r\n navigator.clipboard.writeText(currentVideoSrc);\r\n closeContextMenu();\r\n }, [currentVideoSrc, closeContextMenu]);\r\n\r\n // Progress bar click handler\r\n const handleProgressClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {\r\n const rect = progressRef.current?.getBoundingClientRect();\r\n if (rect && duration) {\r\n const percent = (e.clientX - rect.left) / rect.width;\r\n seek(percent * duration);\r\n }\r\n }, [duration, seek]);\r\n\r\n // Progress bar hover handler for time preview with frame capture\r\n const handleProgressHover = useCallback((e: React.MouseEvent<HTMLDivElement>) => {\r\n const rect = progressRef.current?.getBoundingClientRect();\r\n if (rect && duration) {\r\n const percent = (e.clientX - rect.left) / rect.width;\r\n const time = Math.max(0, Math.min(duration, percent * duration));\r\n setHoverTime({ time, x: e.clientX - rect.left });\r\n \r\n // Generate frame preview using hidden video element\r\n if (previewVideoRef.current && previewCanvasRef.current) {\r\n const previewVideo = previewVideoRef.current;\r\n const canvas = previewCanvasRef.current;\r\n const ctx = canvas.getContext('2d');\r\n \r\n if (Math.abs(previewVideo.currentTime - time) > 1) {\r\n previewVideo.currentTime = time;\r\n }\r\n \r\n previewVideo.onseeked = () => {\r\n if (ctx) {\r\n canvas.width = 160;\r\n canvas.height = 90;\r\n ctx.drawImage(previewVideo, 0, 0, 160, 90);\r\n setPreviewFrame(canvas.toDataURL('image/jpeg', 0.5));\r\n }\r\n };\r\n }\r\n }\r\n }, [duration]);\r\n\r\n const handleProgressLeave = useCallback(() => {\r\n setHoverTime(null);\r\n setPreviewFrame(null);\r\n }, []);\r\n\r\n // Hide controls logic\r\n const hideControls = useCallback(() => {\r\n if (isPlaying && !showSettings && !showPlaylist) {\r\n setShowControls(false);\r\n }\r\n }, [isPlaying, showSettings, showPlaylist]);\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, isMobile ? 4000 : controlsTimeout);\r\n }\r\n }, [isPlaying, controlsTimeout, hideControls, isMobile]);\r\n\r\n const handleMouseMove = useMemo(\r\n () => throttle(showControlsTemporarily, 200),\r\n [showControlsTemporarily]\r\n );\r\n\r\n // Touch handlers for mobile\r\n const handleTouchStart = useCallback((e: React.TouchEvent) => {\r\n const touch = e.touches[0];\r\n touchStartRef.current = { x: touch.clientX, y: touch.clientY, time: Date.now() };\r\n setIsTouching(true);\r\n showControlsTemporarily();\r\n }, [showControlsTemporarily]);\r\n\r\n const handleTouchMove = useCallback((e: React.TouchEvent) => {\r\n if (!touchStartRef.current) return;\r\n const touch = e.touches[0];\r\n const deltaX = touch.clientX - touchStartRef.current.x;\r\n const deltaY = touch.clientY - touchStartRef.current.y;\r\n \r\n // Horizontal swipe for seeking (only if significant horizontal movement)\r\n if (Math.abs(deltaX) > Math.abs(deltaY) * 2 && Math.abs(deltaX) > 30) {\r\n e.preventDefault();\r\n const seekDelta = (deltaX / window.innerWidth) * duration * 0.5;\r\n const newTime = Math.max(0, Math.min(duration, currentTime + seekDelta));\r\n // Show preview but don't seek yet\r\n setHoverTime({ time: newTime, x: touch.clientX });\r\n }\r\n }, [duration, currentTime]);\r\n\r\n const handleTouchEnd = useCallback((e: React.TouchEvent) => {\r\n if (!touchStartRef.current) return;\r\n \r\n const touchDuration = Date.now() - touchStartRef.current.time;\r\n const touch = e.changedTouches[0];\r\n const deltaX = touch.clientX - touchStartRef.current.x;\r\n const deltaY = touch.clientY - touchStartRef.current.y;\r\n \r\n // Quick tap - toggle play/controls\r\n if (touchDuration < 200 && Math.abs(deltaX) < 10 && Math.abs(deltaY) < 10) {\r\n if (showControls) {\r\n togglePlay();\r\n } else {\r\n showControlsTemporarily();\r\n }\r\n }\r\n // Horizontal swipe - seek\r\n else if (Math.abs(deltaX) > Math.abs(deltaY) * 2 && Math.abs(deltaX) > 50) {\r\n const seekDelta = (deltaX / window.innerWidth) * duration * 0.5;\r\n seek(currentTime + seekDelta);\r\n }\r\n // Double tap on sides - skip 10s\r\n else if (touchDuration < 300) {\r\n const rect = containerRef.current?.getBoundingClientRect();\r\n if (rect) {\r\n const tapX = touch.clientX - rect.left;\r\n if (tapX < rect.width * 0.3) {\r\n seek(currentTime - 10);\r\n } else if (tapX > rect.width * 0.7) {\r\n seek(currentTime + 10);\r\n }\r\n }\r\n }\r\n \r\n touchStartRef.current = null;\r\n setIsTouching(false);\r\n setHoverTime(null);\r\n }, [showControls, togglePlay, showControlsTemporarily, duration, currentTime, seek]);\r\n\r\n // Video click handler\r\n const handleVideoClick = useCallback(() => {\r\n closeContextMenu();\r\n if (isMobile) return; // Handle via touch events on mobile\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, closeContextMenu, isMobile]);\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 = () => { setIsPlaying(true); onPlay?.(); };\r\n const handlePause = () => { setIsPlaying(false); onPause?.(); };\r\n const handleEnded = () => {\r\n setIsPlaying(false);\r\n onEnded?.();\r\n if (playlist.length > 0) playNextInPlaylist();\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 const handleDurationChange = () => setDuration(video.duration);\r\n const handleProgressEvent = () => {\r\n if (video.buffered.length > 0) {\r\n const bufferedEnd = video.buffered.end(video.buffered.length - 1);\r\n setBuffered((bufferedEnd / video.duration) * 100);\r\n onProgress?.((bufferedEnd / video.duration) * 100);\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 const handleLoadedMetadata = () => {\r\n setDuration(video.duration);\r\n setIsLoading(false);\r\n onReady?.();\r\n };\r\n const handleWaiting = () => setIsLoading(true);\r\n const handlePlaying = () => setIsLoading(false);\r\n const handleCanPlay = () => setIsLoading(false);\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, playlist.length, playNextInPlaylist]);\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 document.addEventListener('fullscreenchange', handleFullscreenChange);\r\n return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);\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 const handleEnterPip = () => { setIsPipActive(true); onPipChange?.(true); };\r\n const handleLeavePip = () => { setIsPipActive(false); onPipChange?.(false); };\r\n video.addEventListener('enterpictureinpicture', handleEnterPip);\r\n video.addEventListener('leavepictureinpicture', handleLeavePip);\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 useEffect(() => {\r\n const handleKeyDown = (e: KeyboardEvent) => {\r\n if (!containerRef.current?.contains(document.activeElement)) return;\r\n \r\n switch (e.key) {\r\n case ' ':\r\n case 'k':\r\n e.preventDefault();\r\n togglePlay();\r\n break;\r\n case 'm':\r\n e.preventDefault();\r\n toggleMute();\r\n break;\r\n case 'f':\r\n e.preventDefault();\r\n toggleFullscreen();\r\n break;\r\n case 'ArrowLeft':\r\n e.preventDefault();\r\n seek(currentTime - 10);\r\n break;\r\n case 'ArrowRight':\r\n e.preventDefault();\r\n seek(currentTime + 10);\r\n break;\r\n case 'ArrowUp':\r\n e.preventDefault();\r\n setVolume(volumeState + 0.1);\r\n break;\r\n case 'ArrowDown':\r\n e.preventDefault();\r\n setVolume(volumeState - 0.1);\r\n break;\r\n }\r\n };\r\n \r\n document.addEventListener('keydown', handleKeyDown);\r\n return () => document.removeEventListener('keydown', handleKeyDown);\r\n }, [togglePlay, toggleMute, toggleFullscreen, seek, setVolume, currentTime, volumeState]);\r\n\r\n // Cleanup\r\n useEffect(() => {\r\n return () => {\r\n if (controlsTimeoutRef.current) clearTimeout(controlsTimeoutRef.current);\r\n };\r\n }, []);\r\n\r\n // Close context menu on click outside\r\n useEffect(() => {\r\n const handleClick = () => closeContextMenu();\r\n if (contextMenu.visible) {\r\n document.addEventListener('click', handleClick);\r\n return () => document.removeEventListener('click', handleClick);\r\n }\r\n }, [contextMenu.visible, closeContextMenu]);\r\n\r\n // Imperative handle\r\n useImperativeHandle(ref, () => ({\r\n play, pause, stop, seek, setVolume, mute, unmute, toggleMute,\r\n enterFullscreen, exitFullscreen, toggleFullscreen,\r\n enterPip, exitPip, togglePip, setPlaybackRate, 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 }), [play, pause, stop, seek, setVolume, mute, unmute, toggleMute, enterFullscreen, exitFullscreen, toggleFullscreen, enterPip, exitPip, togglePip, setPlaybackRate, setQuality, isFullscreen, isPipActive]);\r\n\r\n const progressPercent = duration ? (currentTime / duration) * 100 : 0;\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={className}\r\n style={{ \r\n position: isFullscreen ? 'fixed' : 'relative',\r\n inset: isFullscreen ? 0 : undefined,\r\n zIndex: isFullscreen ? 9999 : undefined,\r\n width, \r\n aspectRatio: isFullscreen ? 'unset' : '16/9',\r\n height: isFullscreen ? '100%' : undefined,\r\n backgroundColor: '#000',\r\n overflow: 'hidden',\r\n userSelect: 'none',\r\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\r\n WebkitTapHighlightColor: 'transparent',\r\n touchAction: 'manipulation',\r\n ...style \r\n }}\r\n onMouseMove={!isMobile ? handleMouseMove : undefined}\r\n onMouseLeave={() => !isMobile && isPlaying && setShowControls(false)}\r\n onTouchStart={handleTouchStart}\r\n onTouchMove={handleTouchMove}\r\n onTouchEnd={handleTouchEnd}\r\n onContextMenu={handleContextMenu}\r\n tabIndex={0}\r\n >\r\n {/* Video Element */}\r\n <video\r\n ref={videoRef}\r\n style={{ width: '100%', height: '100%', objectFit: 'contain' }}\r\n src={currentVideoSrc}\r\n poster={currentPoster}\r\n autoPlay={autoPlay}\r\n muted={muted}\r\n loop={isLoop}\r\n preload={preload}\r\n playsInline\r\n onClick={handleVideoClick}\r\n >\r\n {textTracks.map((track, i) => (\r\n <track key={i} kind={track.kind} src={track.src} srcLang={track.srclang} label={track.label} default={track.default} />\r\n ))}\r\n </video>\r\n\r\n {/* Loading Spinner */}\r\n {isLoading && !error && (\r\n <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none' }}>\r\n <div \r\n style={{ \r\n width: 48, \r\n height: 48, \r\n border: '4px solid transparent', \r\n borderTopColor: accentColor, \r\n borderRadius: '50%',\r\n animation: 'spin 1s linear infinite',\r\n }}\r\n />\r\n </div>\r\n )}\r\n\r\n {/* Keyframes for spinner */}\r\n <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>\r\n\r\n {/* Error Display */}\r\n {error && (\r\n <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.9)', color: '#fff' }}>\r\n <svg style={{ width: 64, height: 64, marginBottom: 16 }} fill={accentColor} viewBox=\"0 0 24 24\">\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 <p style={{ fontSize: 18, marginBottom: 8 }}>Error loading video</p>\r\n <button \r\n onClick={() => { setError(null); videoRef.current?.load(); }}\r\n style={{ \r\n padding: '8px 24px', \r\n borderRadius: 4, \r\n border: 'none',\r\n backgroundColor: accentColor, \r\n color: '#000', \r\n fontWeight: 500,\r\n cursor: 'pointer',\r\n }}\r\n >\r\n Retry\r\n </button>\r\n </div>\r\n )}\r\n\r\n {/* Ad Overlay */}\r\n {isAdPlaying && currentAd && (\r\n <div style={{ position: 'absolute', inset: 0, zIndex: 20 }}>\r\n <div style={{ position: 'absolute', top: 16, left: 16, display: 'flex', alignItems: 'center', gap: 12, padding: '8px 12px', background: 'rgba(0,0,0,0.7)', borderRadius: 4, color: '#fff', fontSize: 14 }}>\r\n <span style={{ padding: '2px 8px', borderRadius: 4, fontSize: 11, fontWeight: 700, backgroundColor: accentColor, color: '#000' }}>AD</span>\r\n <span>{formatTime(adTimeRemaining)} remaining</span>\r\n </div>\r\n <button\r\n onClick={canSkip ? skipAd : undefined}\r\n disabled={!canSkip}\r\n style={{ \r\n position: 'absolute', \r\n bottom: 80, \r\n right: 16, \r\n padding: '8px 20px', \r\n border: '1px solid #fff', \r\n borderRadius: 4, \r\n background: 'transparent',\r\n color: '#fff',\r\n cursor: canSkip ? 'pointer' : 'not-allowed',\r\n opacity: canSkip ? 1 : 0.5,\r\n }}\r\n >\r\n {canSkip ? 'Skip Ad' : `Skip in ${Math.ceil(adTimeRemaining)}s`}\r\n </button>\r\n </div>\r\n )}\r\n\r\n {/* Context Menu */}\r\n {contextMenu.visible && (\r\n <div \r\n style={{ \r\n position: 'absolute', \r\n left: contextMenu.x, \r\n top: contextMenu.y,\r\n zIndex: 50,\r\n minWidth: 200,\r\n background: 'rgba(26,26,26,0.95)',\r\n backdropFilter: 'blur(8px)',\r\n borderRadius: 8,\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.5)',\r\n overflow: 'hidden',\r\n }}\r\n onClick={e => e.stopPropagation()}\r\n >\r\n {/* Play/Pause */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { togglePlay(); closeContextMenu(); }}\r\n >\r\n {isPlaying ? <PauseIcon /> : <PlayIcon />}\r\n <span style={{ flex: 1 }}>{isPlaying ? 'Pause' : 'Play'}</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>Space</span>\r\n </button>\r\n \r\n {/* Mute */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { toggleMute(); closeContextMenu(); }}\r\n >\r\n {isMuted ? <VolumeMuteIcon /> : <VolumeHighIcon />}\r\n <span style={{ flex: 1 }}>{isMuted ? 'Unmute' : 'Mute'}</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>M</span>\r\n </button>\r\n\r\n <div style={{ height: 1, background: 'rgba(255,255,255,0.1)', margin: '4px 0' }} />\r\n\r\n {/* Loop */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { toggleLoop(); closeContextMenu(); }}\r\n >\r\n <RepeatIcon />\r\n <span style={{ flex: 1 }}>{isLoop ? 'Disable Loop' : 'Enable Loop'}</span>\r\n {isLoop && <span style={{ color: accentColor }}>✓</span>}\r\n </button>\r\n\r\n {/* Speed */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => {\r\n const speeds = [0.5, 0.75, 1, 1.25, 1.5, 2];\r\n const idx = speeds.indexOf(playbackRate);\r\n setPlaybackRate(speeds[(idx + 1) % speeds.length]);\r\n closeContextMenu();\r\n }}\r\n >\r\n <SpeedIcon />\r\n <span style={{ flex: 1 }}>Speed: {playbackRate}x</span>\r\n </button>\r\n\r\n <div style={{ height: 1, background: 'rgba(255,255,255,0.1)', margin: '4px 0' }} />\r\n\r\n {/* PiP */}\r\n {pip && document.pictureInPictureEnabled && (\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { togglePip(); closeContextMenu(); }}\r\n >\r\n <PipIcon />\r\n <span style={{ flex: 1 }}>Picture in Picture</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>P</span>\r\n </button>\r\n )}\r\n\r\n {/* Fullscreen */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={() => { toggleFullscreen(); closeContextMenu(); }}\r\n >\r\n <FullscreenIcon />\r\n <span style={{ flex: 1 }}>Fullscreen</span>\r\n <span style={{ color: 'rgba(255,255,255,0.5)', fontSize: 11 }}>F</span>\r\n </button>\r\n\r\n <div style={{ height: 1, background: 'rgba(255,255,255,0.1)', margin: '4px 0' }} />\r\n\r\n {/* Copy URL */}\r\n <button \r\n style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', background: 'transparent', border: 'none', color: '#fff', fontSize: 14, cursor: 'pointer', textAlign: 'left' }}\r\n onClick={copyVideoUrl}\r\n >\r\n <CopyIcon />\r\n <span style={{ flex: 1 }}>Copy Video URL</span>\r\n </button>\r\n\r\n {/* Footer */}\r\n <div style={{ padding: '8px 16px', fontSize: 10, color: 'rgba(255,255,255,0.4)', textAlign: 'center', borderTop: '1px solid rgba(255,255,255,0.1)' }}>\r\n PlexVideo Player © FRAMESET STUDIO\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Controls */}\r\n {controls && !isAdPlaying && (\r\n <div \r\n style={{\r\n position: 'absolute',\r\n bottom: 0,\r\n left: 0,\r\n right: 0,\r\n background: 'linear-gradient(to top, rgba(0,0,0,0.9) 0%, transparent 100%)',\r\n padding: isMobile ? '12px 12px 16px' : 16,\r\n paddingTop: isMobile ? 40 : 16,\r\n transition: 'opacity 0.3s',\r\n opacity: showControls ? 1 : 0,\r\n pointerEvents: showControls ? 'auto' : 'none',\r\n }}\r\n >\r\n {/* Progress Bar Container */}\r\n <div \r\n ref={progressRef}\r\n onClick={handleProgressClick}\r\n onMouseMove={!isMobile ? handleProgressHover : undefined}\r\n onMouseLeave={!isMobile ? handleProgressLeave : undefined}\r\n onTouchMove={(e) => {\r\n if (isMobile) {\r\n const rect = progressRef.current?.getBoundingClientRect();\r\n if (rect && duration) {\r\n const touch = e.touches[0];\r\n const percent = Math.max(0, Math.min(1, (touch.clientX - rect.left) / rect.width));\r\n setHoverTime({ time: percent * duration, x: touch.clientX - rect.left });\r\n }\r\n }\r\n }}\r\n onTouchEnd={(e) => {\r\n if (isMobile && hoverTime) {\r\n seek(hoverTime.time);\r\n setHoverTime(null);\r\n }\r\n }}\r\n style={{\r\n position: 'relative',\r\n height: isMobile ? 12 : 8,\r\n background: 'rgba(255,255,255,0.3)',\r\n borderRadius: isMobile ? 6 : 4,\r\n marginBottom: isMobile ? 12 : 16,\r\n cursor: 'pointer',\r\n }}\r\n >\r\n {/* Buffered bar */}\r\n <div \r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n height: '100%',\r\n width: `${buffered}%`,\r\n background: 'rgba(255,255,255,0.5)',\r\n borderRadius: isMobile ? 6 : 4,\r\n }}\r\n />\r\n {/* Played bar */}\r\n <div \r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n height: '100%',\r\n width: `${progressPercent}%`,\r\n background: accentColor,\r\n borderRadius: isMobile ? 6 : 4,\r\n }}\r\n />\r\n {/* Handle */}\r\n <div \r\n style={{\r\n position: 'absolute',\r\n top: '50%',\r\n left: `${progressPercent}%`,\r\n transform: 'translate(-50%, -50%)',\r\n width: isMobile ? 20 : 16,\r\n height: isMobile ? 20 : 16,\r\n borderRadius: '50%',\r\n background: accentColor,\r\n boxShadow: '0 2px 8px rgba(0,0,0,0.5)',\r\n }}\r\n />\r\n {/* Time Preview Tooltip with Frame */}\r\n {hoverTime && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n bottom: '100%',\r\n left: hoverTime.x,\r\n transform: 'translateX(-50%)',\r\n marginBottom: 12,\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n pointerEvents: 'none',\r\n }}\r\n >\r\n {/* Frame Preview */}\r\n {previewFrame && (\r\n <div\r\n style={{\r\n width: 160,\r\n height: 90,\r\n borderRadius: 6,\r\n overflow: 'hidden',\r\n marginBottom: 4,\r\n boxShadow: '0 4px 16px rgba(0,0,0,0.6)',\r\n border: `2px solid ${accentColor}`,\r\n }}\r\n >\r\n <img src={previewFrame} alt=\"\" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />\r\n </div>\r\n )}\r\n {/* Time Label */}\r\n <div\r\n style={{\r\n padding: '4px 10px',\r\n background: 'rgba(0,0,0,0.9)',\r\n color: '#fff',\r\n fontSize: 12,\r\n fontWeight: 600,\r\n borderRadius: 4,\r\n whiteSpace: 'nowrap',\r\n }}\r\n >\r\n {formatTime(hoverTime.time)}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Control Buttons */}\r\n <div style={{ display: 'flex', alignItems: 'center', gap: isMobile ? 4 : 8, flexWrap: 'wrap' }}>\r\n {/* Play/Pause */}\r\n <button \r\n onClick={togglePlay}\r\n aria-label={isPlaying ? 'Pause' : 'Play'}\r\n style={{\r\n width: isMobile ? 48 : 44,\r\n height: isMobile ? 48 : 44,\r\n minWidth: isMobile ? 48 : 44,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n borderRadius: '50%',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n padding: 0,\r\n }}\r\n >\r\n {isPlaying ? <PauseIcon /> : <PlayIcon />}\r\n </button>\r\n\r\n {/* Volume - hide slider on mobile */}\r\n {volumeEnabled && !isMobile && (\r\n <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>\r\n <button \r\n onClick={toggleMute}\r\n aria-label={isMuted ? 'Unmute' : 'Mute'}\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n }}\r\n >\r\n {isMuted || volumeState === 0 ? <VolumeMuteIcon /> : <VolumeHighIcon />}\r\n </button>\r\n <input\r\n type=\"range\"\r\n min=\"0\"\r\n max=\"1\"\r\n step=\"0.01\"\r\n value={isMuted ? 0 : volumeState}\r\n onChange={e => setVolume(parseFloat(e.target.value))}\r\n style={{ \r\n width: 70,\r\n height: 4,\r\n cursor: 'pointer',\r\n accentColor: accentColor,\r\n }}\r\n />\r\n </div>\r\n )}\r\n\r\n {/* Mobile volume button (toggle only) */}\r\n {volumeEnabled && isMobile && (\r\n <button \r\n onClick={toggleMute}\r\n aria-label={isMuted ? 'Unmute' : 'Mute'}\r\n style={{\r\n width: 44,\r\n height: 44,\r\n minWidth: 44,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n padding: 0,\r\n }}\r\n >\r\n {isMuted || volumeState === 0 ? <VolumeMuteIcon /> : <VolumeHighIcon />}\r\n </button>\r\n )}\r\n\r\n {/* Time - compact on mobile */}\r\n <div style={{ \r\n color: '#fff', \r\n fontSize: isMobile ? 11 : 13, \r\n fontFamily: 'monospace', \r\n marginLeft: isMobile ? 4 : 8,\r\n whiteSpace: 'nowrap',\r\n }}>\r\n {formatTime(currentTime)}{!isMobile && ` / ${formatTime(duration)}`}\r\n </div>\r\n\r\n {/* Spacer */}\r\n <div style={{ flex: 1 }} />\r\n\r\n {/* Playlist Button */}\r\n {playlist.length > 0 && (\r\n <button \r\n onClick={() => setShowPlaylist(!showPlaylist)}\r\n aria-label=\"Playlist\"\r\n style={{\r\n width: isMobile ? 44 : 36,\r\n height: isMobile ? 44 : 36,\r\n minWidth: isMobile ? 44 : 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: showPlaylist ? 'rgba(255,255,255,0.2)' : 'transparent',\r\n border: 'none',\r\n color: accentColor,\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n padding: 0,\r\n }}\r\n >\r\n <PlaylistIcon />\r\n </button>\r\n )}\r\n\r\n {/* Cast Button - hide on mobile if not supported */}\r\n {!isMobile && (\r\n <button \r\n onClick={() => {\r\n const video = videoRef.current;\r\n if (video && 'remote' in video) {\r\n (video as any).remote.prompt().catch(() => {\r\n alert('No casting devices found');\r\n });\r\n } else {\r\n alert('Casting not supported');\r\n }\r\n }}\r\n aria-label=\"Cast\"\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n padding: 0,\r\n }}\r\n >\r\n <CastIcon />\r\n </button>\r\n )}\r\n\r\n {/* Settings */}\r\n {playbackSpeed && (\r\n <div style={{ position: 'relative' }}>\r\n <button \r\n onClick={() => setShowSettings(!showSettings)}\r\n aria-label=\"Settings\"\r\n style={{\r\n width: isMobile ? 44 : 36,\r\n height: isMobile ? 44 : 36,\r\n minWidth: isMobile ? 44 : 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: showSettings ? 'rgba(255,255,255,0.2)' : 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n padding: 0,\r\n }}\r\n >\r\n <SettingsIcon />\r\n </button>\r\n \r\n {showSettings && (\r\n <div\r\n style={{\r\n position: isMobile ? 'fixed' : 'absolute',\r\n bottom: isMobile ? 0 : 48,\r\n right: isMobile ? 0 : 0,\r\n left: isMobile ? 0 : undefined,\r\n minWidth: isMobile ? '100%' : 160,\r\n maxHeight: isMobile ? '50vh' : undefined,\r\n background: '#1a1a1a',\r\n borderRadius: isMobile ? '16px 16px 0 0' : 8,\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.5)',\r\n border: isMobile ? 'none' : '1px solid rgba(255,255,255,0.1)',\r\n zIndex: 100,\r\n overflow: 'hidden',\r\n }}\r\n >\r\n <div style={{ padding: '10px 16px', borderBottom: '1px solid rgba(255,255,255,0.1)', color: 'rgba(255,255,255,0.6)', fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.5 }}>\r\n Playback Speed\r\n </div>\r\n {playbackSpeeds.map(speed => (\r\n <button\r\n key={speed}\r\n onClick={() => setPlaybackRate(speed)}\r\n style={{\r\n width: '100%',\r\n padding: isMobile ? '16px 20px' : '10px 16px',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'space-between',\r\n background: 'transparent',\r\n border: 'none',\r\n color: playbackRate === speed ? accentColor : '#fff',\r\n cursor: 'pointer',\r\n fontSize: isMobile ? 16 : 14,\r\n }}\r\n >\r\n <span>{speed === 1 ? 'Normal' : `${speed}x`}</span>\r\n {playbackRate === speed && <span>✓</span>}\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* PiP - hide on mobile (usually not supported well) */}\r\n {pip && !isMobile && document.pictureInPictureEnabled && (\r\n <button \r\n onClick={togglePip}\r\n aria-label=\"Picture in Picture\"\r\n style={{\r\n width: 36,\r\n height: 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n padding: 0,\r\n }}\r\n >\r\n <PipIcon />\r\n </button>\r\n )}\r\n\r\n {/* Fullscreen */}\r\n {fullscreen && (\r\n <button \r\n onClick={toggleFullscreen}\r\n aria-label={isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}\r\n style={{\r\n width: isMobile ? 44 : 36,\r\n height: isMobile ? 44 : 36,\r\n minWidth: isMobile ? 44 : 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'transparent',\r\n border: 'none',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n borderRadius: 4,\r\n padding: 0,\r\n }}\r\n >\r\n {isFullscreen ? <FullscreenExitIcon /> : <FullscreenIcon />}\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Mobile gesture hint overlay */}\r\n {isMobile && isTouching && hoverTime && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n top: '50%',\r\n left: '50%',\r\n transform: 'translate(-50%, -50%)',\r\n padding: '12px 24px',\r\n background: 'rgba(0,0,0,0.8)',\r\n borderRadius: 8,\r\n color: '#fff',\r\n fontSize: 18,\r\n fontWeight: 600,\r\n pointerEvents: 'none',\r\n }}\r\n >\r\n {formatTime(hoverTime.time)}\r\n </div>\r\n )}\r\n\r\n {/* Big Play Button */}\r\n {!isPlaying && !isLoading && !error && !isAdPlaying && (\r\n <button\r\n onClick={togglePlay}\r\n aria-label=\"Play\"\r\n style={{\r\n position: 'absolute',\r\n top: '50%',\r\n left: '50%',\r\n transform: 'translate(-50%, -50%)',\r\n width: isMobile ? 70 : 80,\r\n height: isMobile ? 70 : 80,\r\n borderRadius: '50%',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n border: 'none',\r\n cursor: 'pointer',\r\n backgroundColor: accentColor,\r\n boxShadow: '0 8px 32px rgba(0,0,0,0.4)',\r\n transition: 'transform 0.2s',\r\n padding: 0,\r\n }}\r\n >\r\n <svg viewBox=\"0 0 24 24\" fill=\"white\" style={{ width: isMobile ? 28 : 32, height: isMobile ? 28 : 32, marginLeft: 4 }}>\r\n <path d=\"M8 5v14l11-7z\" />\r\n </svg>\r\n </button>\r\n )}\r\n\r\n {/* Playlist Sidebar - slides from right (full screen on mobile) */}\r\n {playlist.length > 0 && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n top: isMobile ? 'auto' : 0,\r\n bottom: isMobile ? 0 : undefined,\r\n right: 0,\r\n left: isMobile ? 0 : undefined,\r\n width: isMobile ? '100%' : 320,\r\n height: isMobile ? '70%' : '100%',\r\n maxHeight: isMobile ? '70vh' : undefined,\r\n background: isMobile \r\n ? 'linear-gradient(to top, rgba(0,0,0,0.98) 0%, rgba(0,0,0,0.95) 100%)'\r\n : 'linear-gradient(to left, rgba(0,0,0,0.95) 0%, rgba(0,0,0,0.85) 100%)',\r\n backdropFilter: 'blur(10px)',\r\n transform: showPlaylist \r\n ? 'translateY(0)' \r\n : (isMobile ? 'translateY(100%)' : 'translateX(100%)'),\r\n transition: 'transform 0.3s ease-out',\r\n zIndex: 60,\r\n display: 'flex',\r\n flexDirection: 'column',\r\n borderLeft: isMobile ? 'none' : '1px solid rgba(255,255,255,0.1)',\r\n borderTop: isMobile ? '1px solid rgba(255,255,255,0.1)' : 'none',\r\n borderRadius: isMobile ? '20px 20px 0 0' : 0,\r\n }}\r\n >\r\n {/* Drag handle for mobile */}\r\n {isMobile && (\r\n <div style={{ \r\n width: 40, \r\n height: 4, \r\n background: 'rgba(255,255,255,0.3)', \r\n borderRadius: 2, \r\n margin: '12px auto 8px',\r\n }} />\r\n )}\r\n \r\n {/* Header */}\r\n <div style={{ \r\n display: 'flex', \r\n alignItems: 'center', \r\n justifyContent: 'space-between', \r\n padding: isMobile ? '12px 20px 16px' : '16px 20px', \r\n borderBottom: '1px solid rgba(255,255,255,0.1)',\r\n }}>\r\n <div>\r\n <div style={{ color: accentColor, fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 1, marginBottom: 2 }}>\r\n Playlist\r\n </div>\r\n <div style={{ color: '#fff', fontSize: 16, fontWeight: 600 }}>\r\n {playlist.length} videos\r\n </div>\r\n </div>\r\n <button\r\n onClick={() => setShowPlaylist(false)}\r\n style={{\r\n width: isMobile ? 44 : 36,\r\n height: isMobile ? 44 : 36,\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n background: 'rgba(255,255,255,0.1)',\r\n border: 'none',\r\n borderRadius: '50%',\r\n color: '#fff',\r\n cursor: 'pointer',\r\n }}\r\n >\r\n <CloseIcon />\r\n </button>\r\n </div>\r\n \r\n {/* Playlist Items */}\r\n <div style={{ flex: 1, overflowY: 'auto', padding: '8px 0' }}>\r\n {playlist.map((item, index) => (\r\n <button\r\n key={item.id}\r\n onClick={() => {\r\n setCurrentPlaylistIndex(index);\r\n onPlaylistItemChange?.(index, item);\r\n setTimeout(() => play(), 100);\r\n }}\r\n style={{\r\n width: '100%',\r\n padding: '12px 20px',\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: 12,\r\n background: currentPlaylistIndex === index ? `${accentColor}20` : 'transparent',\r\n border: 'none',\r\n borderLeft: currentPlaylistIndex === index ? `3px solid ${accentColor}` : '3px solid transparent',\r\n cursor: 'pointer',\r\n textAlign: 'left',\r\n transition: 'background 0.2s',\r\n }}\r\n >\r\n {/* Thumbnail */}\r\n <div style={{\r\n width: 100,\r\n height: 56,\r\n borderRadius: 6,\r\n overflow: 'hidden',\r\n background: '#333',\r\n flexShrink: 0,\r\n position: 'relative',\r\n }}>\r\n {item.poster ? (\r\n <img src={item.poster} alt=\"\" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />\r\n ) : (\r\n <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'rgba(255,255,255,0.3)' }}>\r\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style={{ width: 24, height: 24 }}>\r\n <path d=\"M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4h-4z\" />\r\n </svg>\r\n </div>\r\n )}\r\n {/* Playing indicator */}\r\n {currentPlaylistIndex === index && (\r\n <div style={{\r\n position: 'absolute',\r\n inset: 0,\r\n background: 'rgba(0,0,0,0.5)',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n }}>\r\n <div style={{ color: accentColor, fontSize: 20 }}>▶</div>\r\n </div>\r\n )}\r\n {/* Duration badge */}\r\n {item.duration && (\r\n <div style={{\r\n position: 'absolute',\r\n bottom: 4,\r\n right: 4,\r\n padding: '2px 6px',\r\n background: 'rgba(0,0,0,0.8)',\r\n borderRadius: 3,\r\n fontSize: 10,\r\n color: '#fff',\r\n fontWeight: 500,\r\n }}>\r\n {formatTime(item.duration)}\r\n </div>\r\n )}\r\n </div>\r\n \r\n {/* Info */}\r\n <div style={{ flex: 1, minWidth: 0 }}>\r\n <div style={{ \r\n color: currentPlaylistIndex === index ? accentColor : '#fff',\r\n fontSize: 14,\r\n fontWeight: currentPlaylistIndex === index ? 600 : 400,\r\n overflow: 'hidden',\r\n textOverflow: 'ellipsis',\r\n whiteSpace: 'nowrap',\r\n marginBottom: 4,\r\n }}>\r\n {item.title}\r\n </div>\r\n <div style={{ color: 'rgba(255,255,255,0.5)', fontSize: 12 }}>\r\n Video {index + 1}\r\n </div>\r\n </div>\r\n </button>\r\n ))}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Hidden video for frame preview */}\r\n <video\r\n ref={previewVideoRef}\r\n src={currentVideoSrc}\r\n style={{ display: 'none' }}\r\n muted\r\n preload=\"metadata\"\r\n />\r\n \r\n {/* Hidden canvas for frame capture */}\r\n <canvas ref={previewCanvasRef} style={{ display: 'none' }} />\r\n </div>\r\n );\r\n }\r\n);\r\n\r\nPlexVideoPlayer.displayName = 'PlexVideoPlayer';\r\n\r\nexport default PlexVideoPlayer;\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\r\nexport const RepeatIcon: 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 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z\" />\r\n </svg>\r\n);\r\n\r\nexport const CopyIcon: 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 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z\" />\r\n </svg>\r\n);\r\n\r\nexport const DownloadIcon: 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 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z\" />\r\n </svg>\r\n);\r\n\r\nexport const ChevronUpIcon: 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 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z\" />\r\n </svg>\r\n);\r\n\r\nexport const ChevronDownIcon: 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.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z\" />\r\n </svg>\r\n);\r\n\r\nexport const CloseIcon: 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 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" />\r\n </svg>\r\n);\r\n\r\nexport const PlaylistIcon: 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 10h12v2H4zm0-4h12v2H4zm0 8h8v2H4zm10 0v6l5-3z\" />\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","// 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"],"names":["parseVastXml","xmlString","doc","DOMParser","parseFromString","querySelector","ads","error","querySelectorAll","forEach","adElement","inLine","id","getAttribute","title","textContent","description","undefined","creatives","skipOffset","clickThrough","duration","clickTracking","mediaFiles","trackingEvents","creative","linear","durationStr","parseVastDuration","skipOffsetAttr","mediaFile","url","trim","push","type","width","parseInt","height","bitrate","videoClicks","ct","tracking","event","impressionUrls","impression","length","includes","parts","split","parseFloat","fetchVastAd","async","config","response","fetch","method","headers","Accept","ok","text","result","ad","skipDelay","selectBestMediaFile","video","document","createElement","supportedFiles","filter","file","canPlayType","sort","a","b","resA","resB","test","navigator","userAgent","fireTrackingPixel","Image","src","fireTrackingPixels","urls","convertToAdInfo","useVast","vastConfig","videoRef","onAdStart","onAdEnd","onAdSkip","onAdError","isAdPlaying","setIsAdPlaying","useState","currentAd","setCurrentAd","adTimeRemaining","setAdTimeRemaining","canSkip","setCanSkip","currentVastAd","useRef","originalSrc","originalTime","playedPositions","Set","adIntervalRef","vastConfigs","Array","isArray","playAd","useCallback","current","Error","currentSrc","currentTime","play","start","window","setInterval","remaining","Math","max","percent","firstQuartile","midpoint","thirdQuartile","handleAdEnd","complete","endAd","addEventListener","once","clearInterval","catch","skipAd","skip","handleAdClick","open","checkForAd","position","key","midrollTime","has","shouldPlay","add","useEffect","formatTime","seconds","isFinite","isNaN","hrs","floor","mins","secs","toString","padStart","parseTime","time","map","Number","percentage","value","total","min","clamp","throttle","func","limit","inThrottle","args","setTimeout","debounce","wait","timeout","clearTimeout","isFullscreenSupported","fullscreenEnabled","webkitFullscreenEnabled","mozFullScreenEnabled","msFullscreenEnabled","isPipSupported","pictureInPictureEnabled","getFullscreenElement","fullscreenElement","webkitFullscreenElement","mozFullScreenElement","msFullscreenElement","requestFullscreen","element","webkitRequestFullscreen","mozRequestFullScreen","msRequestFullscreen","exitFullscreen","webkitExitFullscreen","mozCancelFullScreen","msExitFullscreen","detectVideoType","extension","pop","toLowerCase","mp4","webm","ogg","ogv","m3u8","mpd","mov","avi","mkv","isHlsNativelySupported","generateId","random","substring","isMobile","isTouchDevice","maxTouchPoints","getBufferedEnd","buffered","i","end","PlayIcon","_jsx","viewBox","fill","className","children","d","PauseIcon","VolumeHighIcon","VolumeMuteIcon","FullscreenIcon","FullscreenExitIcon","PipIcon","SettingsIcon","RepeatIcon","CopyIcon","SpeedIcon","PlaylistIcon","style","CastIcon","CloseIcon","PlexVideoPlayer","forwardRef","props","ref","poster","autoPlay","muted","loop","controls","pip","fullscreen","playbackSpeed","playbackSpeeds","volume","volumeEnabled","initialVolume","textTracks","vast","accentColor","controlsTimeout","doubleClickFullscreen","clickToPlay","preload","playlist","onPlay","onPause","onEnded","onTimeUpdate","onProgress","onVolumeChange","onFullscreenChange","onPipChange","onQualityChange","onError","onReady","onPlaylistItemChange","containerRef","progressRef","controlsTimeoutRef","lastClickRef","isPlaying","setIsPlaying","setCurrentTime","setDuration","setBuffered","volumeState","setVolumeState","isMuted","setIsMuted","isFullscreen","setIsFullscreen","isPipActive","setIsPipActive","isLoading","setIsLoading","setError","showControls","setShowControls","playbackRate","setPlaybackRateState","isLoop","setIsLoop","showSettings","setShowSettings","showPlaylist","setShowPlaylist","hoverTime","setHoverTime","previewFrame","setPreviewFrame","setIsMobile","isTouching","setIsTouching","touchStartRef","previewCanvasRef","previewVideoRef","checkMobile","mobile","matchMedia","matches","removeEventListener","contextMenu","setContextMenu","x","y","visible","currentPlaylistIndex","setCurrentPlaylistIndex","sources","useMemo","currentVideoSrc","currentPoster","err","pause","stop","togglePlay","paused","seek","setVolume","newVolume","mute","unmute","toggleMute","toggleLoop","enterFullscreen","toggleFullscreen","enterPip","requestPictureInPicture","exitPip","pictureInPictureElement","exitPictureInPicture","togglePip","setPlaybackRate","rate","setQuality","qualityLabel","source","find","s","label","quality","wasPlaying","playNextInPlaylist","nextIndex","handleContextMenu","e","preventDefault","rect","getBoundingClientRect","clientX","left","clientY","top","closeContextMenu","prev","copyVideoUrl","clipboard","writeText","handleProgressClick","handleProgressHover","previewVideo","canvas","ctx","getContext","abs","onseeked","drawImage","toDataURL","handleProgressLeave","hideControls","showControlsTemporarily","handleMouseMove","handleTouchStart","touch","touches","Date","now","handleTouchMove","deltaX","deltaY","seekDelta","innerWidth","newTime","handleTouchEnd","touchDuration","changedTouches","tapX","handleVideoClick","handlePlay","handlePause","handleEnded","handleTimeUpdate","handleDurationChange","handleProgressEvent","bufferedEnd","handleVolumeChangeEvent","handleLoadedMetadata","handleWaiting","handlePlaying","handleCanPlay","handleErrorEvent","handleFullscreenChange","isFs","handleEnterPip","handleLeavePip","handleKeyDown","contains","activeElement","handleClick","useImperativeHandle","getCurrentTime","getDuration","getVolume","isPip","getVideoElement","progressPercent","_jsxs","inset","zIndex","aspectRatio","backgroundColor","overflow","userSelect","fontFamily","WebkitTapHighlightColor","touchAction","onMouseMove","onMouseLeave","onTouchStart","onTouchMove","onTouchEnd","onContextMenu","tabIndex","objectFit","playsInline","onClick","track","kind","srcLang","srclang","default","display","alignItems","justifyContent","pointerEvents","border","borderTopColor","borderRadius","animation","flexDirection","background","color","marginBottom","fontSize","load","padding","fontWeight","cursor","gap","disabled","bottom","right","opacity","ceil","minWidth","backdropFilter","boxShadow","stopPropagation","textAlign","flex","margin","speeds","idx","indexOf","borderTop","paddingTop","transition","transform","alt","whiteSpace","flexWrap","step","onChange","target","marginLeft","remote","prompt","alert","maxHeight","borderBottom","textTransform","letterSpacing","speed","borderLeft","overflowY","item","index","flexShrink","textOverflow","displayName","ProgressBar","onSeek","thumbnailPreview","isDragging","setIsDragging","hoverPosition","setHoverPosition","playedPercent","bufferedPercent","getTimeFromPosition","handleMouseDown","handleMouseUp","handleMouseEnter","handleMouseLeave","mouseMoveHandler","onMouseDown","onMouseEnter","nativeEvent","role","enabled","sprites","backgroundImage","backgroundPosition","calculateSpritePosition","interval","backgroundSize","_duration","size","xmlns","VolumeMediumIcon","VolumeLowIcon","PipExitIcon","CaptionsIcon","QualityIcon","ForwardIcon","RewindIcon","SkipNextIcon","SkipPrevIcon","ErrorIcon","ExternalLinkIcon","CheckIcon","VolumeControl","onToggleMute","handleSliderChange","SettingsMenu","onPlaybackRateChange","qualityEnabled","currentQuality","captionsEnabled","currentTrack","onTrackChange","isOpen","setIsOpen","currentView","setCurrentView","handleClickOutside","handleToggle","handleSpeedSelect","handleQualitySelect","handleTrackSelect","_Fragment","AdOverlay","timeRemaining","onSkip","skipText","ERROR_MESSAGES","ErrorDisplay","onRetry","errorCode","code","message","Loader","initialState","isPaused","isEnded","isBuffering","isSeeking","usePlayer","options","initialRate","state","setState","updateState","updates","clampedTime","clampedVolume","container","exitFullscreenFn","exitPipFn","handlers","loadedmetadata","timeupdate","ended","waiting","canplay","seeking","seeked","volumechange","ratechange","enterpictureinpicture","leavepictureinpicture","Object","entries","handler","DEFAULT_HOTKEYS","seekForward","seekBackward","volumeUp","volumeDown","useKeyboard","hotkeys","onMute","onFullscreen","onPip","onVolume","mergedHotkeys","tagName","isContentEditable","shiftKey","Infinity"],"mappings":"mMAkCO,MAAMA,EAAgBC,IAC3B,MACMC,GADS,IAAIC,WACAC,gBAAgBH,EAAW,YAG9C,GADmBC,EAAIG,cAAc,eAEnC,MAAO,CAAEC,IAAK,GAAIC,MAAO,4BAG3B,MAAMD,EAAgB,GAkGtB,OAjGmBJ,EAAIM,iBAAiB,MAE7BC,QAASC,IAClB,MAAMC,EAASD,EAAUL,cAAc,UACvC,IAAKM,EAAQ,OAEb,MAAMC,EAAKF,EAAUG,aAAa,OAAS,GACrCC,EAAQH,EAAON,cAAc,YAAYU,aAAe,GACxDC,EAAcL,EAAON,cAAc,gBAAgBU,kBAAeE,EAGlEC,EAAYP,EAAOH,iBAAiB,YAC1C,IACIW,EACAC,EAFAC,EAAW,EAGf,MAAMC,EAA0B,GAC1BC,EAA8B,GAC9BC,EAA2C,CAAA,EAEjDN,EAAUT,QAASgB,IACjB,MAAMC,EAASD,EAASpB,cAAc,UACtC,IAAKqB,EAAQ,OAGb,MAAMC,EAAcD,EAAOrB,cAAc,aAAaU,YAClDY,IACFN,EAAWO,EAAkBD,IAI/B,MAAME,EAAiBH,EAAOb,aAAa,cACvCgB,IACFV,EAAaS,EAAkBC,IAIPH,EAAOlB,iBAAiB,aAChCC,QAASqB,IACzB,MAAMC,EAAMD,EAAUf,aAAaiB,OAC/BD,GACFR,EAAWU,KAAK,CACdF,MACAG,KAAMJ,EAAUjB,aAAa,SAAW,YACxCsB,MAAOC,SAASN,EAAUjB,aAAa,UAAY,IAAK,IACxDwB,OAAQD,SAASN,EAAUjB,aAAa,WAAa,IAAK,IAC1DyB,QAASF,SAASN,EAAUjB,aAAa,YAAc,IAAK,UAAOI,MAMzE,MAAMsB,EAAcb,EAAOrB,cAAc,eACrCkC,IACFnB,EAAemB,EAAYlC,cAAc,iBAAiBU,aAAaiB,OACvEO,EAAY/B,iBAAiB,iBAAiBC,QAAS+B,IACrD,MAAMT,EAAMS,EAAGzB,aAAaiB,OACxBD,GAAKT,EAAcW,KAAKF,MAKPL,EAAOlB,iBAAiB,2BAChCC,QAASgC,IACxB,MAAMC,EAAQD,EAAS5B,aAAa,SAC9BkB,EAAMU,EAAS1B,aAAaiB,OAC9BU,GAASX,IACNP,EAAekB,KAClBlB,EAAekB,GAAS,IAE1BlB,EAAekB,GAAOT,KAAKF,QAMjC,MAAMY,EAA2B,GACjChC,EAAOH,iBAAiB,cAAcC,QAASmC,IAC7C,MAAMb,EAAMa,EAAW7B,aAAaiB,OAChCD,GAAKY,EAAeV,KAAKF,KAG3BR,EAAWsB,OAAS,GACtBvC,EAAI2B,KAAK,CACPrB,KACAE,QACAE,cACAK,WACAF,aACAC,eACAE,gBACAqB,iBACApB,aACAC,qBAKC,CAAElB,QAMLsB,EAAqBP,IACzB,GAAIA,EAASyB,SAAS,KACpB,SAGF,MAAMC,EAAQ1B,EAAS2B,MAAM,KAC7B,GAAqB,IAAjBD,EAAMF,OAAc,OAAO,EAM/B,OAAe,KAJDT,SAASW,EAAM,GAAI,IAID,GAHhBX,SAASW,EAAM,GAAI,IACnBE,WAAWF,EAAM,KAQtBG,EAAcC,MAAOC,IAChC,IACE,MAAMC,QAAiBC,MAAMF,EAAOrB,IAAK,CACvCwB,OAAQ,MACRC,QAAS,CACPC,OAAU,qBAId,IAAKJ,EAASK,GAEZ,OAAO,KAGT,MAAMzD,QAAkBoD,EAASM,OAC3BC,EAAS5D,EAAaC,GAE5B,GAAI2D,EAAOrD,OAA+B,IAAtBqD,EAAOtD,IAAIuC,OAE7B,OAAO,KAGT,MAAMgB,EAAKD,EAAOtD,IAAI,GAOtB,YAJyBW,IAArBmC,EAAOU,gBAA6C7C,IAAlB4C,EAAG1C,aACvC0C,EAAG1C,WAAaiC,EAAOU,WAGlBD,CACT,CAAE,MAAOtD,GAEP,OAAO,IACT,GAMWwD,EAAuBxC,IAClC,GAA0B,IAAtBA,EAAWsB,OAAc,OAAO,KAEpC,MAAMmB,EAAQC,SAASC,cAAc,SAC/BC,EAAiB5C,EAAW6C,OAAQC,GACA,KAAjCL,EAAMM,YAAYD,EAAKnC,OAGhC,GAA8B,IAA1BiC,EAAetB,OAAc,OAAO,KAGxCsB,EAAeI,KAAK,CAACC,EAAGC,KACtB,MAAMC,EAAOF,EAAErC,MAAQqC,EAAEnC,OACnBsC,EAAOF,EAAEtC,MAAQsC,EAAEpC,OACzB,OAAIqC,IAASC,EAAaA,EAAOD,GACzBD,EAAEnC,SAAW,IAAMkC,EAAElC,SAAW,KAK1C,MADiB,4BAA4BsC,KAAKC,UAAUC,YAC5CX,EAAetB,OAAS,EAC/BsB,EAAeA,EAAetB,OAAS,GAGzCsB,EAAe,IAMXY,EAAqBhD,KACpB,IAAIiD,OACZC,IAAMlD,GAMCmD,EAAsBC,IACjCA,EAAK1E,QAAQsE,IAMFK,EAAmBvB,IACvB,CACLjD,GAAIiD,EAAGjD,GACPE,MAAO+C,EAAG/C,MACVO,SAAUwC,EAAGxC,SACbF,WAAY0C,EAAG1C,WACfC,aAAcyC,EAAGzC,eC/NRiE,EAAU,EACrBC,aACAC,WACAC,YACAC,UACAC,WACAC,gBAEA,MAAOC,EAAaC,GAAkBC,GAAS,IACxCC,EAAWC,GAAgBF,EAA4B,OACvDG,EAAiBC,GAAsBJ,EAAS,IAChDK,EAASC,GAAcN,GAAS,GAEjCO,EAAgBC,EAAsB,MACtCC,EAAcD,EAAe,IAC7BE,EAAeF,EAAe,GAC9BG,EAAkBH,EAAoB,IAAII,KAC1CC,EAAgBL,EAAsB,MAGtCM,EAAcC,MAAMC,QAAQxB,GAAcA,EAAaA,EAAa,CAACA,GAAc,GAGnFyB,EAASC,EACb7D,MAAOC,IACL,MAAMY,EAAQuB,EAAS0B,QACvB,GAAKjD,IAAS4B,EAEd,IACE,MAAM/B,QAAWX,EAAYE,GAC7B,IAAKS,EAEH,YADA8B,IAAY,IAAIuB,MAAM,4BAIxB,MAAMpF,EAAYiC,EAAoBF,EAAGtC,YACzC,IAAKO,EAEH,YADA6D,IAAY,IAAIuB,MAAM,mCAKxBX,EAAYU,QAAUjD,EAAMmD,WAC5BX,EAAaS,QAAUjD,EAAMoD,YAG7BlC,EAAmBrB,EAAGlB,gBAGtB0D,EAAcY,QAAUpD,EACxBmC,EAAaZ,EAAgBvB,IAC7BgC,GAAe,GACfK,EAAmBrC,EAAGxC,UACtB+E,OAA6BnF,IAAlB4C,EAAG1C,YAA8C,IAAlB0C,EAAG1C,YAG7C6C,EAAMiB,IAAMnD,EAAUC,IACtBiC,EAAMoD,YAAc,QACdpD,EAAMqD,OAGRxD,EAAGrC,eAAe8F,OACpBpC,EAAmBrB,EAAGrC,eAAe8F,OAGvC9B,IAAYJ,EAAgBvB,IAG5B8C,EAAcM,QAAUM,OAAOC,YAAY,KACzC,MAAMC,EAAYC,KAAKC,IAAI,EAAG9D,EAAGxC,SAAW2C,EAAMoD,aAClDlB,EAAmBuB,GAGf5D,EAAG1C,YAAc6C,EAAMoD,aAAevD,EAAG1C,YAC3CiF,GAAW,GAIb,MAAMwB,EAAW5D,EAAMoD,YAAcvD,EAAGxC,SAAY,IAChDuG,GAAW,IAAM/D,EAAGrC,eAAeqG,eACrC3C,EAAmBrB,EAAGrC,eAAeqG,eAEnCD,GAAW,IAAM/D,EAAGrC,eAAesG,UACrC5C,EAAmBrB,EAAGrC,eAAesG,UAEnCF,GAAW,IAAM/D,EAAGrC,eAAeuG,eACrC7C,EAAmBrB,EAAGrC,eAAeuG,gBAEtC,KAGH,MAAMC,EAAc,KACdnE,EAAGrC,eAAeyG,UACpB/C,EAAmBrB,EAAGrC,eAAeyG,UAEvCC,KAGFlE,EAAMmE,iBAAiB,QAASH,EAAa,CAAEI,MAAM,GACvD,CAAE,MAAO7H,GACPoF,IAAYpF,aAAiB2G,MAAQ3G,EAAQ,IAAI2G,MAAM,uBACvDgB,GACF,GAEF,CAAC3C,EAAUK,EAAaJ,EAAWG,IAI/BuC,EAAQlB,EAAY,KACxB,MAAMhD,EAAQuB,EAAS0B,QAClBjD,IAGD2C,EAAcM,UAChBoB,cAAc1B,EAAcM,SAC5BN,EAAcM,QAAU,MAItBV,EAAYU,UACdjD,EAAMiB,IAAMsB,EAAYU,QACxBjD,EAAMoD,YAAcZ,EAAaS,QACjCjD,EAAMqD,OAAOiB,MAAM,SAGrBzC,GAAe,GACfG,EAAa,MACbE,EAAmB,GACnBE,GAAW,GACXC,EAAcY,QAAU,KAExBxB,QACC,CAACF,EAAUE,IAGR8C,EAASvB,EAAY,KACzB,IAAKb,IAAYE,EAAcY,QAAS,OAExC,MAAMpD,EAAKwC,EAAcY,QACrBpD,EAAGrC,eAAegH,MACpBtD,EAAmBrB,EAAGrC,eAAegH,MAGvC9C,MACAwC,KACC,CAAC/B,EAAS+B,EAAOxC,IAGd+C,EAAgBzB,EAAY,KAChC,IAAKX,EAAcY,QAAS,OAE5B,MAAMpD,EAAKwC,EAAcY,QACrBpD,EAAGzC,cACLmG,OAAOmB,KAAK7E,EAAGzC,aAAc,UAE3ByC,EAAGvC,eACL4D,EAAmBrB,EAAGvC,gBAEvB,IAGGqH,EAAa3B,EACjB,CAACI,EAAqB/F,KAChBuE,GAAsC,IAAvBgB,EAAY/D,QAE/B+D,EAAYnG,QAAS2C,IACnB,MAAMwF,EAAWxF,EAAOwF,UAAY,UAC9BC,EAAM,GAAGD,KAAYxF,EAAO0F,aAAe,IAEjD,GAAIrC,EAAgBQ,QAAQ8B,IAAIF,GAAM,OAEtC,IAAIG,GAAa,EAEjB,OAAQJ,GACN,IAAK,UACHI,EAA6B,IAAhB5B,EACb,MACF,IAAK,UACChE,EAAO0F,aAAe1B,GAAehE,EAAO0F,cAC9CE,GAAa,GAEf,MACF,IAAK,WACHA,EAAa5B,GAAe/F,EAAW,GAIvC2H,IACFvC,EAAgBQ,QAAQgC,IAAIJ,GAC5B9B,EAAO3D,OAIb,CAACwD,EAAahB,EAAamB,IAY7B,OARAmC,EAAU,IACD,KACDvC,EAAcM,SAChBoB,cAAc1B,EAAcM,UAG/B,IAEI,CACLrB,cACAG,YACAE,kBACAE,UACAoC,SACAE,gBACAE,eC9OSQ,EAAcC,IACzB,IAAKC,SAASD,IAAYE,MAAMF,GAAU,MAAO,OAEjD,MAAMG,EAAM7B,KAAK8B,MAAMJ,EAAU,MAC3BK,EAAO/B,KAAK8B,MAAOJ,EAAU,KAAQ,IACrCM,EAAOhC,KAAK8B,MAAMJ,EAAU,IAElC,OAAIG,EAAM,EACD,GAAGA,KAAOE,EAAKE,WAAWC,SAAS,EAAG,QAAQF,EAAKC,WAAWC,SAAS,EAAG,OAG5E,GAAGH,KAAQC,EAAKC,WAAWC,SAAS,EAAG,QAMnCC,EAAaC,IACxB,MAAM/G,EAAQ+G,EAAK9G,MAAM,KAAK+G,IAAIC,QAClC,OAAqB,IAAjBjH,EAAMF,OACU,KAAXE,EAAM,GAAuB,GAAXA,EAAM,GAAUA,EAAM,GAE5B,IAAjBA,EAAMF,OACU,GAAXE,EAAM,GAAUA,EAAM,GAExBA,EAAM,IAAM,GAMRkH,EAAa,CAACC,EAAeC,IAC1B,IAAVA,EAAoB,EACjBzC,KAAK0C,IAAI,IAAK1C,KAAKC,IAAI,EAAIuC,EAAQC,EAAS,MAMxCE,EAAQ,CAACH,EAAeE,EAAazC,IACzCD,KAAK0C,IAAIzC,EAAKD,KAAKC,IAAIyC,EAAKF,IAMxBI,EAAW,CACtBC,EACAC,KAEA,IAAIC,GAAa,EACjB,MAAO,IAAIC,KACJD,IACHF,KAAQG,GACRD,GAAa,EACbE,WAAW,IAAOF,GAAa,EAAQD,MAQhCI,EAAW,CACtBL,EACAM,KAEA,IAAIC,EAAgD,KACpD,MAAO,IAAIJ,KACLI,GAASC,aAAaD,GAC1BA,EAAUH,WAAW,IAAMJ,KAAQG,GAAOG,KAOjCG,EAAwB,OAEjC/G,SAASgH,mBACRhH,SAA8DiH,yBAC9DjH,SAA2DkH,sBAC3DlH,SAA0DmH,qBAOlDC,EAAiB,IACrB,4BAA6BpH,UAAYA,SAASqH,wBAM9CC,EAAuB,IAEhCtH,SAASuH,mBACRvH,SAA8DwH,yBAC9DxH,SAA2DyH,sBAC3DzH,SAA0D0H,qBAC3D,KAOSC,EAAoBzI,MAAO0I,IAClCA,EAAQD,wBACJC,EAAQD,oBACJC,EAA4EC,8BAC/ED,EAA2EC,0BACxED,EAAyEE,2BAC5EF,EAAwEE,uBACrEF,EAAwEG,2BAC3EH,EAAuEG,uBAOrEC,EAAiB9I,UACxBc,SAASgI,qBACLhI,SAASgI,iBACLhI,SAAuEiI,2BAC1EjI,SAAsEiI,uBACnEjI,SAAsEkI,0BACzElI,SAAqEkI,sBAClElI,SAAmEmI,wBACtEnI,SAAkEmI,oBAOhEC,EAAmBtK,IAC9B,MAAMuK,EAAYvK,EAAIiB,MAAM,KAAK,GAAGA,MAAM,KAAKuJ,OAAOC,cActD,MAZ0C,CACxCC,IAAK,YACLC,KAAM,aACNC,IAAK,YACLC,IAAK,YACLC,KAAM,wBACNC,IAAK,uBACLC,IAAK,kBACLC,IAAK,kBACLC,IAAK,oBAGUX,GAAa,KAAO,aAM1BY,EAAyB,IAE0B,KADhDjJ,SAASC,cAAc,SACxBI,YAAY,iCAMd6I,EAAa,IACjB,QAAQzF,KAAK0F,SAASzD,SAAS,IAAI0D,UAAU,EAAG,MAM5CC,EAAW,IACf,iEAAiE1I,KACtEC,UAAUC,WAODyI,EAAgB,IACpB,iBAAkBhG,QAAU1C,UAAU2I,eAAiB,EAMnDC,EAAkBzJ,IAC7B,GAA8B,IAA1BA,EAAM0J,SAAS7K,OAAc,OAAO,EAExC,MAAMuE,EAAcpD,EAAMoD,YAC1B,IAAK,IAAIuG,EAAI,EAAGA,EAAI3J,EAAM0J,SAAS7K,OAAQ8K,IACzC,GAAI3J,EAAM0J,SAASpG,MAAMqG,IAAMvG,GAAepD,EAAM0J,SAASE,IAAID,IAAMvG,EACrE,OAAOpD,EAAM0J,SAASE,IAAID,GAI9B,OAAO3J,EAAM0J,SAASE,IAAI5J,EAAM0J,SAAS7K,OAAS,IAMvCyB,EAAepC,IAC1B,MACM0B,EADQK,SAASC,cAAc,SAChBI,YAAYpC,GACjC,MAAkB,aAAX0B,GAAoC,UAAXA,GCnM5BiK,EAAW,IACfC,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAA,OAAA,CAAMK,EAAE,oBAINC,EAAY,IAChBN,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAA,OAAA,CAAMK,EAAE,sCAINE,EAAiB,IACrBP,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAA,OAAA,CAAMK,EAAE,kMAING,EAAiB,IACrBR,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAA,OAAA,CAAMK,EAAE,sWAINI,EAAiB,IACrBT,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAA,OAAA,CAAMK,EAAE,qFAINK,EAAqB,IACzBV,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAA,OAAA,CAAMK,EAAE,oFAINM,EAAU,IACdX,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAA,OAAA,CAAMK,EAAE,iHAINO,EAAe,IACnBZ,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAA,OAAA,CAAMK,EAAE,osBAINQ,EAAa,IACjBb,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAA,OAAA,CAAMK,EAAE,sEAINS,EAAW,IACfd,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAA,OAAA,CAAMK,EAAE,sIAINU,EAAY,IAChBf,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAeC,UAAU,UAASC,SAC9DJ,EAAA,OAAA,CAAMK,EAAE,yOAINW,EAAe,IACnBhB,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAee,MAAO,CAAE5M,MAAO,GAAIE,OAAQ,IAAI6L,SAC3EJ,EAAA,OAAA,CAAMK,EAAE,6FAINa,EAAW,IACflB,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAee,MAAO,CAAE5M,MAAO,GAAIE,OAAQ,IAAI6L,SAC3EJ,EAAA,OAAA,CAAMK,EAAE,2MAINc,EAAY,IAChBnB,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAee,MAAO,CAAE5M,MAAO,GAAIE,OAAQ,IAAI6L,SAC3EJ,EAAA,OAAA,CAAMK,EAAE,4GAkBCe,EAAkBC,EAC7B,CAACC,EAAOC,KACN,MAAMpK,IACJA,EAAGqK,OACHA,EAAMC,SACNA,GAAW,EAAKC,MAChBA,GAAQ,EAAKC,KACbA,GAAO,EAAKC,SACZA,GAAW,EAAIvN,MACfA,EAAQ,OAAM8L,UACdA,EAAY,GAAEc,MACdA,EAAQ,CAAA,EAAEY,IACVA,GAAM,EAAIC,WACVA,GAAa,EAAIC,cACjBA,GAAgB,EAAIC,eACpBA,EAAiB,CAAC,GAAK,IAAM,EAAG,KAAM,IAAK,GAC3CC,OAAQC,GAAgB,EAAIC,cAC5BA,EAAgB,EAACC,WACjBA,EAAa,GAAEC,KACfA,EAAIC,YACJA,EAAc,UAASC,gBACvBA,EAAkB,IAAIC,sBACtBA,GAAwB,EAAIC,YAC5BA,GAAc,EAAIC,QAClBA,EAAU,WAAUC,SACpBA,EAAW,GAAEC,OACbA,EAAMC,QACNA,EAAOC,QACPA,EAAOC,aACPA,EAAYC,WACZA,GAAUC,eACVA,GAAcC,mBACdA,GAAkBC,YAClBA,GAAWC,gBACXA,GAAeC,QACfA,GAAO3L,UACPA,GAASC,QACTA,GAAOC,SACPA,GAAQC,UACRA,GAASyL,QACTA,GAAOC,qBACPA,IACEjC,EAGE7J,GAAWe,EAAyB,MACpCgL,GAAehL,EAAuB,MACtCiL,GAAcjL,EAAuB,MACrCkL,GAAqBlL,EAA8B,MACnDmL,GAAenL,EAAe,IAG7BoL,GAAWC,IAAgB7L,GAAS,IACpCsB,GAAawK,IAAkB9L,EAAS,IACxCzE,GAAUwQ,IAAe/L,EAAS,IAClC4H,GAAUoE,IAAehM,EAAS,IAClCiM,GAAaC,IAAkBlM,EAAS0J,EAAQ,EAAIS,IACpDgC,GAASC,IAAcpM,EAAS0J,IAChC2C,GAAcC,IAAmBtM,GAAS,IAC1CuM,GAAaC,IAAkBxM,GAAS,IACxCyM,GAAWC,IAAgB1M,GAAS,IACpCvF,GAAOkS,IAAY3M,EAA4B,OAC/C4M,GAAcC,IAAmB7M,GAAS,IAC1C8M,GAAcC,IAAwB/M,EAAS,IAC/CgN,GAAQC,IAAajN,EAAS2J,IAC9BuD,GAAcC,IAAmBnN,GAAS,IAC1CoN,GAAcC,IAAmBrN,GAAS,IAC1CsN,GAAWC,IAAgBvN,EAA6C,OACxEwN,GAAcC,IAAmBzN,EAAwB,OACzDwH,GAAUkG,IAAe1N,GAAS,IAClC2N,GAAYC,IAAiB5N,GAAS,GACvC6N,GAAgBrN,EAAsD,MACtEsN,GAAmBtN,EAA0B,MAC7CuN,GAAkBvN,EAAyB,MAGjD4C,EAAU,KACR,MAAM4K,EAAc,KAClB,MAAMC,EAASxM,OAAOyM,WAAW,sBAAsBC,SACxC1M,OAAOyM,WAAW,qBAAqBC,SACvC,iBAAkB1M,OACjCiM,GAAYO,IAId,OAFAD,IACAvM,OAAOY,iBAAiB,SAAU2L,GAC3B,IAAMvM,OAAO2M,oBAAoB,SAAUJ,IACjD,IAGH,MAAOK,GAAaC,IAAkBtO,EAAS,CAAEuO,EAAG,EAAGC,EAAG,EAAGC,SAAS,KAG/DC,GAAsBC,IAA2B3O,EAAS,GAG3D4O,GAAUC,EAAuB,IAClB,iBAAR1P,EACF,CAAC,CAAEA,MAAK/C,KAAM,cAEhB+C,EACN,CAACA,IAGE2P,GAAkBD,EAAQ,IAC1BlE,EAAS5N,OAAS,GAAK4N,EAAS+D,IAC3B/D,EAAS+D,IAAsBvP,IAEjCyP,GAAQ,IAAIzP,KAAO,GACzB,CAACwL,EAAU+D,GAAsBE,KAE9BG,GAAgBF,EAAQ,IACxBlE,EAAS5N,OAAS,GAAK4N,EAAS+D,KAAuBlF,OAClDmB,EAAS+D,IAAsBlF,OAEjCA,EACN,CAACmB,EAAU+D,GAAsBlF,KAG9BvJ,UACJA,GAASH,YACTA,GAAWK,gBACXA,GAAeE,QACfA,GAAOoC,OACPA,GAAME,cACNA,GAAaE,WACbA,IACEtD,EAAQ,CACVC,WAAY6K,EACZ5K,YACAC,aACAC,WACAC,YACAC,eAII0B,GAAOL,EAAY7D,UACvB,MAAMa,EAAQuB,GAAS0B,QACvB,GAAKjD,EACL,UACQA,EAAMqD,MACd,CAAE,MAAOyN,GAET,GACC,IAEGC,GAAQ/N,EAAY,KACxBzB,GAAS0B,SAAS8N,SACjB,IAEGC,GAAOhO,EAAY,KACvB,MAAMhD,EAAQuB,GAAS0B,QAClBjD,IACLA,EAAM+Q,QACN/Q,EAAMoD,YAAc,IACnB,IAEG6N,GAAajO,EAAY,KACzBzB,GAAS0B,SAASiO,OACpB7N,KAEA0N,MAED,CAAC1N,GAAM0N,KAEJI,GAAOnO,EAAa8C,IACxB,MAAM9F,EAAQuB,GAAS0B,QAClBjD,IACLA,EAAMoD,YAAcM,KAAKC,IAAI,EAAGD,KAAK0C,IAAIN,EAAM9F,EAAM3C,UAAY,MAChE,IAEG+T,GAAYpO,EAAakD,IAC7B,MAAMlG,EAAQuB,GAAS0B,QACvB,IAAKjD,EAAO,OACZ,MAAMqR,EAAY3N,KAAKC,IAAI,EAAGD,KAAK0C,IAAI,EAAGF,IAC1ClG,EAAM+L,OAASsF,EACfrR,EAAMwL,MAAsB,IAAd6F,EACdrD,GAAeqD,GACfnD,GAAyB,IAAdmD,IACV,IAEGC,GAAOtO,EAAY,KACnBzB,GAAS0B,UACX1B,GAAS0B,QAAQuI,OAAQ,EACzB0C,IAAW,KAEZ,IAEGqD,GAASvO,EAAY,KACzB,MAAMhD,EAAQuB,GAAS0B,QAClBjD,IACLA,EAAMwL,OAAQ,EACO,IAAjBxL,EAAM+L,SACR/L,EAAM+L,OAAS,GACfiC,GAAe,KAEjBE,IAAW,KACV,IAEGsD,GAAaxO,EAAY,KACzBzB,GAAS0B,SAASuI,MACpB+F,KAEAD,MAED,CAACA,GAAMC,KAEJE,GAAazO,EAAY,KACzBzB,GAAS0B,UACX1B,GAAS0B,QAAQwI,MAAQlK,GAAS0B,QAAQwI,KAC1CsD,GAAUxN,GAAS0B,QAAQwI,QAE5B,IAEGiG,GAAkB1O,EAAY7D,UAClC,UACQmO,GAAarK,SAAS2E,qBAC5BwG,IAAgB,EAClB,CAAE,MAAO0C,GAET,GACC,IAEG7I,GAAiBjF,EAAY7D,UACjC,IACMc,SAASuH,0BACLvH,SAASgI,iBACfmG,IAAgB,GAEpB,CAAE,MAAO0C,GAET,GACC,IAEGa,GAAmB3O,EAAY7D,UAC/Bc,SAASuH,wBACLS,WAEAyJ,MAEP,CAACA,GAAiBzJ,KAEf2J,GAAW5O,EAAY7D,UAC3B,UACQoC,GAAS0B,SAAS4O,2BACxBvD,IAAe,EACjB,CAAE,MAAOwC,GAET,GACC,IAEGgB,GAAU9O,EAAY7D,UAC1B,IACMc,SAAS8R,gCACL9R,SAAS+R,uBACf1D,IAAe,GAEnB,CAAE,MAAOwC,GAET,GACC,IAEGmB,GAAYjP,EAAY7D,UACxBc,SAAS8R,8BACLD,WAEAF,MAEP,CAACA,GAAUE,KAERI,GAAkBlP,EAAamP,IAC/B5Q,GAAS0B,UACX1B,GAAS0B,QAAQ2L,aAAeuD,EAChCtD,GAAqBsD,GACrBlD,IAAgB,KAEjB,IAEGmD,GAAapP,EAAaqP,IAC9B,MAAMC,EAAS5B,GAAQ6B,KAAKC,GAAKA,EAAEC,QAAUJ,GAAgBG,EAAEE,UAAYL,GAC3E,GAAIC,GAAU/Q,GAAS0B,QAAS,CAC9B,MAAMjD,EAAQuB,GAAS0B,QACjB0P,GAAc3S,EAAMkR,OACpBpL,EAAO9F,EAAMoD,YACnBpD,EAAMiB,IAAMqR,EAAOrR,IACnBjB,EAAMoD,YAAc0C,EAChB6M,GAAY3S,EAAMqD,OACtB6J,KAAkBmF,EACpB,GACC,CAAC3B,GAASxD,KAGP0F,GAAqB5P,EAAY,KACrC,GAAwB,IAApByJ,EAAS5N,OAAc,OAC3B,MAAMgU,GAAarC,GAAuB,GAAK/D,EAAS5N,OACxD4R,GAAwBoC,GACxBxF,KAAuBwF,EAAWpG,EAASoG,IAC3ClM,WAAW,IAAMtD,KAAQ,MACxB,CAACmN,GAAsB/D,EAAUY,GAAsBhK,KAGpDyP,GAAoB9P,EAAa+P,IACrCA,EAAEC,iBACF,MAAMC,EAAO3F,GAAarK,SAASiQ,wBAC/BD,GACF7C,GAAe,CACbC,EAAG0C,EAAEI,QAAUF,EAAKG,KACpB9C,EAAGyC,EAAEM,QAAUJ,EAAKK,IACpB/C,SAAS,KAGZ,IAEGgD,GAAmBvQ,EAAY,KACnCoN,GAAeoD,IAAI,IAAUA,EAAMjD,SAAS,MAC3C,IAEGkD,GAAezQ,EAAY,KAC/BnC,UAAU6S,UAAUC,UAAU/C,IAC9B2C,MACC,CAAC3C,GAAiB2C,KAGfK,GAAsB5Q,EAAa+P,IACvC,MAAME,EAAO1F,GAAYtK,SAASiQ,wBAClC,GAAID,GAAQ5V,GAAU,CACpB,MAAMuG,GAAWmP,EAAEI,QAAUF,EAAKG,MAAQH,EAAK9U,MAC/CgT,GAAKvN,EAAUvG,GACjB,GACC,CAACA,GAAU8T,KAGR0C,GAAsB7Q,EAAa+P,IACvC,MAAME,EAAO1F,GAAYtK,SAASiQ,wBAClC,GAAID,GAAQ5V,GAAU,CACpB,MAAMuG,GAAWmP,EAAEI,QAAUF,EAAKG,MAAQH,EAAK9U,MACzC2H,EAAOpC,KAAKC,IAAI,EAAGD,KAAK0C,IAAI/I,GAAUuG,EAAUvG,KAItD,GAHAgS,GAAa,CAAEvJ,OAAMuK,EAAG0C,EAAEI,QAAUF,EAAKG,OAGrCvD,GAAgB5M,SAAW2M,GAAiB3M,QAAS,CACvD,MAAM6Q,EAAejE,GAAgB5M,QAC/B8Q,EAASnE,GAAiB3M,QAC1B+Q,EAAMD,EAAOE,WAAW,MAE1BvQ,KAAKwQ,IAAIJ,EAAa1Q,YAAc0C,GAAQ,IAC9CgO,EAAa1Q,YAAc0C,GAG7BgO,EAAaK,SAAW,KAClBH,IACFD,EAAO5V,MAAQ,IACf4V,EAAO1V,OAAS,GAChB2V,EAAII,UAAUN,EAAc,EAAG,EAAG,IAAK,IACvCvE,GAAgBwE,EAAOM,UAAU,aAAc,MAGrD,CACF,GACC,CAAChX,KAEEiX,GAAsBtR,EAAY,KACtCqM,GAAa,MACbE,GAAgB,OACf,IAGGgF,GAAevR,EAAY,MAC3B0K,IAAcsB,IAAiBE,IACjCP,IAAgB,IAEjB,CAACjB,GAAWsB,GAAcE,KAEvBsF,GAA0BxR,EAAY,KAC1C2L,IAAgB,GACZnB,GAAmBvK,SACrB8D,aAAayG,GAAmBvK,SAE9ByK,KACFF,GAAmBvK,QAAU0D,WAAW4N,GAAcjL,GAAW,IAAO+C,KAEzE,CAACqB,GAAWrB,EAAiBkI,GAAcjL,KAExCmL,GAAkB9D,EACtB,IAAMrK,EAASkO,GAAyB,KACxC,CAACA,KAIGE,GAAmB1R,EAAa+P,IACpC,MAAM4B,EAAQ5B,EAAE6B,QAAQ,GACxBjF,GAAc1M,QAAU,CAAEoN,EAAGsE,EAAMxB,QAAS7C,EAAGqE,EAAMtB,QAASvN,KAAM+O,KAAKC,OACzEpF,IAAc,GACd8E,MACC,CAACA,KAEEO,GAAkB/R,EAAa+P,IACnC,IAAKpD,GAAc1M,QAAS,OAC5B,MAAM0R,EAAQ5B,EAAE6B,QAAQ,GAClBI,EAASL,EAAMxB,QAAUxD,GAAc1M,QAAQoN,EAC/C4E,EAASN,EAAMtB,QAAU1D,GAAc1M,QAAQqN,EAGrD,GAAI5M,KAAKwQ,IAAIc,GAA6B,EAAnBtR,KAAKwQ,IAAIe,IAAevR,KAAKwQ,IAAIc,GAAU,GAAI,CACpEjC,EAAEC,iBACF,MAAMkC,EAAaF,EAASzR,OAAO4R,WAAc9X,GAAW,GACtD+X,EAAU1R,KAAKC,IAAI,EAAGD,KAAK0C,IAAI/I,GAAU+F,GAAc8R,IAE7D7F,GAAa,CAAEvJ,KAAMsP,EAAS/E,EAAGsE,EAAMxB,SACzC,GACC,CAAC9V,GAAU+F,KAERiS,GAAiBrS,EAAa+P,IAClC,IAAKpD,GAAc1M,QAAS,OAE5B,MAAMqS,EAAgBT,KAAKC,MAAQnF,GAAc1M,QAAQ6C,KACnD6O,EAAQ5B,EAAEwC,eAAe,GACzBP,EAASL,EAAMxB,QAAUxD,GAAc1M,QAAQoN,EAC/C4E,EAASN,EAAMtB,QAAU1D,GAAc1M,QAAQqN,EAGrD,GAAIgF,EAAgB,KAAO5R,KAAKwQ,IAAIc,GAAU,IAAMtR,KAAKwQ,IAAIe,GAAU,GACjEvG,GACFuC,KAEAuD,UAIC,GAAI9Q,KAAKwQ,IAAIc,GAA6B,EAAnBtR,KAAKwQ,IAAIe,IAAevR,KAAKwQ,IAAIc,GAAU,GAAI,CACzE,MAAME,EAAaF,EAASzR,OAAO4R,WAAc9X,GAAW,GAC5D8T,GAAK/N,GAAc8R,EACrB,MAEK,GAAII,EAAgB,IAAK,CAC5B,MAAMrC,EAAO3F,GAAarK,SAASiQ,wBACnC,GAAID,EAAM,CACR,MAAMuC,EAAOb,EAAMxB,QAAUF,EAAKG,KAC9BoC,EAAoB,GAAbvC,EAAK9U,MACdgT,GAAK/N,GAAc,IACVoS,EAAoB,GAAbvC,EAAK9U,OACrBgT,GAAK/N,GAAc,GAEvB,CACF,CAEAuM,GAAc1M,QAAU,KACxByM,IAAc,GACdL,GAAa,OACZ,CAACX,GAAcuC,GAAYuD,GAAyBnX,GAAU+F,GAAa+N,KAGxEsE,GAAmBzS,EAAY,KAEnC,GADAuQ,KACIjK,GAAU,OACd,IAAKiD,EAAa,OAElB,MAAMuI,EAAMD,KAAKC,MACbxI,GAAyBwI,EAAMrH,GAAaxK,QAAU,IACxD0O,KAEAV,KAEFxD,GAAaxK,QAAU6R,GACtB,CAACvI,EAAaD,EAAuB2E,GAAYU,GAAkB4B,GAAkBjK,KAGxFpE,EAAU,KACR,MAAMlF,EAAQuB,GAAS0B,QACvB,IAAKjD,EAAO,OAEZ,MAAM0V,EAAa,KAAQ/H,IAAa,GAAOjB,OACzCiJ,EAAc,KAAQhI,IAAa,GAAQhB,OAC3CiJ,EAAc,KAClBjI,IAAa,GACbf,MACIH,EAAS5N,OAAS,GAAG+T,MAErBiD,EAAmB,KACvBjI,GAAe5N,EAAMoD,aACrByJ,IAAe7M,EAAMoD,aACrBuB,GAAW3E,EAAMoD,YAAapD,EAAM3C,WAEhCyY,EAAuB,IAAMjI,GAAY7N,EAAM3C,UAC/C0Y,EAAsB,KAC1B,GAAI/V,EAAM0J,SAAS7K,OAAS,EAAG,CAC7B,MAAMmX,EAAchW,EAAM0J,SAASE,IAAI5J,EAAM0J,SAAS7K,OAAS,GAC/DiP,GAAakI,EAAchW,EAAM3C,SAAY,KAC7CyP,KAAckJ,EAAchW,EAAM3C,SAAY,IAChD,GAEI4Y,EAA0B,KAC9BjI,GAAehO,EAAM+L,QACrBmC,GAAWlO,EAAMwL,OACjBuB,KAAiB/M,EAAM+L,OAAQ/L,EAAMwL,QAEjC0K,EAAuB,KAC3BrI,GAAY7N,EAAM3C,UAClBmR,IAAa,GACbpB,QAEI+I,EAAgB,IAAM3H,IAAa,GACnC4H,EAAgB,IAAM5H,IAAa,GACnC6H,EAAgB,IAAM7H,IAAa,GACnC8H,EAAmB,KACvB7H,GAASzO,EAAMzD,OACfiS,IAAa,GACbrB,KAAUnN,EAAMzD,QAgBlB,OAbAyD,EAAMmE,iBAAiB,OAAQuR,GAC/B1V,EAAMmE,iBAAiB,QAASwR,GAChC3V,EAAMmE,iBAAiB,QAASyR,GAChC5V,EAAMmE,iBAAiB,aAAc0R,GACrC7V,EAAMmE,iBAAiB,iBAAkB2R,GACzC9V,EAAMmE,iBAAiB,WAAY4R,GACnC/V,EAAMmE,iBAAiB,eAAgB8R,GACvCjW,EAAMmE,iBAAiB,iBAAkB+R,GACzClW,EAAMmE,iBAAiB,UAAWgS,GAClCnW,EAAMmE,iBAAiB,UAAWiS,GAClCpW,EAAMmE,iBAAiB,UAAWkS,GAClCrW,EAAMmE,iBAAiB,QAASmS,GAEzB,KACLtW,EAAMkQ,oBAAoB,OAAQwF,GAClC1V,EAAMkQ,oBAAoB,QAASyF,GACnC3V,EAAMkQ,oBAAoB,QAAS0F,GACnC5V,EAAMkQ,oBAAoB,aAAc2F,GACxC7V,EAAMkQ,oBAAoB,iBAAkB4F,GAC5C9V,EAAMkQ,oBAAoB,WAAY6F,GACtC/V,EAAMkQ,oBAAoB,eAAgB+F,GAC1CjW,EAAMkQ,oBAAoB,iBAAkBgG,GAC5ClW,EAAMkQ,oBAAoB,UAAWiG,GACrCnW,EAAMkQ,oBAAoB,UAAWkG,GACrCpW,EAAMkQ,oBAAoB,UAAWmG,GACrCrW,EAAMkQ,oBAAoB,QAASoG,KAEpC,CAAC5J,EAAQC,EAASC,EAASC,EAAcC,GAAYC,GAAgBK,GAASD,GAASxI,GAAY8H,EAAS5N,OAAQ+T,KAGvH1N,EAAU,KACR,MAAMqR,EAAyB,KAC7B,MAAMC,IAASvW,SAASuH,kBACxB4G,GAAgBoI,GAChBxJ,KAAqBwJ,IAGvB,OADAvW,SAASkE,iBAAiB,mBAAoBoS,GACvC,IAAMtW,SAASiQ,oBAAoB,mBAAoBqG,IAC7D,CAACvJ,KAGJ9H,EAAU,KACR,MAAMlF,EAAQuB,GAAS0B,QACvB,IAAKjD,EAAO,OACZ,MAAMyW,EAAiB,KAAQnI,IAAe,GAAOrB,MAAc,IAC7DyJ,EAAiB,KAAQpI,IAAe,GAAQrB,MAAc,IAGpE,OAFAjN,EAAMmE,iBAAiB,wBAAyBsS,GAChDzW,EAAMmE,iBAAiB,wBAAyBuS,GACzC,KACL1W,EAAMkQ,oBAAoB,wBAAyBuG,GACnDzW,EAAMkQ,oBAAoB,wBAAyBwG,KAEpD,CAACzJ,KAGJ/H,EAAU,KACR,MAAMyR,EAAiB5D,IACrB,GAAKzF,GAAarK,SAAS2T,SAAS3W,SAAS4W,eAE7C,OAAQ9D,EAAElO,KACR,IAAK,IACL,IAAK,IACHkO,EAAEC,iBACF/B,KACA,MACF,IAAK,IACH8B,EAAEC,iBACFxB,KACA,MACF,IAAK,IACHuB,EAAEC,iBACFrB,KACA,MACF,IAAK,YACHoB,EAAEC,iBACF7B,GAAK/N,GAAc,IACnB,MACF,IAAK,aACH2P,EAAEC,iBACF7B,GAAK/N,GAAc,IACnB,MACF,IAAK,UACH2P,EAAEC,iBACF5B,GAAUrD,GAAc,IACxB,MACF,IAAK,YACHgF,EAAEC,iBACF5B,GAAUrD,GAAc,MAM9B,OADA9N,SAASkE,iBAAiB,UAAWwS,GAC9B,IAAM1W,SAASiQ,oBAAoB,UAAWyG,IACpD,CAAC1F,GAAYO,GAAYG,GAAkBR,GAAMC,GAAWhO,GAAa2K,KAG5E7I,EAAU,IACD,KACDsI,GAAmBvK,SAAS8D,aAAayG,GAAmBvK,UAEjE,IAGHiC,EAAU,KACR,MAAM4R,EAAc,IAAMvD,KAC1B,GAAIpD,GAAYI,QAEd,OADAtQ,SAASkE,iBAAiB,QAAS2S,GAC5B,IAAM7W,SAASiQ,oBAAoB,QAAS4G,IAEpD,CAAC3G,GAAYI,QAASgD,KAGzBwD,EAAoB1L,EAAK,KAAA,CACvBhI,QAAM0N,SAAOC,QAAMG,QAAMC,aAAWE,QAAMC,UAAQC,cAClDE,mBAAiBzJ,kBAAgB0J,oBACjCC,YAAUE,WAASG,aAAWC,mBAAiBE,cAC/C4E,eAAgB,IAAMzV,GAAS0B,SAASG,aAAe,EACvD6T,YAAa,IAAM1V,GAAS0B,SAAS5F,UAAY,EACjD6Z,UAAW,IAAM3V,GAAS0B,SAAS8I,QAAU,EAC7CkC,QAAS,IAAM1M,GAAS0B,SAASuI,QAAS,EAC1CkC,UAAW,KAAOnM,GAAS0B,SAASiO,OACpC/C,aAAc,IAAMA,GACpBgJ,MAAO,IAAM9I,GACb+I,gBAAiB,IAAM7V,GAAS0B,UAC9B,CAACI,GAAM0N,GAAOC,GAAMG,GAAMC,GAAWE,GAAMC,GAAQC,GAAYE,GAAiBzJ,GAAgB0J,GAAkBC,GAAUE,GAASG,GAAWC,GAAiBE,GAAYjE,GAAcE,KAE/L,MAAMgJ,GAAkBha,GAAY+F,GAAc/F,GAAY,IAAM,EAEpE,OACEia,EAAA,MAAA,CACEjM,IAAKiC,GACLrD,UAAWA,EACXc,MAAO,CACLnG,SAAUuJ,GAAe,QAAU,WACnCoJ,MAAOpJ,GAAe,OAAIlR,EAC1Bua,OAAQrJ,GAAe,UAAOlR,EAC9BkB,QACAsZ,YAAatJ,GAAe,QAAU,OACtC9P,OAAQ8P,GAAe,YAASlR,EAChCya,gBAAiB,OACjBC,SAAU,SACVC,WAAY,OACZC,WAAY,oEACZC,wBAAyB,cACzBC,YAAa,kBACVhN,GAELiN,YAAc1O,QAA6BrM,EAAlBwX,GACzBwD,aAAc,KAAO3O,IAAYoE,IAAaiB,IAAgB,GAC9DuJ,aAAcxD,GACdyD,YAAapD,GACbqD,WAAY/C,GACZgD,cAAevF,GACfwF,SAAU,YAGVxO,EAAA,QAAA,CACEuB,IAAK9J,GACLwJ,MAAO,CAAE5M,MAAO,OAAQE,OAAQ,OAAQka,UAAW,WACnDtX,IAAK2P,GACLtF,OAAQuF,GACRtF,SAAUA,EACVC,MAAOA,EACPC,KAAMqD,GACNtC,QAASA,EACTgM,eACAC,QAAShD,GAAgBvL,SAExBgC,EAAWnG,IAAI,CAAC2S,EAAO/O,IACtBG,EAAA,QAAA,CAAe6O,KAAMD,EAAMC,KAAM1X,IAAKyX,EAAMzX,IAAK2X,QAASF,EAAMG,QAASpG,MAAOiG,EAAMjG,MAAOqG,QAASJ,EAAMI,SAAhGnP,MAKf4E,KAAchS,IACbuN,SAAKiB,MAAO,CAAEnG,SAAU,WAAY2S,MAAO,EAAGwB,QAAS,OAAQC,WAAY,SAAUC,eAAgB,SAAUC,cAAe,QAAQhP,SACpIJ,EAAA,MAAA,CACEiB,MAAO,CACL5M,MAAO,GACPE,OAAQ,GACR8a,OAAQ,wBACRC,eAAgBhN,EAChBiN,aAAc,MACdC,UAAW,+BAOnBxP,EAAA,QAAA,CAAAI,SAAQ,0DAGP3N,IACC+a,EAAA,MAAA,CAAKvM,MAAO,CAAEnG,SAAU,WAAY2S,MAAO,EAAGwB,QAAS,OAAQQ,cAAe,SAAUP,WAAY,SAAUC,eAAgB,SAAUO,WAAY,kBAAmBC,MAAO,QAAQvP,SAAA,CACpLJ,EAAA,MAAA,CAAKiB,MAAO,CAAE5M,MAAO,GAAIE,OAAQ,GAAIqb,aAAc,IAAM1P,KAAMoC,EAAarC,QAAQ,YAAWG,SAC7FJ,EAAA,OAAA,CAAMK,EAAE,uGAEVL,EAAA,IAAA,CAAGiB,MAAO,CAAE4O,SAAU,GAAID,aAAc,GAAGxP,SAAA,wBAC3CJ,EAAA,SAAA,CACE2O,QAAS,KAAQhK,GAAS,MAAOlN,GAAS0B,SAAS2W,QACnD7O,MAAO,CACL8O,QAAS,WACTR,aAAc,EACdF,OAAQ,OACRzB,gBAAiBtL,EACjBqN,MAAO,OACPK,WAAY,IACZC,OAAQ,WACT7P,SAAA,aAQNtI,IAAeG,IACduV,EAAA,MAAA,CAAKvM,MAAO,CAAEnG,SAAU,WAAY2S,MAAO,EAAGC,OAAQ,IAAItN,SAAA,CACxDoN,EAAA,MAAA,CAAKvM,MAAO,CAAEnG,SAAU,WAAY0O,IAAK,GAAIF,KAAM,GAAI2F,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,WAAYL,WAAY,kBAAmBH,aAAc,EAAGI,MAAO,OAAQE,SAAU,IAAIzP,SAAA,CACvMJ,EAAA,OAAA,CAAMiB,MAAO,CAAE8O,QAAS,UAAWR,aAAc,EAAGM,SAAU,GAAIG,WAAY,IAAKpC,gBAAiBtL,EAAaqN,MAAO,QAAQvP,SAAA,OAChIoN,EAAA,OAAA,CAAApN,SAAA,CAAO/E,EAAWlD,IAAgB,mBAEpC6H,EAAA,SAAA,CACE2O,QAAStW,GAAUoC,QAAStH,EAC5Bgd,UAAW9X,GACX4I,MAAO,CACLnG,SAAU,WACVsV,OAAQ,GACRC,MAAO,GACPN,QAAS,WACTV,OAAQ,iBACRE,aAAc,EACdG,WAAY,cACZC,MAAO,OACPM,OAAQ5X,GAAU,UAAY,cAC9BiY,QAASjY,GAAU,EAAI,IACxB+H,SAEA/H,GAAU,UAAY,WAAWuB,KAAK2W,KAAKpY,YAMjDkO,GAAYI,SACX+G,EAAA,MAAA,CACEvM,MAAO,CACLnG,SAAU,WACVwO,KAAMjD,GAAYE,EAClBiD,IAAKnD,GAAYG,EACjBkH,OAAQ,GACR8C,SAAU,IACVd,WAAY,sBACZe,eAAgB,YAChBlB,aAAc,EACdmB,UAAW,6BACX7C,SAAU,UAEZc,QAAS1F,GAAKA,EAAE0H,4BAGhBnD,EAAA,SAAA,CACEvM,MAAO,CAAE5M,MAAO,OAAQ4a,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,YAAaL,WAAY,cAAeL,OAAQ,OAAQM,MAAO,OAAQE,SAAU,GAAII,OAAQ,UAAWW,UAAW,QACpMjC,QAAS,KAAQxH,KAAcsC,MAAqBrJ,SAAA,CAEvCJ,EAAZ4D,GAAatD,EAAgBP,MAC9BC,EAAA,OAAA,CAAMiB,MAAO,CAAE4P,KAAM,GAAGzQ,SAAGwD,GAAY,QAAU,SACjD5D,EAAA,OAAA,CAAMiB,MAAO,CAAE0O,MAAO,wBAAyBE,SAAU,IAAIzP,SAAA,aAI/DoN,EAAA,SAAA,CACEvM,MAAO,CAAE5M,MAAO,OAAQ4a,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,YAAaL,WAAY,cAAeL,OAAQ,OAAQM,MAAO,OAAQE,SAAU,GAAII,OAAQ,UAAWW,UAAW,QACpMjC,QAAS,KAAQjH,KAAc+B,MAAqBrJ,SAAA,CAEzCJ,EAAVmE,GAAW3D,EAAqBD,MACjCP,EAAA,OAAA,CAAMiB,MAAO,CAAE4P,KAAM,YAAM1M,GAAU,SAAW,SAChDnE,UAAMiB,MAAO,CAAE0O,MAAO,wBAAyBE,SAAU,IAAIzP,SAAA,SAG/DJ,EAAA,MAAA,CAAKiB,MAAO,CAAE1M,OAAQ,EAAGmb,WAAY,wBAAyBoB,OAAQ,WAGtEtD,YACEvM,MAAO,CAAE5M,MAAO,OAAQ4a,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,YAAaL,WAAY,cAAeL,OAAQ,OAAQM,MAAO,OAAQE,SAAU,GAAII,OAAQ,UAAWW,UAAW,QACpMjC,QAAS,KAAQhH,KAAc8B,MAAqBrJ,SAAA,CAEpDJ,EAACa,EAAU,CAAA,GACXb,EAAA,OAAA,CAAMiB,MAAO,CAAE4P,KAAM,GAAGzQ,SAAG4E,GAAS,eAAiB,gBACpDA,IAAUhF,UAAMiB,MAAO,CAAE0O,MAAOrN,GAAalC,SAAA,SAIhDoN,EAAA,SAAA,CACEvM,MAAO,CAAE5M,MAAO,OAAQ4a,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,YAAaL,WAAY,cAAeL,OAAQ,OAAQM,MAAO,OAAQE,SAAU,GAAII,OAAQ,UAAWW,UAAW,QACpMjC,QAAS,KACP,MAAMoC,EAAS,CAAC,GAAK,IAAM,EAAG,KAAM,IAAK,GACnCC,EAAMD,EAAOE,QAAQnM,IAC3BsD,GAAgB2I,GAAQC,EAAM,GAAKD,EAAOhc,SAC1C0U,gBAGFzJ,EAACe,MACDyM,EAAA,OAAA,CAAMvM,MAAO,CAAE4P,KAAM,GAAGzQ,SAAA,CAAA,UAAU0E,GAAY,UAGhD9E,EAAA,MAAA,CAAKiB,MAAO,CAAE1M,OAAQ,EAAGmb,WAAY,wBAAyBoB,OAAQ,WAGrEjP,GAAO1L,SAASqH,yBACfgQ,EAAA,SAAA,CACEvM,MAAO,CAAE5M,MAAO,OAAQ4a,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,YAAaL,WAAY,cAAeL,OAAQ,OAAQM,MAAO,OAAQE,SAAU,GAAII,OAAQ,UAAWW,UAAW,QACpMjC,QAAS,KAAQxG,KAAasB,gBAE9BzJ,EAACW,EAAO,CAAA,GACRX,UAAMiB,MAAO,CAAE4P,KAAM,GAAGzQ,SAAA,uBACxBJ,EAAA,OAAA,CAAMiB,MAAO,CAAE0O,MAAO,wBAAyBE,SAAU,sBAK7DrC,EAAA,SAAA,CACEvM,MAAO,CAAE5M,MAAO,OAAQ4a,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,YAAaL,WAAY,cAAeL,OAAQ,OAAQM,MAAO,OAAQE,SAAU,GAAII,OAAQ,UAAWW,UAAW,QACpMjC,QAAS,KAAQ9G,KAAoB4B,gBAErCzJ,EAACS,MACDT,EAAA,OAAA,CAAMiB,MAAO,CAAE4P,KAAM,GAAGzQ,SAAA,eACxBJ,EAAA,OAAA,CAAMiB,MAAO,CAAE0O,MAAO,wBAAyBE,SAAU,IAAIzP,SAAA,SAG/DJ,EAAA,MAAA,CAAKiB,MAAO,CAAE1M,OAAQ,EAAGmb,WAAY,wBAAyBoB,OAAQ,WAGtEtD,EAAA,SAAA,CACEvM,MAAO,CAAE5M,MAAO,OAAQ4a,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAIH,QAAS,YAAaL,WAAY,cAAeL,OAAQ,OAAQM,MAAO,OAAQE,SAAU,GAAII,OAAQ,UAAWW,UAAW,QACpMjC,QAAShF,aAET3J,EAACc,MACDd,EAAA,OAAA,CAAMiB,MAAO,CAAE4P,KAAM,GAAGzQ,SAAA,sBAI1BJ,EAAA,MAAA,CAAKiB,MAAO,CAAE8O,QAAS,WAAYF,SAAU,GAAIF,MAAO,wBAAyBiB,UAAW,SAAUM,UAAW,mCAAmC9Q,SAAA,0CAOvJwB,IAAa9J,IACZ0V,EAAA,MAAA,CACEvM,MAAO,CACLnG,SAAU,WACVsV,OAAQ,EACR9G,KAAM,EACN+G,MAAO,EACPX,WAAY,gEACZK,QAASvQ,GAAW,iBAAmB,GACvC2R,WAAY3R,GAAW,GAAK,GAC5B4R,WAAY,eACZd,QAAS1L,GAAe,EAAI,EAC5BwK,cAAexK,GAAe,OAAS,QACxCxE,SAAA,CAGDoN,SACEjM,IAAKkC,GACLkL,QAAS7E,GACToE,YAAc1O,QAAiCrM,EAAtB4W,GACzBoE,aAAe3O,QAAiCrM,EAAtBqX,GAC1B6D,YAAcpF,IACZ,GAAIzJ,GAAU,CACZ,MAAM2J,EAAO1F,GAAYtK,SAASiQ,wBAClC,GAAID,GAAQ5V,GAAU,CACpB,MAAMsX,EAAQ5B,EAAE6B,QAAQ,GAClBhR,EAAUF,KAAKC,IAAI,EAAGD,KAAK0C,IAAI,GAAIuO,EAAMxB,QAAUF,EAAKG,MAAQH,EAAK9U,QAC3EkR,GAAa,CAAEvJ,KAAMlC,EAAUvG,GAAUgT,EAAGsE,EAAMxB,QAAUF,EAAKG,MACnE,CACF,GAEFgF,WAAarF,IACPzJ,IAAY8F,KACd+B,GAAK/B,GAAUtJ,MACfuJ,GAAa,QAGjBtE,MAAO,CACLnG,SAAU,WACVvG,OAAQiL,GAAW,GAAK,EACxBkQ,WAAY,wBACZH,aAAc/P,GAAW,EAAI,EAC7BoQ,aAAcpQ,GAAW,GAAK,GAC9ByQ,OAAQ,WACT7P,SAAA,CAGDJ,EAAA,MAAA,CACEiB,MAAO,CACLnG,SAAU,WACV0O,IAAK,EACLF,KAAM,EACN/U,OAAQ,OACRF,MAAO,GAAGuL,MACV8P,WAAY,wBACZH,aAAc/P,GAAW,EAAI,KAIjCQ,EAAA,MAAA,CACEiB,MAAO,CACLnG,SAAU,WACV0O,IAAK,EACLF,KAAM,EACN/U,OAAQ,OACRF,MAAO,GAAGkZ,MACVmC,WAAYpN,EACZiN,aAAc/P,GAAW,EAAI,KAIjCQ,EAAA,MAAA,CACEiB,MAAO,CACLnG,SAAU,WACV0O,IAAK,MACLF,KAAM,GAAGiE,MACT8D,UAAW,wBACXhd,MAAOmL,GAAW,GAAK,GACvBjL,OAAQiL,GAAW,GAAK,GACxB+P,aAAc,MACdG,WAAYpN,EACZoO,UAAW,+BAIdpL,IACCkI,EAAA,MAAA,CACEvM,MAAO,CACLnG,SAAU,WACVsV,OAAQ,OACR9G,KAAMhE,GAAUiB,EAChB8K,UAAW,mBACXzB,aAAc,GACdX,QAAS,OACTQ,cAAe,SACfP,WAAY,SACZE,cAAe,QAChBhP,SAAA,CAGAoF,IACCxF,EAAA,MAAA,CACEiB,MAAO,CACL5M,MAAO,IACPE,OAAQ,GACRgb,aAAc,EACd1B,SAAU,SACV+B,aAAc,EACdc,UAAW,6BACXrB,OAAQ,aAAa/M,KACtBlC,SAEDJ,EAAA,MAAA,CAAK7I,IAAKqO,GAAc8L,IAAI,GAAGrQ,MAAO,CAAE5M,MAAO,OAAQE,OAAQ,OAAQka,UAAW,aAItFzO,EAAA,MAAA,CACEiB,MAAO,CACL8O,QAAS,WACTL,WAAY,kBACZC,MAAO,OACPE,SAAU,GACVG,WAAY,IACZT,aAAc,EACdgC,WAAY,UACbnR,SAEA/E,EAAWiK,GAAUtJ,cAO9BwR,EAAA,MAAA,CAAKvM,MAAO,CAAEgO,QAAS,OAAQC,WAAY,SAAUgB,IAAK1Q,GAAW,EAAI,EAAGgS,SAAU,QAAQpR,SAAA,CAE5FJ,EAAA,SAAA,CACE2O,QAASxH,GAAU,aACPvD,GAAY,QAAU,OAClC3C,MAAO,CACL5M,MAAOmL,GAAW,GAAK,GACvBjL,OAAQiL,GAAW,GAAK,GACxBgR,SAAUhR,GAAW,GAAK,GAC1ByP,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBI,aAAc,MACdG,WAAY,cACZL,OAAQ,OACRM,MAAO,OACPM,OAAQ,UACRF,QAAS,GACV3P,SAEYJ,EAAZ4D,GAAatD,EAAgBP,EAAP,CAAA,KAIxBmC,IAAkB1C,IACjBgO,EAAA,MAAA,CAAKvM,MAAO,CAAEgO,QAAS,OAAQC,WAAY,SAAUgB,IAAK,GAAG9P,SAAA,CAC3DJ,EAAA,SAAA,CACE2O,QAASjH,GAAU,aACPvD,GAAU,SAAW,OACjClD,MAAO,CACL5M,MAAO,GACPE,OAAQ,GACR0a,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAY,cACZL,OAAQ,OACRM,MAAO,OACPM,OAAQ,WACT7P,SAE+BJ,EAA/BmE,IAA2B,IAAhBF,GAAqBzD,EAAqBD,EAAP,MAEjDP,EAAA,QAAA,CACE5L,KAAK,QACLkI,IAAI,IACJzC,IAAI,IACJ4X,KAAK,OACLrV,MAAO+H,GAAU,EAAIF,GACrByN,SAAUzI,GAAK3B,GAAUnS,WAAW8T,EAAE0I,OAAOvV,QAC7C6E,MAAO,CACL5M,MAAO,GACPE,OAAQ,EACR0b,OAAQ,UACR3N,YAAaA,QAOpBJ,GAAiB1C,IAChBQ,EAAA,SAAA,CACE2O,QAASjH,gBACGvD,GAAU,SAAW,OACjClD,MAAO,CACL5M,MAAO,GACPE,OAAQ,GACRic,SAAU,GACVvB,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAY,cACZL,OAAQ,OACRM,MAAO,OACPM,OAAQ,UACRF,QAAS,GACV3P,SAE+BJ,EAA/BmE,IAA2B,IAAhBF,GAAqBzD,EAAqBD,EAAP,MAKnDiN,EAAA,MAAA,CAAKvM,MAAO,CACV0O,MAAO,OACPE,SAAUrQ,GAAW,GAAK,GAC1BuO,WAAY,YACZ6D,WAAYpS,GAAW,EAAI,EAC3B+R,WAAY,UACbnR,SAAA,CACE/E,EAAW/B,KAAekG,IAAY,MAAMnE,EAAW9H,SAI1DyM,EAAA,MAAA,CAAKiB,MAAO,CAAE4P,KAAM,KAGnBlO,EAAS5N,OAAS,GACjBiL,EAAA,SAAA,CACE2O,QAAS,IAAMtJ,IAAiBD,IAAa,aAClC,WACXnE,MAAO,CACL5M,MAAOmL,GAAW,GAAK,GACvBjL,OAAQiL,GAAW,GAAK,GACxBgR,SAAUhR,GAAW,GAAK,GAC1ByP,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAYtK,GAAe,wBAA0B,cACrDiK,OAAQ,OACRM,MAAOrN,EACP2N,OAAQ,UACRV,aAAc,EACdQ,QAAS,GACV3P,SAEDJ,EAACgB,EAAY,CAAA,MAKfxB,IACAQ,EAAA,SAAA,CACE2O,QAAS,KACP,MAAMzY,EAAQuB,GAAS0B,QACnBjD,GAAS,WAAYA,EACtBA,EAAc2b,OAAOC,SAAStX,MAAM,KACnCuX,MAAM,8BAGRA,MAAM,0BAET,aACU,OACX9Q,MAAO,CACL5M,MAAO,GACPE,OAAQ,GACR0a,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAY,cACZL,OAAQ,OACRM,MAAO,OACPM,OAAQ,UACRV,aAAc,EACdQ,QAAS,GACV3P,SAEDJ,EAACkB,EAAQ,CAAA,KAKZa,GACCyL,EAAA,MAAA,CAAKvM,MAAO,CAAEnG,SAAU,YAAYsF,SAAA,CAClCJ,EAAA,SAAA,CACE2O,QAAS,IAAMxJ,IAAiBD,IAAa,aAClC,WACXjE,MAAO,CACL5M,MAAOmL,GAAW,GAAK,GACvBjL,OAAQiL,GAAW,GAAK,GACxBgR,SAAUhR,GAAW,GAAK,GAC1ByP,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAYxK,GAAe,wBAA0B,cACrDmK,OAAQ,OACRM,MAAO,OACPM,OAAQ,UACRV,aAAc,EACdQ,QAAS,GACV3P,SAEDJ,EAACY,EAAY,MAGdsE,IACCsI,EAAA,MAAA,CACEvM,MAAO,CACLnG,SAAU0E,GAAW,QAAU,WAC/B4Q,OAAQ5Q,GAAW,EAAI,GACvB6Q,MAAkB,EAClB/G,KAAM9J,GAAW,OAAIrM,EACrBqd,SAAUhR,GAAW,OAAS,IAC9BwS,UAAWxS,GAAW,YAASrM,EAC/Buc,WAAY,UACZH,aAAc/P,GAAW,gBAAkB,EAC3CkR,UAAW,6BACXrB,OAAQ7P,GAAW,OAAS,kCAC5BkO,OAAQ,IACRG,SAAU,UACXzN,SAAA,CAEDJ,EAAA,MAAA,CAAKiB,MAAO,CAAE8O,QAAS,YAAakC,aAAc,kCAAmCtC,MAAO,wBAAyBE,SAAU,GAAIG,WAAY,IAAKkC,cAAe,YAAaC,cAAe,IAAK/R,SAAA,mBAGnM4B,EAAe/F,IAAImW,GAClB5E,EAAA,SAAA,CAEEmB,QAAS,IAAMvG,GAAgBgK,GAC/BnR,MAAO,CACL5M,MAAO,OACP0b,QAASvQ,GAAW,YAAc,YAClCyP,QAAS,OACTC,WAAY,SACZC,eAAgB,gBAChBO,WAAY,cACZL,OAAQ,OACRM,MAAO7K,KAAiBsN,EAAQ9P,EAAc,OAC9C2N,OAAQ,UACRJ,SAAUrQ,GAAW,GAAK,IAC3BY,SAAA,CAEDJ,EAAA,OAAA,CAAAI,SAAiB,IAAVgS,EAAc,SAAW,GAAGA,OAClCtN,KAAiBsN,GAASpS,EAAA,OAAA,CAAAI,SAAA,QAhBtBgS,UAyBhBvQ,IAAQrC,IAAYrJ,SAASqH,yBAC5BwC,EAAA,SAAA,CACE2O,QAASxG,GAAS,aACP,qBACXlH,MAAO,CACL5M,MAAO,GACPE,OAAQ,GACR0a,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAY,cACZL,OAAQ,OACRM,MAAO,OACPM,OAAQ,UACRV,aAAc,EACdQ,QAAS,GACV3P,SAEDJ,EAACW,EAAO,CAAA,KAKXmB,GACC9B,EAAA,SAAA,CACE2O,QAAS9G,GAAgB,aACbxD,GAAe,kBAAoB,aAC/CpD,MAAO,CACL5M,MAAOmL,GAAW,GAAK,GACvBjL,OAAQiL,GAAW,GAAK,GACxBgR,SAAUhR,GAAW,GAAK,GAC1ByP,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAY,cACZL,OAAQ,OACRM,MAAO,OACPM,OAAQ,UACRV,aAAc,EACdQ,QAAS,GACV3P,SAEeJ,EAAfqE,GAAgB3D,EAAyBD,EAAP,CAAA,WAQ5CjB,IAAYmG,IAAcL,IACzBtF,EAAA,MAAA,CACEiB,MAAO,CACLnG,SAAU,WACV0O,IAAK,MACLF,KAAM,MACN+H,UAAW,wBACXtB,QAAS,YACTL,WAAY,kBACZH,aAAc,EACdI,MAAO,OACPE,SAAU,GACVG,WAAY,IACZZ,cAAe,QAChBhP,SAEA/E,EAAWiK,GAAUtJ,SAKxB4H,KAAca,KAAchS,KAAUqF,IACtCkI,EAAA,SAAA,CACE2O,QAASxH,GAAU,aACR,OACXlG,MAAO,CACLnG,SAAU,WACV0O,IAAK,MACLF,KAAM,MACN+H,UAAW,wBACXhd,MAAOmL,GAAW,GAAK,GACvBjL,OAAQiL,GAAW,GAAK,GACxB+P,aAAc,MACdN,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBE,OAAQ,OACRY,OAAQ,UACRrC,gBAAiBtL,EACjBoO,UAAW,6BACXU,WAAY,iBACZrB,QAAS,GACV3P,SAEDJ,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,QAAQe,MAAO,CAAE5M,MAAOmL,GAAW,GAAK,GAAIjL,OAAQiL,GAAW,GAAK,GAAIoS,WAAY,GAAGxR,SACnHJ,EAAA,OAAA,CAAMK,EAAE,sBAMbsC,EAAS5N,OAAS,GACjByY,EAAA,MAAA,CACEvM,MAAO,CACLnG,SAAU,WACV0O,IAAKhK,GAAW,OAAS,EACzB4Q,OAAQ5Q,GAAW,OAAIrM,EACvBkd,MAAO,EACP/G,KAAM9J,GAAW,OAAIrM,EACrBkB,MAAOmL,GAAW,OAAS,IAC3BjL,OAAQiL,GAAW,MAAQ,OAC3BwS,UAAWxS,GAAW,YAASrM,EAC/Buc,WAAYlQ,GACR,sEACA,uEACJiR,eAAgB,aAChBY,UAAWjM,GACP,gBACC5F,GAAW,mBAAqB,mBACrC4R,WAAY,0BACZ1D,OAAQ,GACRuB,QAAS,OACTQ,cAAe,SACf4C,WAAY7S,GAAW,OAAS,kCAChC0R,UAAW1R,GAAW,kCAAoC,OAC1D+P,aAAc/P,GAAW,gBAAkB,GAC5CY,SAAA,CAGAZ,IACCQ,EAAA,MAAA,CAAKiB,MAAO,CACV5M,MAAO,GACPE,OAAQ,EACRmb,WAAY,wBACZH,aAAc,EACduB,OAAQ,mBAKZtD,EAAA,MAAA,CAAKvM,MAAO,CACVgO,QAAS,OACTC,WAAY,SACZC,eAAgB,gBAChBY,QAASvQ,GAAW,iBAAmB,YACvCyS,aAAc,mCACf7R,SAAA,CACCoN,EAAA,MAAA,CAAApN,SAAA,CACEJ,EAAA,MAAA,CAAKiB,MAAO,CAAE0O,MAAOrN,EAAauN,SAAU,GAAIG,WAAY,IAAKkC,cAAe,YAAaC,cAAe,EAAGvC,aAAc,GAAGxP,SAAA,aAGhIoN,EAAA,MAAA,CAAKvM,MAAO,CAAE0O,MAAO,OAAQE,SAAU,GAAIG,WAAY,eACpDrN,EAAS5N,OAAM,gBAGpBiL,EAAA,SAAA,CACE2O,QAAS,IAAMtJ,IAAgB,GAC/BpE,MAAO,CACL5M,MAAOmL,GAAW,GAAK,GACvBjL,OAAQiL,GAAW,GAAK,GACxByP,QAAS,OACTC,WAAY,SACZC,eAAgB,SAChBO,WAAY,wBACZL,OAAQ,OACRE,aAAc,MACdI,MAAO,OACPM,OAAQ,WACT7P,SAEDJ,EAACmB,EAAS,CAAA,QAKdnB,EAAA,MAAA,CAAKiB,MAAO,CAAE4P,KAAM,EAAGyB,UAAW,OAAQvC,QAAS,SAAS3P,SACzDuC,EAAS1G,IAAI,CAACsW,EAAMC,IACnBhF,YAEEmB,QAAS,KACPhI,GAAwB6L,GACxBjP,KAAuBiP,EAAOD,GAC9B1V,WAAW,IAAMtD,KAAQ,MAE3B0H,MAAO,CACL5M,MAAO,OACP0b,QAAS,YACTd,QAAS,OACTC,WAAY,SACZgB,IAAK,GACLR,WAAYhJ,KAAyB8L,EAAQ,GAAGlQ,MAAkB,cAClE+M,OAAQ,OACRgD,WAAY3L,KAAyB8L,EAAQ,aAAalQ,IAAgB,wBAC1E2N,OAAQ,UACRW,UAAW,OACXQ,WAAY,mBACbhR,SAAA,CAGDoN,EAAA,MAAA,CAAKvM,MAAO,CACV5M,MAAO,IACPE,OAAQ,GACRgb,aAAc,EACd1B,SAAU,SACV6B,WAAY,OACZ+C,WAAY,EACZ3X,SAAU,YACXsF,SAAA,CACEmS,EAAK/Q,OACJxB,EAAA,MAAA,CAAK7I,IAAKob,EAAK/Q,OAAQ8P,IAAI,GAAGrQ,MAAO,CAAE5M,MAAO,OAAQE,OAAQ,OAAQka,UAAW,WAEjFzO,EAAA,MAAA,CAAKiB,MAAO,CAAE5M,MAAO,OAAQE,OAAQ,OAAQ0a,QAAS,OAAQC,WAAY,SAAUC,eAAgB,SAAUQ,MAAO,yBAAyBvP,SAC5IJ,EAAA,MAAA,CAAKC,QAAQ,YAAYC,KAAK,eAAee,MAAO,CAAE5M,MAAO,GAAIE,OAAQ,IAAI6L,SAC3EJ,EAAA,OAAA,CAAMK,EAAE,uHAKbqG,KAAyB8L,GACxBxS,EAAA,MAAA,CAAKiB,MAAO,CACVnG,SAAU,WACV2S,MAAO,EACPiC,WAAY,kBACZT,QAAS,OACTC,WAAY,SACZC,eAAgB,UACjB/O,SACCJ,EAAA,MAAA,CAAKiB,MAAO,CAAE0O,MAAOrN,EAAauN,SAAU,IAAIzP,SAAA,QAInDmS,EAAKhf,UACJyM,EAAA,MAAA,CAAKiB,MAAO,CACVnG,SAAU,WACVsV,OAAQ,EACRC,MAAO,EACPN,QAAS,UACTL,WAAY,kBACZH,aAAc,EACdM,SAAU,GACVF,MAAO,OACPK,WAAY,KACb5P,SACE/E,EAAWkX,EAAKhf,eAMvBia,EAAA,MAAA,CAAKvM,MAAO,CAAE4P,KAAM,EAAGL,SAAU,GAAGpQ,SAAA,CAClCJ,EAAA,MAAA,CAAKiB,MAAO,CACV0O,MAAOjJ,KAAyB8L,EAAQlQ,EAAc,OACtDuN,SAAU,GACVG,WAAYtJ,KAAyB8L,EAAQ,IAAM,IACnD3E,SAAU,SACV6E,aAAc,WACdnB,WAAY,SACZ3B,aAAc,GACfxP,SACEmS,EAAKvf,QAERwa,EAAA,MAAA,CAAKvM,MAAO,CAAE0O,MAAO,wBAAyBE,SAAU,IAAIzP,SAAA,CAAA,SACnDoS,EAAQ,UApFdD,EAAKzf,UA8FpBkN,EAAA,QAAA,CACEuB,IAAKwE,GACL5O,IAAK2P,GACL7F,MAAO,CAAEgO,QAAS,QAClBvN,SACAgB,QAAQ,aAIV1C,EAAA,SAAA,CAAQuB,IAAKuE,GAAkB7E,MAAO,CAAEgO,QAAS,eAMzD7N,EAAgBuR,YAAc,wBCpkDjBC,EAA0C,EACrDtZ,cACA/F,WACAqM,WACAiT,SACAC,mBACA3C,YAAW,MAEX,MAAM1M,EAAcjL,EAAuB,OACpCua,EAAYC,GAAiBhb,GAAS,IACtCsN,EAAWC,GAAgBvN,EAAwB,OACnDib,EAAeC,GAAoBlb,EAAS,GAE7Cmb,EAAgBhX,EAAW7C,EAAa/F,GACxC6f,EAAkBjX,EAAWyD,EAAUrM,GAEvC8f,EAAsBna,EACzBmQ,IACC,IAAK5F,EAAYtK,QAAS,OAAO,EACjC,MAAMgQ,EAAO1F,EAAYtK,QAAQiQ,wBAEjC,OADgB7M,GAAO8M,EAAUF,EAAKG,MAAQH,EAAK9U,MAAO,EAAG,GAC5Cd,GAEnB,CAACA,IAGGoX,EAAkBzR,EACrB+P,IACC,IAAKxF,EAAYtK,QAAS,OAC1B,MAAMgQ,EAAO1F,EAAYtK,QAAQiQ,wBAC3BC,EAAU,YAAaJ,EAAIA,EAAEI,QAAU,EACvCvO,EAAWyB,EAAM8M,EAAUF,EAAKG,KAAM,EAAGH,EAAK9U,OAC9C2H,EAAOqX,EAAoBhK,GAEjC6J,EAAiBpY,GACjByK,EAAavJ,GAET+W,GACFF,EAAO7W,IAGX,CAAC+W,EAAYM,EAAqBR,IAG9BS,EAAkBpa,EACrB+P,IACC,GAAIkH,EAAU,OACdlH,EAAEC,iBACF8J,GAAc,GACd,MAAMhX,EAAOqX,EAAoBpK,EAAEI,SACnCwJ,EAAO7W,IAET,CAACmU,EAAUkD,EAAqBR,IAG5BU,EAAgBra,EAAY,KAChC8Z,GAAc,IACb,IAEGQ,EAAmBta,EACtB+P,IACC,MAAMjN,EAAOqX,EAAoBpK,EAAEI,SACnC9D,EAAavJ,IAEf,CAACqX,IAGGI,EAAmBva,EAAY,KACnCqM,EAAa,OACZ,IAGHnK,EAAU,KACR,GAAI2X,EAAY,CACd,MAAMW,EAAoBzK,GAAkB0B,EAAgB1B,GAI5D,OAHAxP,OAAOY,iBAAiB,YAAaqZ,GACrCja,OAAOY,iBAAiB,UAAWkZ,GAE5B,KACL9Z,OAAO2M,oBAAoB,YAAasN,GACxCja,OAAO2M,oBAAoB,UAAWmN,GAE1C,GAEC,CAACR,EAAYpI,EAAiB4I,IAGjC,MAAM3I,EAAmB1R,EACtB+P,IACC,GAAIkH,EAAU,OACdlH,EAAEC,iBACF,MAAM2B,EAAQ5B,EAAE6B,QAAQ,GAClB9O,EAAOqX,EAAoBxI,EAAMxB,SACvC2J,GAAc,GACdH,EAAO7W,IAET,CAACmU,EAAUkD,EAAqBR,IAG5B5H,EAAkB/R,EACrB+P,IACC,IAAK8J,EAAY,OACjB,MAAMlI,EAAQ5B,EAAE6B,QAAQ,GAClB9O,EAAOqX,EAAoBxI,EAAMxB,SACvCwJ,EAAO7W,IAET,CAAC+W,EAAYM,EAAqBR,IAG9BtH,EAAiBrS,EAAY,KACjC8Z,GAAc,IACb,IAEH,OACExF,EAAA,MAAA,CACEjM,IAAKkC,EACLtD,UAAU,wCACVwT,YAAaL,EACbM,aAAcJ,EACdrF,aAAcsF,EACdvF,YAAcjF,GAAM0B,EAAgB1B,EAAE4K,aACtCzF,aAAcxD,EACdyD,YAAapD,EACbqD,WAAY/C,EACZuI,KAAK,SAAQ,aACF,iBAAgB,gBACZ,EAAC,gBACDvgB,kBACA+F,EAAW,iBACV+B,EAAW/B,GAC3BkV,SAAU,EAACpO,SAAA,CAEXoN,EAAA,MAAA,CAAKrN,UAAU,8BAA6BC,SAAA,CAC1CJ,EAAA,MAAA,CACEG,UAAU,uCACVc,MAAO,CAAE5M,MAAO,GAAG+e,QAErBpT,EAAA,MAAA,CACEG,UAAU,qCACVc,MAAO,CAAE5M,MAAO,GAAG8e,QAErBnT,EAAA,MAAA,CACEG,UAAU,qCACVc,MAAO,CAAEqI,KAAM,GAAG6J,WAKrBL,GAAkBiB,SAAyB,OAAdzO,GAC5BkI,EAAA,MAAA,CACErN,UAAU,uCACVc,MAAO,CACLqI,KAAM,GAAG2J,MACT5e,MAAOye,EAAiBze,OAAS,IACjCE,OAAQue,EAAiBve,QAAU,IACpC6L,SAAA,CAEA0S,EAAiBkB,SAChBhU,EAAA,MAAA,CACEiB,MAAO,CACL5M,MAAO,OACPE,OAAQ,OACR0f,gBAAiB,OAAOnB,EAAiBkB,WACzCE,mBAAoBC,EAClB7O,EACA/R,EACAuf,EAAiBsB,UAAY,GAC7BtB,EAAiBze,OAAS,IAC1Bye,EAAiBve,QAAU,IAE7B8f,eAAgB,WAItBrU,EAAA,MAAA,CAAKG,UAAU,oCAAmCC,SAC/C/E,EAAWiK,QAMH,OAAdA,IAAuBwN,GAAkBiB,SACxC/T,EAAA,MAAA,CACEG,UAAU,uCACVc,MAAO,CACLqI,KAAM,GAAG2J,MACT5e,MAAO,OACPE,OAAQ,OACRwb,QAAS,WACV3P,SAEDJ,EAAA,OAAA,CAAMiB,MAAO,CAAE0O,MAAO,QAASE,SAAU,QAAQzP,SAC9C/E,EAAWiK,WASlB6O,EAA0B,CAC9BnY,EACAsY,EACAF,EACA/f,EACAE,KAEA,MAAMie,EAAQ5Y,KAAK8B,MAAMM,EAAOoY,GAKhC,MAAO,IAFK5B,EAFI,GAICne,QAHLuF,KAAK8B,MAAM8W,EADP,IAImBje,OChOxBwL,EAAgC,EAAGI,YAAWoU,OAAO,MAChEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,oBAICC,GAAiC,EAAGH,YAAWoU,OAAO,MACjEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,sCAICE,GAAsC,EAAGJ,YAAWoU,OAAO,MACtEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,kMAICoU,GAAwC,EAAGtU,YAAWoU,OAAO,MACxEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,+FAICqU,GAAqC,EAAGvU,YAAWoU,OAAO,MACrEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,6BAICG,GAAsC,EAAGL,YAAWoU,OAAO,MACtEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,sWAICI,GAAsC,EAAGN,YAAWoU,OAAO,MACtEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,qFAICK,GAA0C,EAAGP,YAAWoU,OAAO,MAC1EvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,oFAICM,GAA+B,EAAGR,YAAWoU,OAAO,MAC/DvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,iHAICsU,GAAmC,EAAGxU,YAAWoU,OAAO,MACnEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,8GAICO,GAAoC,EAAGT,YAAWoU,OAAO,MACpEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,osBAICuU,GAAoC,EAAGzU,YAAWoU,OAAO,MACpEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,kSAICU,GAAiC,EAAGZ,YAAWoU,OAAO,MACjEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,yOAICwU,GAAmC,EAAG1U,YAAWoU,OAAO,MACnEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,0LAICyU,GAAmC,EAAG3U,YAAWoU,OAAO,MACnEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,8CAIC0U,GAAkC,EAAG5U,YAAWoU,OAAO,MAClEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,kDAIC2U,GAAoC,EAAG7U,YAAWoU,OAAO,MACpEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,0CAIC4U,GAAoC,EAAG9U,YAAWoU,OAAO,MACpEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,kCAIC6U,GAAiC,EAAG/U,YAAWoU,OAAO,MACjEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,uGAIC8U,GAAwC,EAAGhV,YAAWoU,OAAO,MACxEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,yIAIC+U,GAAiC,EAAGjV,YAAWoU,OAAO,MACjEvU,EAAA,MAAA,CACEG,UAAWA,EACX9L,MAAOkgB,EACPhgB,OAAQggB,EACRtU,QAAQ,YACRC,KAAK,eACLsU,MAAM,sCAENxU,EAAA,OAAA,CAAMK,EAAE,wDCrQCgV,GAA8C,EACzDpT,SACAP,QACAuB,iBACAqS,mBAEA,MAAMC,EAAqBrc,EACxB+P,IACC,MAAM1B,EAAYpS,WAAW8T,EAAE0I,OAAOvV,OACtC6G,EAAesE,IAEjB,CAACtE,IAgBH,OACEuK,EAAA,MAAA,CAAKrN,UAAU,sCAAqCC,SAAA,CAClDJ,YACEG,UAAU,yBACVwO,QAAS2G,EAAY,aACT5T,EAAQ,SAAW,OAC/B1O,MAAO0O,EAAQ,aAAe,WAC9BtN,KAAK,SAAQgM,SAlBRJ,EADL0B,GAAoB,IAAXO,EACHzB,GAENyB,EAAS,IACHyS,GAENzS,EAAS,IACHwS,GAEFlU,GARgB,MAsBtBP,EAAA,MAAA,CAAKG,UAAU,6CAA4CC,SACzDJ,WACE5L,KAAK,QACL+L,UAAU,mCACV7D,IAAK,EACLzC,IAAK,EACL4X,KAAM,IACNrV,MAAOsF,EAAQ,EAAIO,EACnByP,SAAU6D,eACC,SACXtU,MAAO,CACLyO,WAAY,yEACa,KAAtBhO,EAAQ,EAAIO,gCACsC,KAAtBP,EAAQ,EAAIO,8CC9C1CuT,GAA4C,EACvD1Q,eACA9C,iBACAyT,uBACAC,iBACA9O,UACA+O,iBACAvS,kBACAwS,kBACAxT,aACAyT,eACAC,oBAEA,MAAOC,EAAQC,GAAahe,GAAS,IAC9Bie,EAAaC,GAAkBle,EAAmB,QACnDwL,EAAehL,EAAuB,MAG5C4C,EAAU,KACR,MAAM+a,EAAsBlN,IACtBzF,EAAarK,UAAYqK,EAAarK,QAAQ2T,SAAS7D,EAAE0I,UAC3DqE,GAAU,GACVE,EAAe,UAInB,GAAIH,EAEF,OADA5f,SAASkE,iBAAiB,YAAa8b,GAChC,IAAMhgB,SAASiQ,oBAAoB,YAAa+P,IAGxD,CAACJ,IAEJ,MAAMK,EAAeld,EAAY,KAC/B8c,EAAWtM,IAAUA,GACrBwM,EAAe,SACd,IAEGG,EAAoBnd,EACvBkZ,IACCqD,EAAqBrD,GACrB8D,EAAe,SAEjB,CAACT,IAGGa,EAAsBpd,EACzB0P,IACCxF,IAAkBwF,GAClBsN,EAAe,SAEjB,CAAC9S,IAGGmT,EAAoBrd,EACvB0V,IACCkH,IAAgBlH,GAChBsH,EAAe,SAEjB,CAACJ,IAkIH,OACEtI,EAAA,MAAA,CAAKrN,UAAU,wCAAwCoB,IAAKiC,EAAYpD,SAAA,CACtEJ,EAAA,SAAA,CACEG,UAAU,yBACVwO,QAASyH,EAAY,aACV,WAAU,gBACNL,EACf/iB,MAAM,WACNoB,KAAK,SAAQgM,SAEbJ,EAACY,SAEFmV,GACCvI,SACErN,UAAW,qCACT4V,EAAS,yCAA2C,IACpD3V,SAAA,CAEe,SAAhB6V,GAhJPzI,EAAAgJ,EAAA,CAAApW,SAAA,CACEJ,EAAA,MAAA,CAAKG,UAAU,oCAAmCC,SAAA,aAClDoN,EAAA,SAAA,CACErN,UAAU,mCACVwO,QAAS,IAAMuH,EAAe,SAC9B9hB,KAAK,SAAQgM,SAAA,CAEbJ,EAAA,OAAA,CAAAI,SAAA,mBACAJ,EAAA,OAAA,CAAAI,SAAwB,IAAjB0E,EAAqB,SAAW,GAAGA,UAE3C4Q,GAAkB9O,GAAWA,EAAQ7R,OAAS,GAC7CyY,EAAA,SAAA,CACErN,UAAU,mCACVwO,QAAS,IAAMuH,EAAe,WAC9B9hB,KAAK,mBAEL4L,EAAA,OAAA,CAAAI,SAAA,YACAJ,EAAA,OAAA,CAAAI,SAAOuV,GAAkB,YAG5BC,GAAmBxT,GAAcA,EAAWrN,OAAS,GACpDyY,EAAA,SAAA,CACErN,UAAU,mCACVwO,QAAS,IAAMuH,EAAe,YAC9B9hB,KAAK,SAAQgM,SAAA,CAEbJ,EAAA,OAAA,CAAAI,SAAA,aACAJ,EAAA,OAAA,CAAAI,SAAOyV,GAAgB,cAsHN,UAAhBI,GA/GPzI,EAAAgJ,EAAA,CAAApW,SAAA,CACEJ,EAAA,SAAA,CACEG,UAAU,oCACVwO,QAAS,IAAMuH,EAAe,QAC9BjV,MAAO,CAAEgP,OAAQ,UAAWZ,OAAQ,OAAQK,WAAY,cAAerb,MAAO,OAAQuc,UAAW,QACjGxc,KAAK,SAAQgM,SAAA,qBAIfJ,EAAA,MAAA,CAAKG,UAAU,gCAA+BC,SAC3C4B,EAAe/F,IAAKmW,GACnBpS,EAAA,SAAA,CAEEG,UAAW,iCACT2E,IAAiBsN,EAAQ,uCAAyC,IAEpEzD,QAAS,IAAM0H,EAAkBjE,GACjChe,KAAK,SAAQgM,SAEF,IAAVgS,EAAc,SAAW,GAAGA,MAPxBA,SAoGU,YAAhB6D,GArFPzI,EAAAgJ,EAAA,CAAApW,SAAA,CACEJ,EAAA,SAAA,CACEG,UAAU,oCACVwO,QAAS,IAAMuH,EAAe,QAC9BjV,MAAO,CAAEgP,OAAQ,UAAWZ,OAAQ,OAAQK,WAAY,cAAerb,MAAO,OAAQuc,UAAW,QACjGxc,KAAK,SAAQgM,SAAA,cAIfJ,EAAA,MAAA,CAAKG,UAAU,kCAAiCC,SAC7CwG,GAAS3K,IAAKuM,GACbgF,EAAA,SAAA,CAEErN,UAAW,qCACTwV,IAAmBnN,EAAOI,QAAU,2CAA6C,IAEnF+F,QAAS,IAAM2H,EAAoB9N,EAAOI,SAAWJ,EAAOrR,KAC5D/C,KAAK,SAAQgM,SAAA,CAEbJ,EAAA,OAAA,CAAAI,SAAOoI,EAAOG,OAASH,EAAOI,SAAW,YACxC+M,IAAmBnN,EAAOI,SAAW5I,EAACoV,GAAS,CAACb,KAAM,OARlD/L,EAAOI,SAAWJ,EAAOrR,WA0Ef,aAAhB8e,GA1DPzI,eACExN,EAAA,SAAA,CACEG,UAAU,oCACVwO,QAAS,IAAMuH,EAAe,QAC9BjV,MAAO,CAAEgP,OAAQ,UAAWZ,OAAQ,OAAQK,WAAY,cAAerb,MAAO,OAAQuc,UAAW,QACjGxc,KAAK,SAAQgM,SAAA,eAIfoN,EAAA,MAAA,CAAKrN,UAAU,4CACbqN,EAAA,SAAA,CACErN,UAAW,qCACR0V,EAA4D,GAA7C,4CAElBlH,QAAS,IAAM4H,EAAkB,MACjCniB,KAAK,SAAQgM,SAAA,CAEbJ,EAAA,OAAA,CAAAI,SAAA,SACEyV,GAAgB7V,EAACoV,GAAS,CAACb,KAAM,QAEpCnS,GAAYnG,IAAK2S,GAChBpB,EAAA,SAAA,CAEErN,UAAW,qCACT0V,IAAiBjH,EAAMG,QAAU,2CAA6C,IAEhFJ,QAAS,IAAM4H,EAAkB3H,EAAMG,SACvC3a,KAAK,mBAEL4L,EAAA,OAAA,CAAAI,SAAOwO,EAAMjG,QACZkN,IAAiBjH,EAAMG,SAAW/O,EAACoV,GAAS,CAACb,KAAM,OAR/C3F,EAAMG,uBCrLV0H,GAAsC,EACjD1gB,KACA2gB,gBACAre,UACAse,SACAhI,cAEA,MAAMiI,EAAWve,EACb,UACA,WAAWuB,KAAK2W,KAAKmG,MAEzB,OACElJ,SAAKrN,UAAU,gCAAgCwO,QAASA,EAAOvO,SAAA,CAE7DoN,SAAKrN,UAAU,6BAA4BC,SAAA,CACzCJ,EAAA,OAAA,CAAMG,UAAU,8CAChBqN,EAAA,OAAA,CAAApN,SAAA,CAAO/E,EAAWqb,GAAc,mBAIjC3gB,EAAGzC,cACFka,YACErN,UAAU,mCACVwO,QAAU1F,IACRA,EAAE0H,kBACFhC,KAEFva,KAAK,SAAQgM,SAAA,CAEbJ,EAACmV,GAAgB,CAACZ,KAAM,qBAM5BvU,EAAA,SAAA,CACEG,UAAU,6BACVwO,QAAU1F,IACRA,EAAE0H,kBACEtY,GAASse,KAEfxG,UAAW9X,EACXjE,KAAK,SAAQgM,SAEZwW,QCjDHC,GAAyC,CAC7C,EAAG,kCACH,EAAG,oDACH,EAAG,0DACH,EAAG,sCAGQC,GAA4C,EAAGrkB,QAAOskB,cACjE,MAAMC,EAAYvkB,GAAOwkB,MAAQ,EAC3BC,EAAUL,GAAeG,IAAc,6BAE7C,OACExJ,EAAA,MAAA,CAAKrN,UAAU,qCACbH,EAACkV,GAAS,CAAC/U,UAAU,gCAAgCoU,KAAM,KAC3DvU,EAAA,MAAA,CAAKG,UAAU,mCAAkCC,SAAE8W,IAClDF,EAAY,GACXxJ,EAAA,MAAA,CAAKrN,UAAU,gCAA+BC,SAAA,CAAA,eAAc4W,KAE9DhX,EAAA,SAAA,CACEG,UAAU,iCACVwO,QAASoI,EACT3iB,KAAK,SAAQgM,SAAA,kBCvBR+W,GAAgC,EAAG1Q,aACzCA,EAGHzG,EAAA,MAAA,CAAKG,UAAU,4BAA2BC,SACxCJ,EAAA,MAAA,CAAKG,UAAU,wCAJE,KCOjBiX,GAA4B,CAChCxT,WAAW,EACXyT,UAAU,EACVC,SAAS,EACTC,aAAa,EACbC,WAAW,EACXnT,cAAc,EACdgJ,OAAO,EACPlJ,SAAS,EACTrM,aAAa,EACbmK,OAAQ,EACR3I,YAAa,EACb/F,SAAU,EACVqM,SAAU,EACVkF,aAAc,EACd6Q,eAAgB,KAChBljB,MAAO,MAGIglB,GAAY,CAACC,EAA4B,MACpD,MAAMjW,SACJA,GAAW,EAAKC,MAChBA,GAAQ,EAAKC,KACbA,GAAO,EACPM,OAAQE,EAAgB,EACxB2C,aAAc6S,EAAc,GAC1BD,EAEEjgB,EAAWe,EAAyB,MACpCgL,EAAehL,EAAuB,OACrCof,EAAOC,GAAY7f,EAAsB,IAC3Cof,GACHnV,OAAQE,EACRgC,QAASzC,EACToD,aAAc6S,IAIVG,EAAc5e,EAAa6e,IAC/BF,EAAUnO,IAAI,IAAWA,KAASqO,MACjC,IAGGxe,EAAOL,EAAY7D,UACvB,MAAMa,EAAQuB,EAAS0B,QACvB,GAAKjD,EAEL,UACQA,EAAMqD,OACZue,EAAY,CAAElU,WAAW,EAAMyT,UAAU,EAAOC,SAAS,GAC3D,CAAE,MAAO7kB,GAET,GACC,CAACqlB,IAGE7Q,EAAQ/N,EAAY,KACxB,MAAMhD,EAAQuB,EAAS0B,QAClBjD,IAELA,EAAM+Q,QACN6Q,EAAY,CAAElU,WAAW,EAAOyT,UAAU,MACzC,CAACS,IAGE3Q,EAAajO,EAAY,KACzB0e,EAAMhU,UACRqD,IAEA1N,KAED,CAACqe,EAAMhU,UAAWrK,EAAM0N,IAGrBI,EAAOnO,EAAa8C,IACxB,MAAM9F,EAAQuB,EAAS0B,QACvB,IAAKjD,EAAO,OAEZ,MAAM8hB,EAAcpe,KAAKC,IAAI,EAAGD,KAAK0C,IAAIN,EAAM9F,EAAM3C,UAAY,IACjE2C,EAAMoD,YAAc0e,EACpBF,EAAY,CAAExe,YAAa0e,KAC1B,CAACF,IAGExQ,EAAYpO,EAAa+I,IAC7B,MAAM/L,EAAQuB,EAAS0B,QACvB,IAAKjD,EAAO,OAEZ,MAAM+hB,EAAgBre,KAAKC,IAAI,EAAGD,KAAK0C,IAAI,EAAG2F,IAC9C/L,EAAM+L,OAASgW,EACf/hB,EAAMwL,MAA0B,IAAlBuW,EACdH,EAAY,CACV7V,OAAQgW,EACR9T,QAA2B,IAAlB8T,KAEV,CAACH,IAGEpQ,EAAaxO,EAAY,KAC7B,MAAMhD,EAAQuB,EAAS0B,QAClBjD,IAELA,EAAMwL,OAASxL,EAAMwL,MACrBoW,EAAY,CAAE3T,QAASjO,EAAMwL,UAC5B,CAACoW,IAGE1P,EAAkBlP,EAAamP,IACnC,MAAMnS,EAAQuB,EAAS0B,QAClBjD,IAELA,EAAM4O,aAAeuD,EACrByP,EAAY,CAAEhT,aAAcuD,MAC3B,CAACyP,IAGElQ,EAAkB1O,EAAY7D,UAClC,MAAM6iB,EAAY1U,EAAarK,QAC/B,GAAK+e,EAEL,UACQpa,EAAkBoa,GACxBJ,EAAY,CAAEzT,cAAc,GAC9B,CAAE,MAAO5R,GAET,GACC,CAACqlB,IAGEK,EAAmBjf,EAAY7D,UACnC,UACQ8I,IACN2Z,EAAY,CAAEzT,cAAc,GAC9B,CAAE,MAAO5R,GAET,GACC,CAACqlB,IAGEjQ,EAAmB3O,EAAY7D,UAC/BuiB,EAAMvT,mBACF8T,UAEAvQ,KAEP,CAACgQ,EAAMvT,aAAcuD,EAAiBuQ,IAGnCrQ,EAAW5O,EAAY7D,UAC3B,MAAMa,EAAQuB,EAAS0B,QACvB,GAAKjD,GAAUqH,IAEf,UACQrH,EAAM6R,0BACZ+P,EAAY,CAAEzK,OAAO,GACvB,CAAE,MAAO5a,GAET,GACC,CAACqlB,IAGEM,EAAYlf,EAAY7D,UAC5B,GAAKc,SAAS8R,wBAEd,UACQ9R,SAAS+R,uBACf4P,EAAY,CAAEzK,OAAO,GACvB,CAAE,MAAO5a,GAET,GACC,CAACqlB,IAGE3P,EAAYjP,EAAY7D,UACxBuiB,EAAMvK,YACF+K,UAEAtQ,KAEP,CAAC8P,EAAMvK,MAAOvF,EAAUsQ,IAqG3B,OAlGAhd,EAAU,KACR,MAAMlF,EAAQuB,EAAS0B,QACvB,IAAKjD,EAAO,OAGZA,EAAMwL,MAAQA,EACdxL,EAAM+L,OAASE,EACfjM,EAAM4O,aAAe6S,EACrBzhB,EAAMyL,KAAOA,EAEb,MAAM0W,EAAW,CACfC,eAAgB,KACdR,EAAY,CAAEvkB,SAAU2C,EAAM3C,YAEhCglB,WAAY,KACVT,EAAY,CACVxe,YAAapD,EAAMoD,YACnBsG,SAAUD,EAAezJ,MAG7BqD,KAAM,KACJue,EAAY,CAAElU,WAAW,EAAMyT,UAAU,EAAOC,SAAS,KAE3DrQ,MAAO,KACL6Q,EAAY,CAAElU,WAAW,EAAOyT,UAAU,KAE5CmB,MAAO,KACLV,EAAY,CAAElU,WAAW,EAAOyT,UAAU,EAAMC,SAAS,KAE3DmB,QAAS,KACPX,EAAY,CAAEP,aAAa,KAE7BmB,QAAS,KACPZ,EAAY,CAAEP,aAAa,KAE7BoB,QAAS,KACPb,EAAY,CAAEN,WAAW,KAE3BoB,OAAQ,KACNd,EAAY,CAAEN,WAAW,KAE3BqB,aAAc,KACZf,EAAY,CACV7V,OAAQ/L,EAAM+L,OACdkC,QAASjO,EAAMwL,SAGnBoX,WAAY,KACVhB,EAAY,CAAEhT,aAAc5O,EAAM4O,gBAEpCrS,MAAO,KACLqlB,EAAY,CAAErlB,MAAOyD,EAAMzD,SAE7BsmB,sBAAuB,KACrBjB,EAAY,CAAEzK,OAAO,KAEvB2L,sBAAuB,KACrBlB,EAAY,CAAEzK,OAAO,MAczB,OATA4L,OAAOC,QAAQb,GAAU1lB,QAAQ,EAAEiC,EAAOukB,MACxCjjB,EAAMmE,iBAAiBzF,EAAOukB,KAI5B1X,GACFlI,IAGK,KACL0f,OAAOC,QAAQb,GAAU1lB,QAAQ,EAAEiC,EAAOukB,MACxCjjB,EAAMkQ,oBAAoBxR,EAAOukB,OAGpC,CAAC1X,EAAUC,EAAOC,EAAMQ,EAAewV,EAAape,EAAMue,IAG7D1c,EAAU,KACR,MAAMqR,EAAyB,KAC7B,MAAMC,IAASjP,IACfqa,EAAY,CAAEzT,aAAcqI,KAQ9B,OALAvW,SAASkE,iBAAiB,mBAAoBoS,GAC9CtW,SAASkE,iBAAiB,yBAA0BoS,GACpDtW,SAASkE,iBAAiB,sBAAuBoS,GACjDtW,SAASkE,iBAAiB,qBAAsBoS,GAEzC,KACLtW,SAASiQ,oBAAoB,mBAAoBqG,GACjDtW,SAASiQ,oBAAoB,yBAA0BqG,GACvDtW,SAASiQ,oBAAoB,sBAAuBqG,GACpDtW,SAASiQ,oBAAoB,qBAAsBqG,KAEpD,CAACqL,IAEG,CACLF,QACAngB,WACA+L,eACAjK,OACA0N,QACAE,aACAE,OACAC,YACAI,aACAU,kBACAR,kBACAzJ,eAAgBga,EAChBtQ,mBACAC,WACAE,QAASoQ,EACTjQ,cCnTEiR,GAA0C,CAC9C7f,KAAM,QACNiO,KAAM,IACN1F,WAAY,IACZD,IAAK,IACLwX,YAAa,aACbC,aAAc,YACdC,SAAU,UACVC,WAAY,aAeDC,GAAc,EACzB1F,UACA2F,UAAU,CAAA,EACV9W,SACA+W,SACAC,eACAC,QACAhH,SACAiH,WACAtW,mBAEA,MAAMuW,EAAgB,IAAKX,MAAoBM,GAEzC7M,EAAgB3T,EACnBtE,IACC,IAAKmf,EAAS,OAGd,MAAMpC,EAAS/c,EAAM+c,OACrB,GAAuB,UAAnBA,EAAOqI,SAA0C,aAAnBrI,EAAOqI,SAA0BrI,EAAOsI,kBACxE,OAIF,MAAM/B,EAAY1U,EAAarK,QAC/B,IAAK+e,IAAcA,EAAUpL,SAAS3W,SAAS4W,eAC7C,OAKF,OAFYnY,EAAMmG,KAGhB,KAAKgf,EAAcxgB,KACnB,IAAK,IACH3E,EAAMsU,iBACNtG,IACA,MACF,KAAKmX,EAAcvS,KACnB,IAAK,IACH5S,EAAMsU,iBACNyQ,IACA,MACF,KAAKI,EAAcjY,WACnB,IAAK,IACHlN,EAAMsU,iBACN0Q,IACA,MACF,KAAKG,EAAclY,IACnB,IAAK,IACHjN,EAAMsU,iBACN2Q,IACA,MACF,KAAKE,EAAcV,YACjBzkB,EAAMsU,iBACN2J,EAAOje,EAAMslB,SAAW,GAAK,IAC7B,MACF,KAAKH,EAAcT,aACjB1kB,EAAMsU,iBACN2J,EAAOje,EAAMslB,UAAW,IAAM,IAC9B,MACF,KAAKH,EAAcR,SACjB3kB,EAAMsU,iBACN4Q,EAAS,IACT,MACF,KAAKC,EAAcP,WACjB5kB,EAAMsU,iBACN4Q,GAAS,IACT,MACF,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACL,IAAK,IACHllB,EAAMsU,iBAEN2J,GAAQsH,OAMd,CAACpG,EAASgG,EAAenX,EAAQ+W,EAAQC,EAAcC,EAAOhH,EAAQiH,EAAUtW,IAGlFpI,EAAU,KACR,GAAK2Y,EAIL,OAFA5d,SAASkE,iBAAiB,UAAWwS,GAE9B,KACL1W,SAASiQ,oBAAoB,UAAWyG,KAEzC,CAACkH,EAASlH"}