abcd / src /components /VideoPlayer.tsx
docs4you's picture
Upload 41 files
84121fd verified
import React, { useRef, useEffect, useState } from 'react'
import Hls from 'hls.js'
import { Channel } from '../types/channel'
import { channelService } from '../services/channelService'
import { Play, Pause, Volume2, VolumeX, Maximize, AlertCircle } from 'lucide-react'
interface VideoPlayerProps {
channel: Channel
}
export default function VideoPlayer({ channel }: VideoPlayerProps) {
const videoRef = useRef<HTMLVideoElement>(null)
const hlsRef = useRef<Hls | null>(null)
const [isPlaying, setIsPlaying] = useState(false)
const [isMuted, setIsMuted] = useState(false)
const [volume, setVolume] = useState(1)
const [error, setError] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
if (!videoRef.current || !channel) return
const video = videoRef.current
setError(null)
setIsLoading(true)
// Limpiar HLS anterior
if (hlsRef.current) {
hlsRef.current.destroy()
hlsRef.current = null
}
const streamUrl = channelService.getProxyUrl(channel.url)
if (Hls.isSupported()) {
const hls = new Hls({
enableWorker: true,
lowLatencyMode: true,
backBufferLength: 90
})
hls.loadSource(streamUrl)
hls.attachMedia(video)
hls.on(Hls.Events.MANIFEST_PARSED, () => {
setIsLoading(false)
video.play().catch(console.error)
})
hls.on(Hls.Events.ERROR, (event, data) => {
console.error('HLS Error:', data)
if (data.fatal) {
setError('Error al cargar el canal. Inténtalo de nuevo.')
setIsLoading(false)
}
})
hlsRef.current = hls
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// Safari nativo
video.src = streamUrl
video.addEventListener('loadedmetadata', () => {
setIsLoading(false)
video.play().catch(console.error)
})
video.addEventListener('error', () => {
setError('Error al cargar el canal. Inténtalo de nuevo.')
setIsLoading(false)
})
} else {
setError('Tu navegador no soporta la reproducción de este tipo de contenido.')
setIsLoading(false)
}
return () => {
if (hlsRef.current) {
hlsRef.current.destroy()
hlsRef.current = null
}
}
}, [channel])
useEffect(() => {
const video = videoRef.current
if (!video) return
const handlePlay = () => setIsPlaying(true)
const handlePause = () => setIsPlaying(false)
const handleVolumeChange = () => {
setVolume(video.volume)
setIsMuted(video.muted)
}
video.addEventListener('play', handlePlay)
video.addEventListener('pause', handlePause)
video.addEventListener('volumechange', handleVolumeChange)
return () => {
video.removeEventListener('play', handlePlay)
video.removeEventListener('pause', handlePause)
video.removeEventListener('volumechange', handleVolumeChange)
}
}, [])
const togglePlay = () => {
if (!videoRef.current) return
if (isPlaying) {
videoRef.current.pause()
} else {
videoRef.current.play()
}
}
const toggleMute = () => {
if (!videoRef.current) return
videoRef.current.muted = !videoRef.current.muted
}
const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!videoRef.current) return
const newVolume = parseFloat(e.target.value)
videoRef.current.volume = newVolume
setVolume(newVolume)
}
const toggleFullscreen = () => {
if (!videoRef.current) return
if (document.fullscreenElement) {
document.exitFullscreen()
} else {
videoRef.current.requestFullscreen()
}
}
if (error) {
return (
<div className="flex-1 flex items-center justify-center bg-black">
<div className="text-center text-white">
<AlertCircle className="h-12 w-12 mx-auto mb-4 text-red-500" />
<h3 className="text-lg font-semibold mb-2">Error de reproducción</h3>
<p className="text-gray-300 mb-4">{error}</p>
<button
onClick={() => window.location.reload()}
className="btn-primary"
>
Reintentar
</button>
</div>
</div>
)
}
return (
<div className="flex-1 relative bg-black group">
<video
ref={videoRef}
className="w-full h-full object-contain"
controls={false}
autoPlay
muted={false}
playsInline
/>
{isLoading && (
<div className="absolute inset-0 flex items-center justify-center bg-black bg-opacity-75">
<div className="text-center text-white">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-white mx-auto mb-2"></div>
<p>Cargando canal...</p>
</div>
</div>
)}
{/* Controles */}
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black to-transparent p-4 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<div className="flex items-center space-x-4">
<button
onClick={togglePlay}
className="p-2 hover:bg-white hover:bg-opacity-20 rounded-full transition-colors"
>
{isPlaying ? (
<Pause className="h-6 w-6 text-white" />
) : (
<Play className="h-6 w-6 text-white" />
)}
</button>
<div className="flex items-center space-x-2">
<button
onClick={toggleMute}
className="p-2 hover:bg-white hover:bg-opacity-20 rounded-full transition-colors"
>
{isMuted ? (
<VolumeX className="h-5 w-5 text-white" />
) : (
<Volume2 className="h-5 w-5 text-white" />
)}
</button>
<input
type="range"
min="0"
max="1"
step="0.1"
value={isMuted ? 0 : volume}
onChange={handleVolumeChange}
className="w-20 h-1 bg-gray-600 rounded-lg appearance-none cursor-pointer"
/>
</div>
<div className="flex-1" />
<div className="text-white text-sm font-medium">
{channel.name}
</div>
<button
onClick={toggleFullscreen}
className="p-2 hover:bg-white hover:bg-opacity-20 rounded-full transition-colors"
>
<Maximize className="h-5 w-5 text-white" />
</button>
</div>
</div>
</div>
)
}