UNPKG

unified-video-framework

Version:

Cross-platform video player framework supporting iOS, Android, Web, Smart TVs (Samsung/LG), Roku, and more

306 lines 13.2 kB
import React, { useEffect, useRef, useState, useCallback } from 'react'; import { WebPlayer } from "../WebPlayer.js"; import EPGOverlay from "./components/EPGOverlay.js"; export const WebPlayerViewWithEPG = (props) => { const { epg, epgConfig, showEPG = false, onToggleEPG, onEPGFavorite, onEPGRecord, onEPGSetReminder, onEPGCatchup, onEPGProgramSelect, onEPGChannelSelect, ...webPlayerProps } = props; const containerRef = useRef(null); const playerRef = useRef(null); const [epgVisible, setEPGVisible] = useState(showEPG); const [playerReady, setPlayerReady] = useState(false); const [dimensions, setDimensions] = useState({ width: typeof window !== 'undefined' ? window.innerWidth : 1920, height: typeof window !== 'undefined' ? window.innerHeight : 1080, }); useEffect(() => { if (typeof window === 'undefined') return; const responsiveEnabled = props.responsive?.enabled !== false; if (!responsiveEnabled) return; const handleResize = () => { setDimensions({ width: window.innerWidth, height: window.innerHeight, }); }; window.addEventListener('resize', handleResize); handleResize(); return () => window.removeEventListener('resize', handleResize); }, [props.responsive?.enabled]); const handleToggleEPG = useCallback((visible) => { setEPGVisible(visible); if (onToggleEPG) { onToggleEPG(visible); } }, [onToggleEPG]); useEffect(() => { const handleKeyPress = (e) => { if (e.key === 'g' && e.ctrlKey) { e.preventDefault(); handleToggleEPG(!epgVisible); } }; document.addEventListener('keydown', handleKeyPress); return () => document.removeEventListener('keydown', handleKeyPress); }, [epgVisible, handleToggleEPG]); useEffect(() => { setEPGVisible(showEPG); }, [showEPG]); const getPlayerDimensions = () => { const responsiveEnabled = props.responsive?.enabled !== false; if (!responsiveEnabled) return props.style || {}; const { width, height } = dimensions; let calculatedStyle = { width: '100vw', height: epgVisible ? '35vh' : '100vh', maxWidth: '100vw', maxHeight: epgVisible ? '35vh' : '100vh', boxSizing: 'border-box', position: 'fixed', top: 0, left: 0, zIndex: epgVisible ? 50 : 1000, backgroundColor: '#000000', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: 0, padding: 0, transition: 'height 0.3s ease, max-height 0.3s ease', ...props.style, }; return calculatedStyle; }; useEffect(() => { try { const params = new URLSearchParams(window.location.search); const popup = (params.get('popup') || '').toLowerCase() === '1'; const status = (params.get('rental') || '').toLowerCase(); const orderId = params.get('order_id') || ''; const sessionId = params.get('session_id') || ''; if (popup && (status === 'success' || status === 'cancel')) { try { window.opener?.postMessage({ type: 'uvfCheckout', status, orderId, sessionId }, '*'); } catch (_) { } try { window.close(); } catch (_) { } } } catch (_) { } }, []); useEffect(() => { let cancelled = false; async function boot() { if (!containerRef.current) return; const player = new WebPlayer(); playerRef.current = player; if (props.cast) { try { const existing = document.querySelector('script[data-cast-sdk="1"]'); if (!existing) { const s = document.createElement('script'); s.src = 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1'; s.async = true; s.setAttribute('data-cast-sdk', '1'); document.head.appendChild(s); } } catch (_) { } } let paywallCfg = props.paywall; if (!paywallCfg && props.paywallConfigUrl) { try { const resp = await fetch(props.paywallConfigUrl); if (resp.ok) paywallCfg = await resp.json(); } catch (_) { } } if (props.emailAuth?.enabled) { if (!paywallCfg) { paywallCfg = { enabled: true, apiBase: 'http://localhost:3000', userId: 'user-' + Math.random().toString(36).substr(2, 9), videoId: 'video-' + Math.random().toString(36).substr(2, 9), gateways: ['stripe'], }; } paywallCfg = { ...paywallCfg, emailAuth: { enabled: props.emailAuth.enabled, skipIfAuthenticated: props.emailAuth.skipIfAuthenticated ?? true, sessionStorage: { tokenKey: props.emailAuth.sessionStorage?.tokenKey || 'uvf_session_token', refreshTokenKey: props.emailAuth.sessionStorage?.refreshTokenKey || 'uvf_refresh_token', userIdKey: props.emailAuth.sessionStorage?.userIdKey || 'uvf_user_id', }, api: { requestOtp: props.emailAuth.apiEndpoints?.requestOtp || '/auth/request-otp', verifyOtp: props.emailAuth.apiEndpoints?.verifyOtp || '/auth/verify-otp', refreshToken: props.emailAuth.apiEndpoints?.refreshToken || '/auth/refresh-token', logout: props.emailAuth.apiEndpoints?.logout || '/auth/logout', }, ui: { title: props.emailAuth.ui?.title || 'Sign in to continue', description: props.emailAuth.ui?.description || 'Enter your email to receive a verification code', emailPlaceholder: props.emailAuth.ui?.emailPlaceholder || 'Enter your email', otpPlaceholder: props.emailAuth.ui?.otpPlaceholder || 'Enter 6-digit code', submitButtonText: props.emailAuth.ui?.submitButtonText || 'Send Code', resendButtonText: props.emailAuth.ui?.resendButtonText || 'Resend Code', resendCooldown: props.emailAuth.ui?.resendCooldown || 30, }, validation: { otpLength: props.emailAuth.validation?.otpLength || 6, otpTimeout: props.emailAuth.validation?.otpTimeout || 300, rateLimiting: { maxAttempts: props.emailAuth.validation?.rateLimiting?.maxAttempts || 5, windowMinutes: props.emailAuth.validation?.rateLimiting?.windowMinutes || 60, }, }, }, }; } const config = { autoPlay: props.autoPlay ?? false, muted: props.muted ?? false, enableAdaptiveBitrate: props.enableAdaptiveBitrate ?? true, debug: props.debug ?? false, freeDuration: props.freeDuration, paywall: paywallCfg }; try { await player.initialize(containerRef.current, config); try { if (props.playerTheme && player.setTheme) { player.setTheme(props.playerTheme); } } catch (_) { } const source = { url: props.url, type: props.type ?? 'auto', subtitles: props.subtitles, metadata: props.metadata, }; await player.load(source); if (!cancelled) { setPlayerReady(true); props.onReady?.(player); } } catch (err) { if (!cancelled) props.onError?.(err); } } void boot(); return () => { cancelled = true; if (playerRef.current) { playerRef.current.destroy().catch(() => { }); playerRef.current = null; } }; }, [ props.autoPlay, props.muted, props.enableAdaptiveBitrate, props.debug, props.url, props.type, JSON.stringify(props.subtitles), JSON.stringify(props.metadata), props.cast, props.freeDuration, JSON.stringify(props.responsive), JSON.stringify(props.paywall), JSON.stringify(props.emailAuth), props.paywallConfigUrl, ]); useEffect(() => { const p = playerRef.current; if (p && typeof p.setFreeDuration === 'function' && typeof props.freeDuration !== 'undefined') { try { p.setFreeDuration(props.freeDuration); } catch (_) { } } }, [props.freeDuration]); useEffect(() => { const p = playerRef.current; if (p && typeof p.setPaywallConfig === 'function' && props.paywall) { const paywall = props.paywall; if (paywall.enabled && (paywall.apiBase || paywall.userId || paywall.videoId)) { try { console.log('[WebPlayerViewWithEPG] Updating paywall config:', paywall); p.setPaywallConfig(paywall); } catch (err) { console.warn('[WebPlayerViewWithEPG] Failed to update paywall config:', err); } } } }, [JSON.stringify(props.paywall)]); useEffect(() => { const p = playerRef.current; try { if (p && typeof p.setTheme === 'function') { p.setTheme(props.playerTheme); } } catch (_) { } }, [JSON.stringify(props.playerTheme)]); const playerStyle = getPlayerDimensions(); const epgConfigWithHandlers = { ...epgConfig, onFavorite: onEPGFavorite, onRecord: onEPGRecord, onSetReminder: onEPGSetReminder, onCatchup: onEPGCatchup, onProgramSelect: onEPGProgramSelect, onChannelSelect: onEPGChannelSelect, }; return (React.createElement("div", { className: `uvf-player-with-epg-container ${props.className || ''}`, style: { position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: '#000', zIndex: 50, } }, React.createElement("div", { ref: containerRef, className: `uvf-responsive-container ${props.className || ''}`, style: playerStyle }), epg && (React.createElement(EPGOverlay, { data: epg, config: epgConfigWithHandlers, visible: epgVisible, onToggle: handleToggleEPG })), epg && playerReady && !epgVisible && (React.createElement("div", { style: { position: 'fixed', bottom: '20px', left: '50%', transform: 'translateX(-50%)', backgroundColor: 'rgba(0, 0, 0, 0.8)', color: '#fff', padding: '12px 20px', borderRadius: '8px', fontSize: '14px', zIndex: 100, animation: 'fadeInOut 4s ease-in-out', pointerEvents: 'none', } }, "Press ", React.createElement("strong", null, "Ctrl+G"), " or click the \uD83D\uDCFA button to show the Electronic Program Guide")), React.createElement("style", null, ` @keyframes fadeInOut { 0%, 100% { opacity: 0; } 10%, 90% { opacity: 1; } } `))); }; export default WebPlayerViewWithEPG; //# sourceMappingURL=WebPlayerViewWithEPG.js.map