446 lines
15 KiB
PHP
Executable File
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>
|