almeira-web/src/components/media/LivePlayer.tsx

206 lines
10 KiB
TypeScript

'use client';
import { Tv, PlayCircle, RadioTower, WifiOff, PauseCircle, Volume2 } from 'lucide-react';
import { useState, useEffect, useRef } from 'react';
import { usePlayerStore } from '@/store/usePlayerStore';
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
interface LivePlayerProps {
url?: string;
type: 'tv' | 'radio';
title?: string;
}
export function LivePlayer({ url, type, title }: LivePlayerProps) {
const {
isAudioPlaying, toggleAudio, setAudioUrl, audioVolume, setAudioVolume,
isVideoActive, setVideoActive, setVideoUrl, setVideoType
} = usePlayerStore();
const [processedUrl, setProcessedUrl] = useState<string | null>(null);
const [isYouTube, setIsYouTube] = useState(false);
useEffect(() => {
if (!url) {
setProcessedUrl(null);
return;
}
// YT Detection & Conversion
if (url.includes('youtube.com/watch?v=') || url.includes('youtu.be/')) {
const videoId = url.includes('watch?v=')
? url.split('v=')[1]?.split('&')[0]
: url.split('/').pop();
if (videoId) {
const ytUrl = `https://www.youtube.com/embed/${videoId}?autoplay=1&rel=0`;
setProcessedUrl(ytUrl);
setIsYouTube(true);
if (type === 'tv') {
setVideoUrl(ytUrl);
setVideoType('youtube');
}
}
} else {
setProcessedUrl(url);
setIsYouTube(false);
if (type === 'tv') {
setVideoUrl(url);
setVideoType('m3u8');
} else {
setAudioUrl(url);
}
}
}, [url, type, setAudioUrl, setVideoUrl, setVideoType]);
// Handle inner Video Player Initialization
const innerVideoRef = useRef<HTMLDivElement>(null);
const innerPlayerRef = useRef<any>(null);
useEffect(() => {
if (type === 'tv' && !isYouTube && processedUrl && innerVideoRef.current) {
if (innerPlayerRef.current) {
innerPlayerRef.current.dispose();
innerPlayerRef.current = null;
}
const videoElement = document.createElement('video-js');
videoElement.classList.add('vjs-fill', 'vjs-almeira');
innerVideoRef.current.innerHTML = '';
innerVideoRef.current.appendChild(videoElement);
innerPlayerRef.current = videojs(videoElement, {
autoplay: 'muted',
controls: false,
fluid: true,
responsive: true,
sources: [{
src: processedUrl,
type: 'application/x-mpegURL'
}]
});
}
return () => {
if (innerPlayerRef.current) {
innerPlayerRef.current.dispose();
innerPlayerRef.current = null;
}
};
}, [type, isYouTube, processedUrl]);
if (!processedUrl) {
return (
<div className={`relative w-full rounded-3xl overflow-hidden border border-slate-800 shadow-2xl bg-slate-900 flex flex-col items-center justify-center p-12 transition-all ${type === 'tv' ? 'aspect-video' : 'h-64'}`}>
{/* Static Background Effect */}
<div className="absolute inset-0 opacity-[0.03] pointer-events-none bg-[url('https://media.giphy.com/media/oEI9uWUic6Rji/giphy.gif')] bg-cover mix-blend-overlay"></div>
<div className="relative z-10 flex flex-col items-center animate-in fade-in zoom-in duration-700">
<div className={`p-6 rounded-full mb-6 border-2 border-dashed animate-pulse ${type === 'tv' ? 'bg-cyan-500/10 border-cyan-500/30 text-cyan-400' : 'bg-rose-500/10 border-rose-500/30 text-rose-400'}`}>
<WifiOff size={64} />
</div>
<h2 className="text-4xl font-black font-outfit text-white tracking-tighter uppercase mb-2">SEÑAL PERDIDA</h2>
<p className="text-slate-500 font-bold uppercase tracking-widest text-[10px] md:text-xs text-center max-w-sm">
La transmisión de {type.toUpperCase()} no está disponible en este momento. Por favor, intenta más tarde.
</p>
</div>
{/* Decorative scanning line */}
<div className="absolute top-0 left-0 w-full h-1 bg-white/10 animate-[scan_3s_linear_infinite] pointer-events-none"></div>
</div>
);
}
return (
<div className={`relative w-full rounded-3xl overflow-hidden border border-slate-800 shadow-2xl bg-black transition-all ${type === 'tv' ? 'aspect-video' : 'h-48'}`}>
{type === 'tv' ? (
<div className="w-full h-full relative group">
{isYouTube ? (
<iframe
src={processedUrl}
className="w-full h-full border-0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
title={title || "Live TV Stream"}
/>
) : (
<div ref={innerVideoRef} className="w-full h-full pointer-events-none" />
)}
{/* Overlay to catch interaction and sync state */}
<div
className="absolute inset-0 bg-transparent cursor-pointer flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity bg-black/40"
onClick={() => setVideoActive(true)}
>
<div className="w-20 h-20 bg-pink-600 rounded-full flex items-center justify-center shadow-[0_0_30px_rgba(219,39,119,0.5)] animate-pulse">
<PlayCircle size={40} className="text-white translate-x-1" />
</div>
</div>
</div>
) : (
<div className="w-full h-full flex flex-col items-center justify-center relative p-8">
<div className="absolute inset-0 bg-gradient-to-r from-rose-600/10 to-orange-600/10 pointer-events-none" />
{/* Visualizer bars for Radio */}
<div className="absolute bottom-4 left-0 right-0 h-16 flex items-end justify-center gap-1 opacity-20 overflow-hidden px-8 pointer-events-none">
{Array.from({length: 40}).map((_, i) => (
<div
key={i}
className={`w-2 bg-rose-500 rounded-t-sm ${isAudioPlaying ? 'animate-pulse' : ''}`}
style={{
height: isAudioPlaying ? `${20 + Math.random() * 80}%` : '10%',
animationDuration: `${0.4 + Math.random()}s`
}}
></div>
))}
</div>
<div className="relative z-10 flex flex-col items-center w-full max-w-lg">
<div className="flex items-center gap-4 mb-6">
<div className="p-3 bg-rose-500 rounded-full text-white shadow-xl shadow-rose-500/20">
<RadioTower size={24} />
</div>
<div>
<h3 className="text-xl font-black text-white uppercase tracking-tight leading-none mb-1">En Vivo: {title || 'Radio Online'}</h3>
<p className="text-rose-400 text-[10px] font-black uppercase tracking-widest">
{isAudioPlaying ? 'Sintonizando Almeira Radio HD' : 'Radio Pulsa para Escuchar'}
</p>
</div>
</div>
{/* Store-driven Controls */}
<div className="flex items-center gap-6 w-full bg-white/5 backdrop-blur-md rounded-2xl p-4 border border-white/10">
<button
onClick={() => toggleAudio()}
className="w-14 h-14 bg-rose-500 hover:bg-rose-400 text-white rounded-xl flex items-center justify-center shadow-lg transition-all active:scale-95"
>
{isAudioPlaying ? <PauseCircle size={32} fill="white" /> : <PlayCircle size={32} fill="white" />}
</button>
<div className="flex-1 flex items-center gap-3">
<Volume2 size={18} className="text-rose-400" />
<input
type="range"
min="0"
max="1"
step="0.01"
value={audioVolume}
onChange={(e) => setAudioVolume(parseFloat(e.target.value))}
className="flex-1 h-1.5 bg-slate-800 rounded-full appearance-none cursor-pointer accent-rose-500"
/>
</div>
</div>
<a
href={processedUrl}
target="_blank"
rel="noopener noreferrer"
className="text-[9px] font-bold text-slate-500 hover:text-white transition-colors mt-4 uppercase tracking-widest border border-slate-800 px-3 py-1 rounded-full"
>
Enlace Directo de Audio &nearr;
</a>
</div>
</div>
)}
</div>
);
}