Commit inicial con archivos existentes
This commit is contained in:
445
video_viewer.php
Executable file
445
video_viewer.php
Executable file
@@ -0,0 +1,445 @@
|
||||
<?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>
|
||||
Reference in New Issue
Block a user