aethex.live/components/HLSPlayer.tsx
copilot-swe-agent[bot] c0431cd8e7 feat: implement Next.js app with HLS video player and AeThex branding
Co-authored-by: MrPiglr <31398225+MrPiglr@users.noreply.github.com>
2026-02-07 02:28:40 +00:00

131 lines
3.6 KiB
TypeScript

'use client';
import { useEffect, useRef, useState } from 'react';
import Hls from 'hls.js';
interface HLSPlayerProps {
src: string;
autoPlay?: boolean;
loop?: boolean;
muted?: boolean;
}
export default function HLSPlayer({
src,
autoPlay = true,
loop = true,
muted = true,
}: HLSPlayerProps) {
const videoRef = useRef<HTMLVideoElement>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const hlsRef = useRef<Hls | null>(null);
useEffect(() => {
const video = videoRef.current;
if (!video) return;
let mounted = true;
const initializePlayer = () => {
if (Hls.isSupported()) {
const hls = new Hls({
enableWorker: true,
lowLatencyMode: true,
});
hlsRef.current = hls;
hls.loadSource(src);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
if (mounted) {
setIsLoading(false);
if (autoPlay) {
video.play().catch((err) => {
console.error('Autoplay failed:', err);
});
}
}
});
hls.on(Hls.Events.ERROR, (event, data) => {
console.error('HLS error:', data);
if (data.fatal && mounted) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
setError('Network error - attempting to recover');
hls.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
setError('Media error - attempting to recover');
hls.recoverMediaError();
break;
default:
setError('Fatal error - cannot recover');
hls.destroy();
break;
}
}
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// For Safari native HLS support
video.src = src;
const handleLoadedMetadata = () => {
if (mounted) {
setIsLoading(false);
if (autoPlay) {
video.play().catch((err) => {
console.error('Autoplay failed:', err);
});
}
}
};
video.addEventListener('loadedmetadata', handleLoadedMetadata);
return () => {
video.removeEventListener('loadedmetadata', handleLoadedMetadata);
};
} else if (mounted) {
setError('HLS is not supported in this browser');
setIsLoading(false);
}
};
initializePlayer();
return () => {
mounted = false;
if (hlsRef.current) {
hlsRef.current.destroy();
hlsRef.current = null;
}
};
}, [src, autoPlay]);
return (
<div className="relative w-full h-full bg-black">
<video
ref={videoRef}
className="w-full h-full object-contain"
loop={loop}
muted={muted}
playsInline
controls
/>
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center bg-black/80">
<div className="flex flex-col items-center gap-3">
<div className="w-12 h-12 border-4 border-cyan-500 border-t-transparent rounded-full animate-spin"></div>
<p className="text-cyan-400 font-mono text-sm">Loading stream...</p>
</div>
</div>
)}
{error && (
<div className="absolute top-4 left-1/2 -translate-x-1/2 bg-red-500/90 text-white px-4 py-2 rounded-lg font-mono text-sm">
{error}
</div>
)}
</div>
);
}