diff --git a/src/app/tv/page.tsx b/src/app/tv/page.tsx index 442e80a..80d353a 100644 --- a/src/app/tv/page.tsx +++ b/src/app/tv/page.tsx @@ -1,12 +1,15 @@ import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite'; import { createAdminClient } from '@/lib/appwrite.server'; import ProgrammingSchedule from '@/components/ProgrammingSchedule'; -import { Tv, PlayCircle, RadioTower } from 'lucide-react'; +import { Tv, RadioTower } from 'lucide-react'; +import { getEntityConfig } from '@/app/actions/entity'; +import { LivePlayer } from '@/components/media/LivePlayer'; export const revalidate = 60; // Revalida la TV cada minuto si hay cambios export default async function TVPage() { let matrixData = {}; + let entity = null; try { const client = await createAdminClient(); @@ -16,6 +19,7 @@ export default async function TVPage() { 'matrix_tv' ); matrixData = JSON.parse(doc.matrix_data); + entity = await getEntityConfig(); } catch(e) { console.error("No se pudo cargar parrilla de TV", e); } @@ -40,14 +44,7 @@ export default async function TVPage() { {/* Video Player Container */}
-
- {/* Placeholder para iframe de TV / HLS / YouTube */} -
- - Transmisión TV En Vivo -

Aquí se incrustará el código provisto por tu proveedor de streaming de video (YouTube, Twitch, VBM, etc).

-
-
+
{/* Parrilla */} diff --git a/src/components/dashboard/MediaGridSelector.tsx b/src/components/dashboard/MediaGridSelector.tsx index 2cb724d..0cb82ad 100644 --- a/src/components/dashboard/MediaGridSelector.tsx +++ b/src/components/dashboard/MediaGridSelector.tsx @@ -8,9 +8,10 @@ import { Query } from 'appwrite'; import { uploadMediaDocumentServer } from '@/app/actions/appwrite'; /** Utility to generate frontend Appwrite image previews */ -export function getAppwriteImageUrl(fileId: string, width = 400, height = 250) { +export function getAppwriteImageUrl(fileId: string, bucketId?: string, width = 400, height = 250) { if (!fileId) return ''; - return `${endpoint}/storage/buckets/${APPWRITE_ALMEIRA_CONFIG.mediaBucketId}/files/${fileId}/preview?project=${projectId}&width=${width}&height=${height}&quality=80`; + const finalBucketId = bucketId || APPWRITE_ALMEIRA_CONFIG.mediaBucketId; + return `${endpoint}/storage/buckets/${finalBucketId}/files/${fileId}/preview?project=${projectId}&width=${width}&height=${height}&quality=80`; } interface MediaGridSelectorProps { @@ -101,7 +102,7 @@ export const MediaGridSelector = ({ const doc = res.data[0]; setMediaItems(prev => [doc, ...prev]); - const url = getAppwriteImageUrl(doc.file_id, 1200, 800); + const url = getAppwriteImageUrl(doc.file_id, doc.bucket_id, 1200, 800); onChange(doc.$id, url); closeModal(); } catch (error) { @@ -179,12 +180,13 @@ export const MediaGridSelector = ({ ) : filteredItems.length > 0 ? (
{filteredItems.map((item) => { - const url = getAppwriteImageUrl(item.file_id, 300, 300); + const url = getAppwriteImageUrl(item.file_id, item.bucket_id, 300, 300); return (
{ - onChange(item.$id, getAppwriteImageUrl(item.file_id, 1200, 800)); + const selectedUrl = getAppwriteImageUrl(item.file_id, item.bucket_id, 1200, 800); + onChange(item.$id, selectedUrl); if (mode !== 'modal') setIsOpen(false); closeModal(); }} @@ -261,11 +263,12 @@ export const MediaGridSelector = ({ // Si value existe, buscamos su documento para sacar el file_id const selectedDoc = mediaItems.find(m => m.$id === value); // Si aún no está en la lista (cargando), mostramos loader genérico - if (!selectedDoc && mediaItems.length > 0) return null; // No está en la DB o se paginó, idealmente pasamos un initialUrl. - const fileIdToRender = selectedDoc ? selectedDoc.file_id : value; // Fallback al propio ID por si es legacy. + if (!selectedDoc && mediaItems.length > 0) return null; + const fileIdToRender = selectedDoc ? selectedDoc.file_id : value; + const bucketIdToRender = selectedDoc ? selectedDoc.bucket_id : undefined; return ( - Selected Preview + Selected Preview ); })()}
diff --git a/src/components/media/LivePlayer.tsx b/src/components/media/LivePlayer.tsx new file mode 100644 index 0000000..b5684fc --- /dev/null +++ b/src/components/media/LivePlayer.tsx @@ -0,0 +1,115 @@ +'use client'; + +import { Tv, PlayCircle, RadioTower, WifiOff } from 'lucide-react'; +import { useState, useEffect } from 'react'; + +interface LivePlayerProps { + url?: string; + type: 'tv' | 'radio'; + title?: string; +} + +export function LivePlayer({ url, type, title }: LivePlayerProps) { + const [processedUrl, setProcessedUrl] = useState(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) { + setProcessedUrl(`https://www.youtube.com/embed/${videoId}?autoplay=1&rel=0`); + setIsYouTube(true); + } + } else { + setProcessedUrl(url); + setIsYouTube(false); + } + }, [url]); + + if (!processedUrl) { + return ( +
+ {/* Static Background Effect */} +
+ +
+
+ +
+

SEÑAL PERDIDA

+

+ La transmisión de {type.toUpperCase()} no está disponible en este momento. Por favor, intenta más tarde. +

+
+ + {/* Decorative scanning line */} +
+
+ ); + } + + return ( +
+ {type === 'tv' ? ( +