Pantalla TV del turnero: playlist de videos e imágenes en vez de un solo video
Nueva tabla turnero_tv_media (tipo, url, orden, duracion_segundos) para soportar múltiples videos/imágenes reproducidos en secuencia y en bucle. El video que ya estaba cargado se migró como primer ítem de la playlist. - save_tv_media.php / delete_tv_media.php / reorder_tv_media.php reemplazan a save_tv_video.php (eliminado). - configuracion.php (tab TV): subida múltiple, lista reordenable por arrastre, duración editable por imagen. - display_global.php: el <video> único se reemplaza por un reproductor JS que recorre la playlist — video hasta 'ended', imagen por su duración configurada, y vuelve a empezar al terminar. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
2ba13add34
commit
6254e45e8f
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* modules/turnero/api/delete_tv_media.php
|
||||
* Elimina un ítem de la playlist de la Pantalla TV (archivo + fila).
|
||||
* POST JSON: { id: int }
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../../config/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (!isUserLoggedIn()) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['ok' => false, 'error' => 'No autenticado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = Database::getInstance()->getConnection();
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$id = (int)($body['id'] ?? 0);
|
||||
|
||||
if (!$id) {
|
||||
echo json_encode(['ok' => false, 'error' => 'id requerido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT url FROM turnero_tv_media WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$row) {
|
||||
echo json_encode(['ok' => false, 'error' => 'No encontrado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$uploadDir = dirname(__DIR__, 3) . '/uploads/turnero/tv_media/';
|
||||
$filename = basename(parse_url($row['url'], PHP_URL_PATH));
|
||||
$filepath = $uploadDir . $filename;
|
||||
if (is_file($filepath)) {
|
||||
@unlink($filepath);
|
||||
}
|
||||
|
||||
$pdo->prepare("DELETE FROM turnero_tv_media WHERE id = ?")->execute([$id]);
|
||||
|
||||
echo json_encode(['ok' => true]);
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* modules/turnero/api/reorder_tv_media.php
|
||||
* Guarda el orden y la duración (para imágenes) de la playlist de la Pantalla TV.
|
||||
* POST JSON: { items: [{ id, orden, duracion_segundos }, ...] }
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../../config/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (!isUserLoggedIn()) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['ok' => false, 'error' => 'No autenticado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = Database::getInstance()->getConnection();
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
$items = is_array($body['items'] ?? null) ? $body['items'] : [];
|
||||
|
||||
if (!$items) {
|
||||
echo json_encode(['ok' => false, 'error' => 'items requerido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"UPDATE turnero_tv_media SET orden = ?, duracion_segundos = ? WHERE id = ?"
|
||||
);
|
||||
foreach ($items as $i => $it) {
|
||||
$id = (int)($it['id'] ?? 0);
|
||||
if (!$id) continue;
|
||||
$dur = max(1, (int)($it['duracion_segundos'] ?? 8));
|
||||
$stmt->execute([$i, $dur, $id]);
|
||||
}
|
||||
|
||||
echo json_encode(['ok' => true]);
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* modules/turnero/api/save_tv_media.php
|
||||
* Sube un video o imagen a la playlist de la Pantalla TV del turnero.
|
||||
* POST multipart: campo "media" → sube y agrega al final de la lista
|
||||
* Guarda el archivo en /uploads/turnero/tv_media/ y una fila en turnero_tv_media.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../../config/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
if (!isUserLoggedIn()) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['ok' => false, 'error' => 'No autenticado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = Database::getInstance()->getConnection();
|
||||
$uploadDir = dirname(__DIR__, 3) . '/uploads/turnero/tv_media/';
|
||||
$publicBase = rtrim(BASE_URL, '/') . '/uploads/turnero/tv_media/';
|
||||
|
||||
if (!is_dir($uploadDir) && !mkdir($uploadDir, 0755, true)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'No se pudo crear el directorio de uploads']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($_FILES['media'])) {
|
||||
echo json_encode(['ok' => false, 'error' => 'No se recibió ningún archivo']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$file = $_FILES['media'];
|
||||
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
$errores = [
|
||||
UPLOAD_ERR_INI_SIZE => 'El archivo supera upload_max_filesize en php.ini',
|
||||
UPLOAD_ERR_FORM_SIZE => 'El archivo supera MAX_FILE_SIZE del formulario',
|
||||
UPLOAD_ERR_PARTIAL => 'El archivo se subió parcialmente',
|
||||
UPLOAD_ERR_NO_FILE => 'No se seleccionó ningún archivo',
|
||||
UPLOAD_ERR_NO_TMP_DIR => 'Falta la carpeta temporal',
|
||||
UPLOAD_ERR_CANT_WRITE => 'Error al escribir el archivo',
|
||||
];
|
||||
echo json_encode(['ok' => false, 'error' => $errores[$file['error']] ?? 'Error de subida desconocido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$maxBytes = 500 * 1024 * 1024;
|
||||
if ($file['size'] > $maxBytes) {
|
||||
echo json_encode(['ok' => false, 'error' => 'El archivo supera el límite de 500 MB']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$mimeMap = [
|
||||
'video/mp4' => ['video', 'mp4'],
|
||||
'video/webm' => ['video', 'webm'],
|
||||
'video/ogg' => ['video', 'ogv'],
|
||||
'image/jpeg' => ['imagen', 'jpg'],
|
||||
'image/png' => ['imagen', 'png'],
|
||||
'image/webp' => ['imagen', 'webp'],
|
||||
];
|
||||
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mime = $finfo->file($file['tmp_name']);
|
||||
if (!isset($mimeMap[$mime])) {
|
||||
echo json_encode(['ok' => false, 'error' => 'Formato no admitido. Usa MP4, WebM, JPG, PNG o WEBP']);
|
||||
exit;
|
||||
}
|
||||
|
||||
[$tipo, $ext] = $mimeMap[$mime];
|
||||
|
||||
$destFilename = uniqid('tv_', true) . '.' . $ext;
|
||||
$destPath = $uploadDir . $destFilename;
|
||||
|
||||
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'No se pudo guardar el archivo']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$publicUrl = $publicBase . $destFilename;
|
||||
|
||||
$maxOrden = (int)($pdo->query("SELECT COALESCE(MAX(orden),-1) FROM turnero_tv_media")->fetchColumn());
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO turnero_tv_media (tipo, url, orden, duracion_segundos, activo)
|
||||
VALUES (?, ?, ?, 8, 1)"
|
||||
);
|
||||
$stmt->execute([$tipo, $publicUrl, $maxOrden + 1]);
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'item' => [
|
||||
'id' => (int)$pdo->lastInsertId(),
|
||||
'tipo' => $tipo,
|
||||
'url' => $publicUrl,
|
||||
'orden' => $maxOrden + 1,
|
||||
'duracion_segundos' => 8,
|
||||
],
|
||||
]);
|
||||
@@ -1,127 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* modules/turnero/api/save_tv_video.php
|
||||
* Sube o elimina el video de fondo de la Pantalla TV del turnero.
|
||||
*
|
||||
* POST multipart: campo "video" → sube y guarda
|
||||
* POST JSON: { "accion": "borrar" } → elimina el video actual
|
||||
*
|
||||
* Guarda el archivo en /uploads/turnero/tv_video.{ext}
|
||||
* y registra la URL en lab_config (clave: turnero_tv_video).
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../../config/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// Solo administradores autenticados
|
||||
if (!isUserLoggedIn()) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['ok' => false, 'error' => 'No autenticado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = Database::getInstance()->getConnection();
|
||||
$uploadDir = dirname(__DIR__, 3) . '/uploads/turnero/';
|
||||
$publicBase = rtrim(BASE_URL, '/') . '/uploads/turnero/';
|
||||
|
||||
// ── Crear carpeta si no existe ────────────────────────────────
|
||||
if (!is_dir($uploadDir)) {
|
||||
if (!mkdir($uploadDir, 0755, true)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'No se pudo crear el directorio de uploads']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Acción: borrar ────────────────────────────────────────────
|
||||
$rawBody = file_get_contents('php://input');
|
||||
$bodyJson = $rawBody ? json_decode($rawBody, true) : null;
|
||||
|
||||
if (isset($bodyJson['accion']) && $bodyJson['accion'] === 'borrar') {
|
||||
// Obtener ruta guardada
|
||||
$stmt = $pdo->prepare("SELECT valor FROM lab_config WHERE clave = 'turnero_tv_video' LIMIT 1");
|
||||
$stmt->execute();
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row && $row['valor']) {
|
||||
// Derivar nombre de archivo de la URL guardada
|
||||
$filename = basename(parse_url($row['valor'], PHP_URL_PATH));
|
||||
$filepath = $uploadDir . $filename;
|
||||
if ($filepath && is_file($filepath)) {
|
||||
@unlink($filepath);
|
||||
}
|
||||
}
|
||||
|
||||
// Borrar de la BD
|
||||
$pdo->prepare("DELETE FROM lab_config WHERE clave = 'turnero_tv_video'")->execute();
|
||||
echo json_encode(['ok' => true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Acción: subir ──────────────────────────────────────────────
|
||||
if (!isset($_FILES['video'])) {
|
||||
echo json_encode(['ok' => false, 'error' => 'No se recibió ningún archivo']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$file = $_FILES['video'];
|
||||
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
$errores = [
|
||||
UPLOAD_ERR_INI_SIZE => 'El archivo supera upload_max_filesize en php.ini',
|
||||
UPLOAD_ERR_FORM_SIZE => 'El archivo supera MAX_FILE_SIZE del formulario',
|
||||
UPLOAD_ERR_PARTIAL => 'El archivo se subió parcialmente',
|
||||
UPLOAD_ERR_NO_FILE => 'No se seleccionó ningún archivo',
|
||||
UPLOAD_ERR_NO_TMP_DIR => 'Falta la carpeta temporal',
|
||||
UPLOAD_ERR_CANT_WRITE => 'Error al escribir el archivo',
|
||||
];
|
||||
echo json_encode(['ok' => false, 'error' => $errores[$file['error']] ?? 'Error de subida desconocido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Validar tamaño (máx 500 MB)
|
||||
$maxBytes = 500 * 1024 * 1024;
|
||||
if ($file['size'] > $maxBytes) {
|
||||
echo json_encode(['ok' => false, 'error' => 'El video supera el límite de 500 MB']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Validar tipo MIME (solo MP4 y WebM)
|
||||
$allowedMime = ['video/mp4', 'video/webm', 'video/ogg'];
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mime = $finfo->file($file['tmp_name']);
|
||||
if (!in_array($mime, $allowedMime, true)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'Solo se permiten videos MP4 o WebM']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Definir extensión segura desde el MIME real
|
||||
$extMap = ['video/mp4' => 'mp4', 'video/webm' => 'webm', 'video/ogg' => 'ogv'];
|
||||
$ext = $extMap[$mime];
|
||||
|
||||
// Borrar video anterior si existe
|
||||
foreach (['mp4', 'webm', 'ogv'] as $oldExt) {
|
||||
$oldPath = $uploadDir . 'tv_video.' . $oldExt;
|
||||
if (is_file($oldPath)) {
|
||||
@unlink($oldPath);
|
||||
}
|
||||
}
|
||||
|
||||
$destFilename = 'tv_video.' . $ext;
|
||||
$destPath = $uploadDir . $destFilename;
|
||||
|
||||
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'No se pudo guardar el archivo']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$publicUrl = $publicBase . $destFilename;
|
||||
|
||||
// Guardar URL en lab_config (upsert)
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO lab_config (clave, valor) VALUES ('turnero_tv_video', ?)
|
||||
ON DUPLICATE KEY UPDATE valor = VALUES(valor)"
|
||||
);
|
||||
$stmt->execute([$publicUrl]);
|
||||
|
||||
echo json_encode(['ok' => true, 'url' => $publicUrl]);
|
||||
Reference in New Issue
Block a user