Update live stream player to use HLS.js for improved playback
Replaced Video.js with HLS.js for live streaming on the LivePage, improving compatibility and playback efficiency. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 45a1dcfc-f8a2-475a-a6b9-96fbb841dc27 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/60d372ff-2c10-46c7-b01b-10c3435136b0/45a1dcfc-f8a2-475a-a6b9-96fbb841dc27/VVQLpmQ
This commit is contained in:
parent
484a299dd5
commit
c5342f8164
@ -4,36 +4,21 @@ import { Link } from 'wouter';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import AdSenseAd from '@/components/adsense-ad';
|
import AdSenseAd from '@/components/adsense-ad';
|
||||||
|
|
||||||
interface VideoJSPlayer {
|
|
||||||
ready: (callback: () => void) => void;
|
|
||||||
src: (source?: any) => any;
|
|
||||||
dispose: () => void;
|
|
||||||
play: () => Promise<void>;
|
|
||||||
pause: () => void;
|
|
||||||
currentTime: (time?: number) => number;
|
|
||||||
duration: () => number;
|
|
||||||
volume: (vol?: number) => number;
|
|
||||||
muted: (mute?: boolean) => boolean;
|
|
||||||
requestFullscreen: () => void;
|
|
||||||
on: (event: string, handler: any) => void;
|
|
||||||
off: (event: string, handler?: any) => void;
|
|
||||||
tech: (deep?: boolean) => any;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
videojs: any;
|
Hls: any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LivePage() {
|
export default function LivePage() {
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
const playerRef = useRef<VideoJSPlayer | null>(null);
|
const hlsRef = useRef<any>(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isPlaying, setIsPlaying] = useState(false);
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
const [volume, setVolume] = useState(1);
|
const [volume, setVolume] = useState(1);
|
||||||
const [isMuted, setIsMuted] = useState(false);
|
const [isMuted, setIsMuted] = useState(false);
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
// HLS stream URL
|
// HLS stream URL
|
||||||
const streamUrl = 'https://cdne.folxplay.tv/fxt/streams/ch-4/master.m3u8';
|
const streamUrl = 'https://cdne.folxplay.tv/fxt/streams/ch-4/master.m3u8';
|
||||||
@ -63,97 +48,103 @@ export default function LivePage() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let player: VideoJSPlayer | null = null;
|
|
||||||
|
|
||||||
const initializePlayer = async () => {
|
const initializePlayer = async () => {
|
||||||
if (!videoRef.current) return;
|
if (!videoRef.current) return;
|
||||||
|
|
||||||
// Load Video.js dynamically
|
|
||||||
if (!window.videojs) {
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.src = 'https://vjs.zencdn.net/8.8.0/video.min.js';
|
|
||||||
script.async = true;
|
|
||||||
document.head.appendChild(script);
|
|
||||||
|
|
||||||
const link = document.createElement('link');
|
|
||||||
link.href = 'https://vjs.zencdn.net/8.8.0/video-js.css';
|
|
||||||
link.rel = 'stylesheet';
|
|
||||||
document.head.appendChild(link);
|
|
||||||
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
script.onload = resolve;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
player = window.videojs(videoRef.current, {
|
// Load HLS.js
|
||||||
controls: true,
|
if (!window.Hls) {
|
||||||
fluid: true,
|
const script = document.createElement('script');
|
||||||
responsive: true,
|
script.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest';
|
||||||
aspectRatio: '16:9',
|
script.async = true;
|
||||||
playbackRates: [0.5, 1, 1.25, 1.5, 2],
|
document.head.appendChild(script);
|
||||||
html5: {
|
|
||||||
hls: {
|
|
||||||
enableLowInitialPlaylist: true,
|
|
||||||
smoothQualityChange: true,
|
|
||||||
overrideNative: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
techOrder: ['html5'],
|
|
||||||
sources: [{
|
|
||||||
src: streamUrl,
|
|
||||||
type: 'application/x-mpegURL'
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
player.ready(() => {
|
await new Promise((resolve, reject) => {
|
||||||
console.log('🔴 Live stream player ready');
|
script.onload = resolve;
|
||||||
setIsLoading(false);
|
script.onerror = reject;
|
||||||
|
|
||||||
// Auto-play live stream
|
|
||||||
if (player) {
|
|
||||||
player.play().catch((error: any) => {
|
|
||||||
console.log('Live stream autoplay prevented:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Event listeners
|
|
||||||
if (player) {
|
|
||||||
player.on('play', () => {
|
|
||||||
console.log('🔴 Live stream started');
|
|
||||||
setIsPlaying(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
player.on('pause', () => {
|
|
||||||
console.log('⏸️ Live stream paused');
|
|
||||||
setIsPlaying(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
player.on('volumechange', () => {
|
|
||||||
if (player) {
|
|
||||||
const vol = player.volume();
|
|
||||||
const muted = player.muted();
|
|
||||||
setVolume(vol);
|
|
||||||
setIsMuted(muted);
|
|
||||||
console.log('🔊 Live stream volume change:', { volume: vol, muted });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
player.on('fullscreenchange', () => {
|
|
||||||
setIsFullscreen(document.fullscreenElement !== null);
|
|
||||||
});
|
|
||||||
|
|
||||||
player.on('error', (e: any) => {
|
|
||||||
console.error('🚨 Live stream error:', e);
|
|
||||||
setIsLoading(false);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
playerRef.current = player;
|
const video = videoRef.current;
|
||||||
|
|
||||||
|
if (window.Hls && window.Hls.isSupported()) {
|
||||||
|
console.log('🔴 Initializing HLS.js for live stream');
|
||||||
|
const hls = new window.Hls({
|
||||||
|
enableWorker: false,
|
||||||
|
lowLatencyMode: true,
|
||||||
|
backBufferLength: 90
|
||||||
|
});
|
||||||
|
|
||||||
|
hls.loadSource(streamUrl);
|
||||||
|
hls.attachMedia(video);
|
||||||
|
|
||||||
|
hls.on(window.Hls.Events.MEDIA_ATTACHED, () => {
|
||||||
|
console.log('🔴 HLS media attached');
|
||||||
|
});
|
||||||
|
|
||||||
|
hls.on(window.Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
console.log('🔴 HLS manifest parsed, starting playback');
|
||||||
|
setIsLoading(false);
|
||||||
|
// Try auto-play
|
||||||
|
video.play().catch((e) => {
|
||||||
|
console.log('Auto-play blocked:', e);
|
||||||
|
setError('Kliknite play za zagon stream-a');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
hls.on(window.Hls.Events.ERROR, (event: any, data: any) => {
|
||||||
|
console.error('🚨 HLS Error:', event, data);
|
||||||
|
if (data.fatal) {
|
||||||
|
switch (data.type) {
|
||||||
|
case window.Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
setError('Napaka pri povezavi s streamom');
|
||||||
|
hls.startLoad();
|
||||||
|
break;
|
||||||
|
case window.Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
setError('Napaka pri predvajanju videa');
|
||||||
|
hls.recoverMediaError();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setError('Neznana napaka pri streamingu');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
hlsRef.current = hls;
|
||||||
|
|
||||||
|
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||||
|
// Native HLS support (Safari)
|
||||||
|
console.log('🔴 Using native HLS support');
|
||||||
|
video.src = streamUrl;
|
||||||
|
video.addEventListener('loadedmetadata', () => {
|
||||||
|
console.log('🔴 Native HLS loaded');
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setError('HLS not supported in this browser');
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Video event listeners
|
||||||
|
video.addEventListener('play', () => {
|
||||||
|
console.log('🔴 Live stream playing');
|
||||||
|
setIsPlaying(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
video.addEventListener('pause', () => {
|
||||||
|
console.log('⏸️ Live stream paused');
|
||||||
|
setIsPlaying(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
video.addEventListener('volumechange', () => {
|
||||||
|
setVolume(video.volume);
|
||||||
|
setIsMuted(video.muted);
|
||||||
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize live stream player:', error);
|
console.error('Failed to initialize live stream:', error);
|
||||||
|
setError('Napaka pri nalaganju stream-a');
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -161,31 +152,36 @@ export default function LivePage() {
|
|||||||
initializePlayer();
|
initializePlayer();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (player) {
|
if (hlsRef.current) {
|
||||||
player.dispose();
|
hlsRef.current.destroy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const togglePlayPause = () => {
|
const togglePlayPause = () => {
|
||||||
if (playerRef.current) {
|
if (videoRef.current) {
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
playerRef.current.pause();
|
videoRef.current.pause();
|
||||||
} else {
|
} else {
|
||||||
playerRef.current.play();
|
videoRef.current.play().catch((e) => {
|
||||||
|
console.log('Play failed:', e);
|
||||||
|
setError('Napaka pri predvajanju');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleMute = () => {
|
const toggleMute = () => {
|
||||||
if (playerRef.current) {
|
if (videoRef.current) {
|
||||||
playerRef.current.muted(!isMuted);
|
videoRef.current.muted = !isMuted;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleFullscreen = () => {
|
const toggleFullscreen = () => {
|
||||||
if (playerRef.current) {
|
if (videoRef.current) {
|
||||||
playerRef.current.requestFullscreen();
|
if (videoRef.current.requestFullscreen) {
|
||||||
|
videoRef.current.requestFullscreen();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -232,11 +228,33 @@ export default function LivePage() {
|
|||||||
<div className="lg:col-span-3">
|
<div className="lg:col-span-3">
|
||||||
<div className="bg-black rounded-lg overflow-hidden shadow-2xl">
|
<div className="bg-black rounded-lg overflow-hidden shadow-2xl">
|
||||||
<div className="relative aspect-video">
|
<div className="relative aspect-video">
|
||||||
|
{error && (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center bg-black/80 text-red-400 z-20">
|
||||||
|
<div className="text-center">
|
||||||
|
<Radio className="w-12 h-12 mx-auto mb-4 opacity-50" />
|
||||||
|
<p className="text-lg font-semibold mb-2">Stream Error</p>
|
||||||
|
<p className="text-sm opacity-75">{error}</p>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setError(null);
|
||||||
|
setIsLoading(true);
|
||||||
|
window.location.reload();
|
||||||
|
}}
|
||||||
|
className="mt-4 bg-red-600 hover:bg-red-700"
|
||||||
|
data-testid="button-retry"
|
||||||
|
>
|
||||||
|
Poskusite znova
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<video
|
<video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
className="video-js vjs-default-skin w-full h-full"
|
className="w-full h-full bg-black"
|
||||||
playsInline
|
playsInline
|
||||||
data-setup="{}"
|
controls
|
||||||
|
muted={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Live Overlay */}
|
{/* Live Overlay */}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user