Enable video playback on more browsers and fix video loading issues

Integrates HLS.js for wider browser support and fixes video playback by using iframe.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 50814a1e-92e4-4968-856f-7bc7eedf5e8f
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/50814a1e-92e4-4968-856f-7bc7eedf5e8f/tKnLVTM
This commit is contained in:
sebastjanartic 2025-08-04 18:38:31 +00:00
parent b77e18915f
commit e087324c76
4 changed files with 113 additions and 17 deletions

View File

@ -3,6 +3,7 @@ import { X } from "lucide-react";
import { type Video } from "@shared/schema";
import { Button } from "@/components/ui/button";
import { apiRequest } from "@/lib/queryClient";
import Hls from "hls.js";
interface VideoModalProps {
video: Video | null;
@ -45,6 +46,7 @@ function formatDate(date: Date | string): string {
export default function VideoModal({ video, isOpen, onClose }: VideoModalProps) {
const videoRef = useRef<HTMLVideoElement>(null);
const hlsRef = useRef<Hls | null>(null);
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
@ -66,6 +68,82 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
};
}, [isOpen, onClose]);
// Initialize HLS when video is available
useEffect(() => {
if (isOpen && video && videoRef.current) {
const videoElement = videoRef.current;
// Clean up previous HLS instance
if (hlsRef.current) {
hlsRef.current.destroy();
hlsRef.current = null;
}
const videoUrl = video.videoUrl;
console.log('Loading video:', videoUrl);
// 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: true,
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;
}
}
});
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');
}
} else {
// For regular MP4 videos
videoElement.src = videoUrl;
console.log('Using native video support for MP4');
}
}
// Cleanup when modal closes
return () => {
if (hlsRef.current) {
hlsRef.current.destroy();
hlsRef.current = null;
}
};
}, [isOpen, video]);
const handleVideoPlay = async () => {
if (video) {
try {
@ -102,17 +180,28 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
</Button>
<div className="relative bg-black rounded-lg overflow-hidden">
<video
ref={videoRef}
className="w-full h-auto max-h-[80vh]"
controls
preload="metadata"
onPlay={handleVideoPlay}
data-testid="video-player"
src={video.videoUrl}
>
Your browser does not support the video tag.
</video>
{video.videoUrl.includes('iframe.mediadelivery.net') ? (
<iframe
src={video.videoUrl}
className="w-full h-auto max-h-[80vh] aspect-video"
allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"
allowFullScreen
onLoad={handleVideoPlay}
data-testid="video-iframe"
/>
) : (
<video
ref={videoRef}
className="w-full h-auto max-h-[80vh]"
controls
preload="metadata"
onPlay={handleVideoPlay}
data-testid="video-player"
crossOrigin="anonymous"
>
Your browser does not support the video tag.
</video>
)}
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-6">
<h3

7
package-lock.json generated
View File

@ -51,6 +51,7 @@
"express": "^4.21.2",
"express-session": "^1.18.1",
"framer-motion": "^11.13.1",
"hls.js": "^1.6.7",
"input-otp": "^1.4.2",
"lucide-react": "^0.453.0",
"memorystore": "^1.6.7",
@ -5550,6 +5551,12 @@
"node": ">= 0.4"
}
},
"node_modules/hls.js": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.7.tgz",
"integrity": "sha512-QW2fnwDGKGc9DwQUGLbmMOz8G48UZK7PVNJPcOUql1b8jubKx4/eMHNP5mGqr6tYlJNDG1g10Lx2U/qPzL6zwQ==",
"license": "Apache-2.0"
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",

View File

@ -53,6 +53,7 @@
"express": "^4.21.2",
"express-session": "^1.18.1",
"framer-motion": "^11.13.1",
"hls.js": "^1.6.7",
"input-otp": "^1.4.2",
"lucide-react": "^0.453.0",
"memorystore": "^1.6.7",

View File

@ -52,13 +52,12 @@ export class BunnyService {
}
private bunnyVideoToVideo(bunnyVideo: BunnyVideo): Video {
// Generate thumbnail URL from Bunny CDN
const thumbnailUrl = bunnyVideo.thumbnailFileName
? `https://${this.hostname}/${bunnyVideo.guid}/${bunnyVideo.thumbnailFileName}`
: `https://${this.hostname}/${bunnyVideo.guid}/thumbnail.jpg`;
// For private videos, use iframe embed or proxy approach
// Generate thumbnail URL from CDN (thumbnails are usually public)
const thumbnailUrl = `https://${this.hostname}/${bunnyVideo.guid}/thumbnail.jpg`;
// Generate video URL for streaming
const videoUrl = `https://${this.hostname}/${bunnyVideo.guid}/playlist.m3u8`;
// For private videos, we'll use an iframe embed URL which handles authentication
const videoUrl = `https://iframe.mediadelivery.net/embed/${this.libraryId}/${bunnyVideo.guid}`;
return {
id: bunnyVideo.guid,