import { useEffect, useRef, useState } from "react"; import { type Video } from "@shared/schema"; import Hls from "hls.js"; import { X, Volume2, VolumeX, Play, Pause, Maximize, SkipForward } from "lucide-react"; import { Button } from "@/components/ui/button"; interface VASTPlayerProps { video: Video; onClose: () => void; vastTagUrl?: string; enableAds?: boolean; } // VAST Ad Network Configuration const AD_NETWORKS = { publift: { name: "Publift", vastUrl: "https://publiftvast.example.com/vast?zone=video&w=854&h=480", priority: 1 }, vdo: { name: "Vdo.ai", vastUrl: "https://vdo.ai/vast?publisher_id=123&zone=preroll", priority: 2 }, adplayer: { name: "AdPlayer.Pro", vastUrl: "https://adplayer.pro/vast?site_id=456&format=preroll", priority: 3 }, aniview: { name: "Aniview", vastUrl: "https://aniview.com/vast?pub=789&placement=video", priority: 4 } }; // VAST Ad States enum AdState { LOADING = "loading", PLAYING = "playing", PAUSED = "paused", COMPLETED = "completed", SKIPPED = "skipped", ERROR = "error" } export default function VASTPlayer({ video, onClose, vastTagUrl, enableAds = true }: VASTPlayerProps) { const videoRef = useRef(null); const adVideoRef = useRef(null); const hlsRef = useRef(null); const adContainerRef = useRef(null); // Video Player States const [isPlaying, setIsPlaying] = useState(false); const [isMuted, setIsMuted] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [volume, setVolume] = useState(1); // VAST Ad States const [adState, setAdState] = useState(AdState.LOADING); const [isAdPlaying, setIsAdPlaying] = useState(false); const [adDuration, setAdDuration] = useState(0); const [adCurrentTime, setAdCurrentTime] = useState(0); const [canSkipAd, setCanSkipAd] = useState(false); const [skipCountdown, setSkipCountdown] = useState(5); const [currentAdNetwork, setCurrentAdNetwork] = useState(null); // Initialize VAST Ad System useEffect(() => { if (enableAds) { initializeVASTSystem(); } else { initializeMainVideo(); } }, [video, enableAds]); const initializeVASTSystem = async () => { console.log("🎯 Initializing VAST Ad System for go4.video"); // Waterfall approach - try ad networks in priority order const networks = Object.entries(AD_NETWORKS).sort((a, b) => a[1].priority - b[1].priority); for (const [networkId, network] of networks) { try { console.log(`📡 Attempting to load ads from ${network.name}`); const success = await loadVASTAd(network.vastUrl, networkId); if (success) { setCurrentAdNetwork(networkId); console.log(`✅ Successfully loaded ads from ${network.name}`); break; } } catch (error) { console.warn(`⚠️ Failed to load ads from ${network.name}:`, error); continue; } } // Fallback to main video if no ads load if (!currentAdNetwork) { console.log("📺 No ads available, playing main video"); initializeMainVideo(); } }; const loadVASTAd = async (vastUrl: string, networkId: string): Promise => { return new Promise((resolve) => { const adVideo = adVideoRef.current; if (!adVideo) { resolve(false); return; } // Simulate VAST ad loading (in production, use real VAST parser) setTimeout(() => { // Mock ad video URL (replace with real VAST-parsed ad URL) const mockAdUrl = "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"; adVideo.src = mockAdUrl; adVideo.muted = false; adVideo.volume = volume; adVideo.addEventListener('loadedmetadata', () => { setAdDuration(adVideo.duration); setAdState(AdState.PLAYING); setIsAdPlaying(true); startSkipCountdown(); resolve(true); }, { once: true }); adVideo.addEventListener('error', () => { console.error(`❌ Ad failed to load from ${networkId}`); resolve(false); }, { once: true }); adVideo.load(); }, 1000); // Simulate network delay }); }; const startSkipCountdown = () => { const countdown = setInterval(() => { setSkipCountdown((prev) => { if (prev <= 1) { setCanSkipAd(true); clearInterval(countdown); return 0; } return prev - 1; }); }, 1000); }; const skipAd = () => { console.log("⏭️ User skipped ad"); setAdState(AdState.SKIPPED); setIsAdPlaying(false); initializeMainVideo(); // Track ad skip for analytics if (currentAdNetwork) { trackAdEvent('skip', currentAdNetwork); } }; const trackAdEvent = (event: string, network: string) => { console.log(`📊 Ad Event: ${event} on ${network}`); // In production, send to analytics service }; const initializeMainVideo = () => { console.log("🎬 Initializing main video playback"); const videoElement = videoRef.current; if (!videoElement) return; // Clean up previous HLS instance if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; } const videoUrl = video.videoUrl; if (videoUrl.includes('.m3u8')) { if (Hls.isSupported()) { const hls = new Hls({ debug: false, enableWorker: true, lowLatencyMode: false, startLevel: -1, capLevelToPlayerSize: true, maxLoadingDelay: 4, maxBufferLength: 30, maxBufferSize: 60 * 1000 * 1000, }); hls.loadSource(videoUrl); hls.attachMedia(videoElement); hls.on(Hls.Events.MANIFEST_PARSED, () => { console.log("📺 HLS manifest loaded for main video"); videoElement.play(); setIsPlaying(true); }); hlsRef.current = hls; } } }; // Ad video event handlers useEffect(() => { const adVideo = adVideoRef.current; if (!adVideo) return; const handleAdTimeUpdate = () => { setAdCurrentTime(adVideo.currentTime); }; const handleAdEnded = () => { console.log("✅ Ad completed successfully"); setAdState(AdState.COMPLETED); setIsAdPlaying(false); initializeMainVideo(); if (currentAdNetwork) { trackAdEvent('complete', currentAdNetwork); } }; adVideo.addEventListener('timeupdate', handleAdTimeUpdate); adVideo.addEventListener('ended', handleAdEnded); return () => { adVideo.removeEventListener('timeupdate', handleAdTimeUpdate); adVideo.removeEventListener('ended', handleAdEnded); }; }, [currentAdNetwork]); const toggleAdPlayPause = () => { const adVideo = adVideoRef.current; if (!adVideo) return; if (isAdPlaying) { adVideo.pause(); setIsAdPlaying(false); setAdState(AdState.PAUSED); } else { adVideo.play(); setIsAdPlaying(true); setAdState(AdState.PLAYING); } }; const toggleMute = () => { const activeVideo = isAdPlaying ? adVideoRef.current : videoRef.current; if (!activeVideo) return; const newMutedState = !isMuted; activeVideo.muted = newMutedState; setIsMuted(newMutedState); }; // Format time for display const formatTime = (seconds: number): string => { const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.floor(seconds % 60); return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; }; return (
{/* Close button */} {/* Video Container */}
{/* Main Video */}