Files
sistema_funcionando_lastwar/video_viewer.php

446 lines
15 KiB
PHP
Executable File

<?php
// Incluir manejador de errores
require_once __DIR__ . '/includes/error_handler.php';
// Configuración
$videos_dir = 'videos/';
$thumbnails_dir = 'videos/thumbnails/';
$video_extensions = ['mp4', 'webm', 'mkv', 'mov'];
// Función para generar miniatura con FFmpeg
function generateThumbnail($videoPath, $thumbnailPath, $time = '00:00:02') {
// Comando para extraer un fotograma del video
$command = sprintf(
'ffmpeg -i %s -ss %s -vframes 1 -q:v 2 -y %s 2>&1',
escapeshellarg($videoPath),
escapeshellarg($time),
escapeshellarg($thumbnailPath)
);
$output = [];
$returnVar = 0;
// Ejecutar el comando
exec($command, $output, $returnVar);
// Verificar si se generó correctamente
if ($returnVar === 0 && file_exists($thumbnailPath) && filesize($thumbnailPath) > 0) {
return true;
}
// Si falla, intentar con un tiempo diferente
if ($time !== '00:00:01') {
return generateThumbnail($videoPath, $thumbnailPath, '00:00:01');
}
return false;
}
// Obtener lista de videos
$videos = [];
$video_files = [];
// Escanear directorio de videos
foreach ($video_extensions as $ext) {
$video_files = array_merge($video_files, glob($videos_dir . '*.' . $ext));
}
// Procesar información de los videos
$videos = [];
if (!empty($video_files)) {
foreach ($video_files as $video_file) {
if (empty($video_file)) continue;
$filename = basename($video_file);
$title = pathinfo($filename, PATHINFO_FILENAME);
// Verificar si el archivo de video existe y es legible
if (!file_exists($video_file) || !is_readable($video_file)) {
continue;
}
// Generar miniatura o usar una por defecto
$thumbnail_path = $thumbnails_dir . $title . '.jpg';
$default_thumbnail = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNjAwIiBoZWlnaHQ9IjkwMCIgdmlld0JveD0iMCAwIDE2MDAgOTAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMjIyIi8+PHRleHQgeD0iNTAlIiB5PSI1JSIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjE0IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmaWxsPSIjZmZmIiBmb250LXdlaWdodD0iYm9sZCI+Tm8gaGF5IHZpZGVvcyBkaXNwb25pYmxlc8K3PC90ZXh0Pjwvc3ZnPg=';
// Intentar generar la miniatura si no existe
if (!file_exists($thumbnail_path) || !is_readable($thumbnail_path)) {
// Crear directorio de miniaturas si no existe
if (!is_dir($thumbnails_dir)) {
@mkdir($thumbnails_dir, 0777, true);
}
// Generar miniatura con FFmpeg
if (function_exists('shell_exec') && is_callable('shell_exec')) {
$ffmpeg_path = trim(shell_exec('which ffmpeg'));
if (!empty($ffmpeg_path)) {
generateThumbnail($video_file, $thumbnail_path);
}
}
}
// Usar miniatura generada o la predeterminada
$thumbnail = (file_exists($thumbnail_path) && is_readable($thumbnail_path))
? $thumbnail_path
: $default_thumbnail;
$videos[] = [
'title' => htmlspecialchars($title, ENT_QUOTES, 'UTF-8'),
'url' => $video_file, // Usamos la ruta relativa
'thumbnail' => $thumbnail,
'duration' => '0:00',
'views' => number_format(rand(1000, 1000000)),
'upload_date' => date('d/m/Y', filemtime($video_file)),
'description' => 'Descripción del video ' . htmlspecialchars($title, ENT_QUOTES, 'UTF-8')
];
}
}
// Determinar el video actual
$current_video = null;
$current_video_index = 0;
if (!empty($_GET['v'])) {
$requested_video = urldecode($_GET['v']);
foreach ($videos as $index => $video) {
if ($video['title'] === $requested_video) {
$current_video = $video;
$current_video_index = $index;
break;
}
}
}
// Si no se encontró el video solicitado, usar el primero
if (empty($current_video) && !empty($videos)) {
$current_video = $videos[0];
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reproductor de Videos</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Roboto', Arial, sans-serif;
}
body {
background-color: #f9f9f9;
color: #333;
line-height: 1.6;
}
.container {
display: flex;
max-width: 1400px;
margin: 0 auto;
padding: 20px;
gap: 20px;
}
.video-container {
flex: 2;
}
.video-wrapper {
position: relative;
padding-bottom: 56.25%; /* 16:9 Aspect Ratio */
background: #000;
margin-bottom: 20px;
border-radius: 8px;
overflow: hidden;
}
.video-player {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
.video-info {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.video-title {
font-size: 1.5rem;
margin-bottom: 10px;
}
.video-meta {
color: #666;
font-size: 0.9rem;
margin-bottom: 15px;
}
.video-description {
margin-top: 20px;
}
.sidebar {
flex: 1;
}
.video-list {
display: flex;
flex-direction: column;
gap: 15px;
}
.video-item {
display: flex;
gap: 10px;
text-decoration: none;
color: inherit;
background: #fff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
transition: transform 0.2s;
}
.video-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.video-thumbnail {
position: relative;
width: 160px;
min-width: 160px;
height: 90px;
background: #000;
}
.video-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
.video-duration {
position: absolute;
bottom: 5px;
right: 5px;
background: rgba(0,0,0,0.8);
color: #fff;
padding: 2px 5px;
border-radius: 3px;
font-size: 0.8rem;
}
.video-details {
padding: 10px;
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.video-item-title {
font-size: 0.9rem;
font-weight: 500;
margin-bottom: 5px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.video-item-meta {
font-size: 0.8rem;
color: #666;
}
.video-item {
position: relative;
}
.video-item.current-playing {
border-left: 3px solid #ff0000;
background-color: rgba(255, 0, 0, 0.05);
}
.now-playing {
position: absolute;
top: 10px;
left: 10px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 3px 8px;
border-radius: 4px;
font-size: 0.8rem;
display: flex;
align-items: center;
gap: 5px;
z-index: 1;
}
.now-playing i {
color: #ff0000;
}
.main-header {
background-color: #202020;
color: white;
padding: 20px 0;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
text-align: center;
}
.header-content {
max-width: 1400px;
margin: 0 auto;
padding: 0 20px;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.main-header h1 {
margin: 0;
font-size: 2rem;
font-weight: 500;
color: #fff;
text-align: center;
width: 100%;
}
@media (max-width: 992px) {
.container {
flex-direction: column;
}
}
</style>
</head>
<body>
<header class="main-header">
<div class="header-content">
<h1>Videos de REOD</h1>
</div>
</header>
<div class="container">
<?php if (!empty($videos)): ?>
<div class="video-container">
<!-- Video Principal -->
<div class="video-wrapper">
<?php if ($current_video): ?>
<video id="main-video" class="video-player" controls playsinline>
<source src="<?= htmlspecialchars($current_video['url']) ?>" type="video/mp4">
<?php
// Verificar si existe un archivo de subtítulos para este video
$subtitlePath = preg_replace('/\.[^.\s]{3,4}$/', '.vtt', $current_video['url']);
if (file_exists($subtitlePath)):
?>
<track kind="subtitles" src="<?= $subtitlePath ?>" srclang="es" label="Español" default>
<?php endif; ?>
Tu navegador no soporta el elemento de video.
</video>
<?php endif; ?>
</div>
<!-- Información del Video -->
<div class="video-info">
<?php if ($current_video): ?>
<h1 class="video-title"><?= htmlspecialchars($current_video['title']) ?></h1>
<div class="video-meta">
<span><?= $current_video['views'] ?> vistas • <?= $current_video['upload_date'] ?></span>
</div>
<div class="video-description">
<h3>Descripción</h3>
<p><?= nl2br(htmlspecialchars($current_video['description'])) ?></p>
</div>
<?php endif; ?>
</div>
</div>
<!-- Barra lateral con lista de reproducción -->
<aside class="sidebar">
<h2>Próximos videos</h2>
<div class="video-list">
<?php foreach ($videos as $index => $video): ?>
<a href="?v=<?= urlencode($video['title']) ?>" class="video-item <?= ($video['url'] === $current_video['url']) ? 'current-playing' : '' ?>">
<?php if ($video['url'] === $current_video['url']): ?>
<div class="now-playing">
<i class="fas fa-play"></i> Reproduciendo
</div>
<?php endif; ?>
<div class="video-thumbnail">
<img src="<?= $video['thumbnail'] ?>" alt="<?= htmlspecialchars($video['title']) ?>">
<span class="video-duration"><?= $video['duration'] ?></span>
</div>
<div class="video-details">
<h3 class="video-item-title"><?= htmlspecialchars($video['title']) ?></h3>
<div class="video-item-meta">
<div>Canal de Ejemplo</div>
<div><?= $video['views'] ?> vistas • <?= $video['upload_date'] ?></div>
</div>
</div>
</a>
<?php endforeach; ?>
</div>
</aside>
<?php else: ?>
<div class="no-videos">
<h2>No se encontraron videos</h2>
<p>Sube algunos videos a la carpeta /videos/ para comenzar.</p>
</div>
<?php endif; ?>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const videoPlayer = document.getElementById('main-video');
// Configurar el botón de búsqueda si existe
const searchInput = document.getElementById('search-input');
const searchButton = document.getElementById('search-button');
if (searchButton && searchInput) {
const performSearch = () => {
const query = searchInput.value.trim().toLowerCase();
if (!query) return;
// Buscar el primer video que coincida
const videos = Array.from(document.querySelectorAll('.video-item'));
const foundVideo = videos.find(video => {
const title = video.querySelector('.video-item-title')?.textContent.toLowerCase();
return title && title.includes(query);
});
if (foundVideo) {
// Navegar al video encontrado
const link = foundVideo.getAttribute('href');
if (link) {
window.location.href = link;
}
}
};
searchButton.addEventListener('click', performSearch);
searchInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') performSearch();
});
}
});
</script>
</body>
</html>