From 99c55d99dfacb16eccda3560ae6975cabe32aa00 Mon Sep 17 00:00:00 2001 From: sebastjanartic <45803536-sebastjanartic@users.noreply.replit.com> Date: Tue, 5 Aug 2025 06:23:00 +0000 Subject: [PATCH] Improve video playback using HLS.js for better streaming compatibility Refactors video player from Video.js/IMA to HLS.js for improved HLS streaming and removes videojs-contrib-ads. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 11420304-80a9-4ef2-adff-cbdaa418ffa8 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/11420304-80a9-4ef2-adff-cbdaa418ffa8/Mx2OL5A --- client/src/components/video-modal.tsx | 191 +++++++++++--------------- replit.md | 4 +- 2 files changed, 83 insertions(+), 112 deletions(-) diff --git a/client/src/components/video-modal.tsx b/client/src/components/video-modal.tsx index 2d63e31..a0831b7 100644 --- a/client/src/components/video-modal.tsx +++ b/client/src/components/video-modal.tsx @@ -3,20 +3,14 @@ import { X, Share2 } from "lucide-react"; import { type Video } from "@shared/schema"; import { Button } from "@/components/ui/button"; import { apiRequest } from "@/lib/queryClient"; -import videojs from "video.js"; -import "videojs-contrib-ads"; -import "videojs-ima"; -import "video.js/dist/video-js.css"; +import Hls from "hls.js"; import { - FacebookShareButton, - TwitterShareButton, - WhatsappShareButton, FacebookIcon, TwitterIcon, WhatsappIcon } from "react-share"; -// Video.js types extend from module import +// HLS.js types for video streaming interface VideoModalProps { video: Video | null; @@ -59,7 +53,7 @@ function formatDate(date: Date | string): string { export default function VideoModal({ video, isOpen, onClose }: VideoModalProps) { const videoRef = useRef(null); - const playerRef = useRef(null); + const hlsRef = useRef(null); const [showShareMenu, setShowShareMenu] = useState(false); const [videoThumbnail, setVideoThumbnail] = useState(null); @@ -83,126 +77,99 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps) }; }, [isOpen, onClose]); - // Initialize Video.js when video is available + // Initialize HLS when video is available useEffect(() => { if (isOpen && video && videoRef.current) { const videoElement = videoRef.current; - // Clean up previous Video.js instance - if (playerRef.current) { - try { - playerRef.current.dispose(); - } catch (e) { - console.log('Player cleanup error:', e); - } - playerRef.current = null; + // Clean up previous HLS instance + if (hlsRef.current) { + hlsRef.current.destroy(); + hlsRef.current = null; } const videoUrl = video.videoUrl; - console.log('Loading video with Video.js:', videoUrl); + console.log('Loading video with HLS.js:', videoUrl); - try { - // Initialize Video.js player without sources first - const player = videojs(videoElement, { - controls: true, - fluid: true, - responsive: true, - preload: 'metadata', - html5: { - hls: { - enableLowInitialPlaylist: true, - smoothQualityChange: true, - overrideNative: false + // Check if the video URL is HLS (.m3u8) + if (videoUrl.includes('.m3u8')) { + if (Hls.isSupported()) { + // Use HLS.js for browsers that don't support HLS natively + const hls = new Hls({ + debug: false, + enableWorker: false, + lowLatencyMode: true, + backBufferLength: 90 + }); + + hls.loadSource(videoUrl); + hls.attachMedia(videoElement); + + hls.on(Hls.Events.MANIFEST_PARSED, () => { + console.log('HLS manifest loaded successfully'); + }); + + hls.on(Hls.Events.ERROR, (event, data) => { + console.error('HLS error:', data); + if (data.fatal) { + switch (data.type) { + case Hls.ErrorTypes.NETWORK_ERROR: + console.log('Network error, trying to recover...'); + hls.startLoad(); + break; + case Hls.ErrorTypes.MEDIA_ERROR: + console.log('Media error, trying to recover...'); + hls.recoverMediaError(); + break; + default: + console.log('Fatal error, destroying HLS instance...'); + hls.destroy(); + break; + } } - } - }); - - // Initialize ads plugin immediately after player creation - if (typeof (player as any).ads === 'function') { - try { - (player as any).ads({ - debug: false, - prerollTimeout: 3000, - postrollTimeout: 3000, - timeout: 5000 - }); - console.log('Ads plugin initialized'); - } catch (error) { - console.log('Ads plugin initialization failed:', error); - } + }); + + hlsRef.current = hls; + } else if (videoElement.canPlayType('application/vnd.apple.mpegurl')) { + // For Safari that supports HLS natively + videoElement.src = videoUrl; + console.log('Using native HLS support'); + } else { + console.error('HLS is not supported in this browser'); } - - // Set source after ads plugin is ready - player.src({ - src: videoUrl, - type: videoUrl.includes('.m3u8') ? 'application/x-mpegURL' : 'video/mp4' - }); - - player.ready(() => { - console.log('Video.js player ready with source'); - }); - - // Listen for loadeddata event to capture thumbnail - player.on('loadeddata', () => { - console.log('Video data loaded, capturing thumbnail'); - setTimeout(() => { - captureVideoThumbnail(player); - }, 500); - }); - - player.on('canplay', () => { - console.log('Video can play, attempting thumbnail capture'); - captureVideoThumbnail(player); - }); - - player.on('error', (error: any) => { - console.error('Video.js player error:', error); - }); - - player.on('play', () => { - handleVideoPlay(); - }); - - // Simple ads event listeners - player.on('adstart', () => { - console.log('Ad playback started'); - }); - - player.on('adend', () => { - console.log('Ad playback ended'); - }); - - player.on('aderror', (error: any) => { - console.log('Ad error:', error); - }); - - playerRef.current = player; - } catch (error) { - console.error('Failed to initialize Video.js player:', error); + } else { + // For regular MP4 videos + videoElement.src = videoUrl; + console.log('Using native video support for MP4'); } + + // Capture thumbnail when video loads + videoElement.addEventListener('loadeddata', () => { + console.log('Video data loaded, capturing thumbnail'); + setTimeout(() => captureVideoThumbnail(), 1000); + }); + + videoElement.addEventListener('canplay', () => { + console.log('Video can play, capturing thumbnail'); + captureVideoThumbnail(); + }); } // Cleanup when modal closes return () => { - if (playerRef.current) { - try { - playerRef.current.dispose(); - } catch (e) { - console.log('Player cleanup error:', e); - } - playerRef.current = null; + if (hlsRef.current) { + hlsRef.current.destroy(); + hlsRef.current = null; } }; }, [isOpen, video]); // Function to capture video thumbnail - const captureVideoThumbnail = (player: any) => { + const captureVideoThumbnail = () => { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); - - // Get video element directly from player - const videoElement = player.el().querySelector('video'); + const videoElement = videoRef.current; if (videoElement && ctx && videoElement.videoWidth > 0) { canvas.width = videoElement.videoWidth; @@ -215,7 +182,7 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps) } else { console.log('Video element not ready for thumbnail capture'); // Retry after a delay - setTimeout(() => captureVideoThumbnail(player), 1000); + setTimeout(() => captureVideoThumbnail(), 1000); } } catch (error) { console.log('Failed to capture video thumbnail:', error); @@ -295,11 +262,15 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
{/* Video Controls and Share Menu */}
diff --git a/replit.md b/replit.md index 9948a3e..3998aee 100644 --- a/replit.md +++ b/replit.md @@ -6,9 +6,9 @@ VideoStream is a fully functional video streaming platform that integrates direc ## Recent Changes (August 2025) -- ✅ **Video.js + VAST Plugin Architecture**: Migrated from HLS.js to Video.js with IMA SDK for professional video streaming and advertising +- ✅ **HLS.js Video Streaming**: Reliable HLS.js implementation for professional video streaming with Bunny.net integration - ✅ **Advanced Video Controls**: Professional video player with fluid responsive design and adaptive streaming -- ✅ **VAST Advertising Support**: Integrated videojs-contrib-ads and videojs-ima for pre-roll, mid-roll, and post-roll video advertisements with Google DoubleClick integration +- ✅ **Video Controls**: Professional video player with full controls and responsive design - ✅ **Search Functionality**: Client-side search working with proper text visibility (white background, black text) - ✅ **Bunny.net Integration**: Complete integration with private video library using signed URLs for secure access - ✅ **Error Handling**: Robust error handling with Video.js fallback mechanisms