Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Soyunpelotilla/03365c6e9aeee8bd4528474e1f9832c6 to your computer and use it in GitHub Desktop.
Save Soyunpelotilla/03365c6e9aeee8bd4528474e1f9832c6 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>CANALES</title>
<style>
:root {
--bg: #0d1117;
--card: #161b22;
--border: #30363d;
--text: #ffffff;
--accent: #58a6ff;
--epg: #8b949e;
--fav: #ffd700;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: var(--bg);
color: var(--text);
font-family: system-ui, sans-serif;
}
header {
text-align: center;
padding: 20px 10px 0;
}
h1 {
color: var(--accent);
font-size: 1.6rem;
margin-bottom: 6px;
}
.reloj-premium {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
background: linear-gradient(to right, #1f2933, #111820);
border: 1px solid #2d3741;
color: #e6edf3;
font-weight: 600;
font-size: 1rem;
padding: 10px 20px;
margin: 4px auto 12px;
max-width: 380px;
text-align: center;
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
letter-spacing: 0.4px;
backdrop-filter: blur(4px);
text-shadow: 0 1px 2px #00000088;
}
#reloj-svg {
width: 1em;
height: 1em;
min-width: 1em;
min-height: 1em;
}
#notificacion-toast {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: #1f6feb;
color: white;
padding: 10px 20px;
border-radius: 8px;
font-weight: 600;
z-index: 9999;
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
animation: fadeInOut 2s ease;
display: none;
}
@keyframes fadeInOut {
0% { opacity: 0; transform: translateX(-50%) translateY(-10px); }
10% { opacity: 1; transform: translateX(-50%) translateY(0); }
90% { opacity: 1; }
100% { opacity: 0; transform: translateX(-50%) translateY(-10px); }
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 16px;
padding: 10px 20px 20px;
}
.canal {
background: var(--card);
border: 1px solid var(--border);
border-radius: 8px;
text-align: center;
padding: 10px;
position: relative;
transition: transform 0.2s, outline 0.2s ease-in-out;
}
.canal:hover,
.canal:focus {
transform: scale(1.03);
cursor: pointer;
outline: 3px solid var(--accent);
z-index: 10;
}
.canal img {
max-width: 100%;
height: auto;
margin-bottom: 6px;
}
.canal span {
display: inline-block;
font-size: 0.95rem;
color: var(--text);
margin-bottom: 6px;
padding-bottom: 4px;
border-bottom: 1px solid var(--border);
}
.favorito {
position: absolute;
top: 4px;
left: 2px;
font-size: 0.8rem;
color: var(--fav);
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
padding: 3px;
display: none;
z-index: 2;
}
.favorito.activo {
display: inline-block;
animation: latido 0.4s ease;
}
@keyframes latido {
0% { transform: scale(1); }
50% { transform: scale(1.4); }
100% { transform: scale(1); }
}
.epg {
font-size: 0.72rem;
color: #ffffff;
margin-top: 6px;
min-height: 2.6em;
display: block;
text-align: center;
line-height: 1.3;
white-space: pre-line;
}
.epg-actual {
color: #00e676 !important;
font-weight: 600;
display: block;
margin-bottom: 2px;
}
.epg span:not(.epg-actual) {
color: #58a6ff !important;
}
.epg-cargando {
display: flex;
justify-content: center;
align-items: center;
gap: 6px;
font-size: 0.72rem;
color: var(--epg);
}
.spinner {
width: 12px;
height: 12px;
border: 2px solid #ccc;
border-top: 2px solid var(--accent);
border-radius: 50%;
animation: girar 0.8s linear infinite;
}
@keyframes girar {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#buscador {
padding: 14px;
width: 90%;
max-width: 500px;
border-radius: 8px;
border: 2px solid var(--border);
background: var(--card);
color: var(--text);
margin: 14px auto 24px;
display: block;
font-size: 1.1rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.tarjeta-superior {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-weight: bold;
}
.tarjeta-superior .icono {
font-size: 1.4rem;
margin-bottom: 4px;
}
.tarjeta-superior .epg {
display: none;
}
@media (max-width: 600px) {
.grid { gap: 20px; }
.canal { padding: 14px 10px; }
}
body, .grid, .canal, .epg, header, h1, h2, .estado, .favorito {
user-select: none !important;
-webkit-user-select: none !important;
}
#buscador {
user-select: text !important;
-webkit-user-select: text !important;
inputmode: text;
}
</style>
</head>
<body>
<div id="notificacion-toast" class="reloj-premium"></div>
<header>
<h1>CANALES</h1>
<div class="reloj-premium">
<svg id="reloj-svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="48" stroke="#58a6ff" stroke-width="4" fill="none"/>
<line id="hora" x1="50" y1="50" x2="50" y2="30" stroke="#e6edf3" stroke-width="5" stroke-linecap="round"/>
<line id="minuto" x1="50" y1="50" x2="50" y2="20" stroke="#e6edf3" stroke-width="3" stroke-linecap="round"/>
</svg>
<span id="reloj-local-texto"></span>
</div>
</header>
<section class="grid" id="tarjetas-superiores">
<div class="canal tarjeta-superior" onclick="toggleFavoritos()" tabindex="0">
<div class="icono">⭐</div>
<span>FAVORITOS</span>
</div>
<div class="canal tarjeta-superior" onclick="abrirEventos()" tabindex="0">
<div class="icono">📅</div>
<span>EVENTOS</span>
</div>
</section>
<input
type="text"
id="buscador"
placeholder="Buscar canal…"
oninput="buscarCanalPorPrefijo()"
autocomplete="off"
autocorrect="off"
spellcheck="false"
tabindex="0"
/>
<main class="grid" id="contenedor-canales">
<!-- 1. M+ LALIGA -->
<div class="canal" data-url="acestream://c9321006921967d6258df6945f1d598a5c0cbf1e" data-epg-id="M+ LaLiga TV HD" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://i.postimg.cc/8kqQxd4V/Background-Eraser-20250426-065222036.png" alt="M+ LALIGA">
<span class="canal-nombre">M+ LALIGA ELCANO</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 2. M+ LALIGA 2 -->
<div class="canal" data-url="acestream://51b363b1c4d42724e05240ad068ad219df8042ec" data-epg-id="M+ LaLiga TV 2 HD" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://i.postimg.cc/K8xVy0gC/Background-Eraser-20250426-065847659.png" alt="M+ LALIGA 2">
<span class="canal-nombre">M+ LALIGA 2 ELCANO</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 3. M+ LALIGA 4K -->
<div class="canal" data-url="acestream://cc9b7f5fe416069a2110da0909b0f915043c468b" data-epg-id="M+ LaLiga TV UHD" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://i.postimg.cc/8kqQxd4V/Background-Eraser-20250426-065222036.png" alt="M+ LALIGA 4K">
<span class="canal-nombre">M+ LALIGA 4K NEW LOOP</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 4. DAZN LALIGA -->
<div class="canal" data-url="acestream://e2b8a4aba2f4ea3dd68992fcdb65c9e62d910b05" data-epg-id="DAZN LaLiga HD" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://i.postimg.cc/j5hg1ZwX/Background-Eraser-20250425-234039911.png" alt="DAZN LALIGA">
<span class="canal-nombre">DAZN LALIGA ELCANO</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 5. DAZN LALIGA 2 -->
<div class="canal" data-url="acestream://51dbbfb42f8091e4ea7a2186b566a40e780953d9" data-epg-id="DAZN LaLiga 2 HD" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://i.postimg.cc/MZV1bdfM/IMG-20250425-234650.png" alt="DAZN LALIGA 2">
<span class="canal-nombre">DAZN LALIGA 2 ELCANO</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 6. DAZN 1 (m3u8) -->
<div class="canal" data-url="http://185.189.225.150:85/Eurosport1/index.m3u8" data-epg-id="DAZN 1" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/3/3e/DAZN_1_Logo.svg" alt="DAZN 1">
<span class="canal-nombre">DAZN 1</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 7. DAZN 2 -->
<div class="canal" data-url="acestream://dazn2feedabcdefabcdefabcdefabcdefabcd5678" data-epg-id="DAZN 2" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/7/7e/DAZN_2.svg" alt="DAZN 2">
<span class="canal-nombre">DAZN 2</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 8. DAZN 3 -->
<div class="canal" data-url="acestream://dazn3feedabcdefabcdefabcdefabcdefabcd0000" data-epg-id="DAZN 3" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/0/0d/DAZN_3.svg" alt="DAZN 3">
<span class="canal-nombre">DAZN 3</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 9. DAZN F1 -->
<div class="canal" data-url="acestream://f1streamabcdefabcdefabcdefabcdefabcdef55" data-epg-id="DAZN F1" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/1/1f/DAZN_F1_2024_logo.svg" alt="DAZN F1">
<span class="canal-nombre">DAZN F1</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 10. MOVISTAR DEPORTES -->
<div class="canal" data-url="acestream://movdeportes11112222333344445555666677778888" data-epg-id="Movistar Deportes" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/3/3e/Movistar_Deportes.svg" alt="MOVISTAR DEPORTES">
<span class="canal-nombre">MOVISTAR DEPORTES</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 11. MOVISTAR DEPORTES 2 -->
<div class="canal" data-url="acestream://movdeportes2abcdefabcdefabcdefabcdefabcd" data-epg-id="Movistar Deportes 2" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/6/6e/Movistar_Deportes_2.svg" alt="MOVISTAR DEPORTES 2">
<span class="canal-nombre">MOVISTAR DEPORTES 2</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 12. MOVISTAR DEPORTES 3 -->
<div class="canal" data-url="acestream://movdeportes3abcdefabcdefabcdefabcdefabcd" data-epg-id="Movistar Deportes 3" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://www.movistarplus.es/recorte/m-logos/65x65/m-plus-deportes-3.png" alt="MOVISTAR DEPORTES 3">
<span class="canal-nombre">MOVISTAR DEPORTES 3</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 13. MOVISTAR DEPORTES 4 -->
<div class="canal" data-url="acestream://movdeportes4abcdefabcdefabcdefabcdefabcd" data-epg-id="Movistar Deportes 4" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://www.movistarplus.es/recorte/m-logos/65x65/m-plus-deportes-4.png" alt="MOVISTAR DEPORTES 4">
<span class="canal-nombre">MOVISTAR DEPORTES 4</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 14. EUROSPORT 1 -->
<div class="canal" data-url="acestream://eurosport1abcdefabcdefabcdefabcdefabcdef" data-epg-id="Eurosport 1" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/5/5e/Eurosport_1_Logo_2015.svg" alt="EUROSPORT 1">
<span class="canal-nombre">EUROSPORT 1</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 15. EUROSPORT 2 -->
<div class="canal" data-url="acestream://eurosport2abcdefabcdefabcdefabcdefabcdef" data-epg-id="Eurosport 2" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/9/9e/Eurosport_2_Logo_2015.svg" alt="EUROSPORT 2">
<span class="canal-nombre">EUROSPORT 2</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 16. MOVISTAR VAMOS -->
<div class="canal" data-url="acestream://vamosabcdefabcdefabcdefabcdefabcdefabcd" data-epg-id="Vamos por M+" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Vamos_por_Movistar_Plus%2B_2023_Logo.svg" alt="MOVISTAR VAMOS">
<span class="canal-nombre">MOVISTAR VAMOS</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 17. MOVISTAR GOLF -->
<div class="canal" data-url="acestream://golfabcdefabcdefabcdefabcdefabcdefabcd" data-epg-id="Movistar Golf" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/4/4e/Movistar_Golf.svg" alt="MOVISTAR GOLF">
<span class="canal-nombre">MOVISTAR GOLF</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 18. GOL PLAY -->
<div class="canal" data-url="acestream://golplayabcdefabcdefabcdefabcdefabcdef" data-epg-id="GOL PLAY" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/1/1f/Gol_Play_logo.svg" alt="GOL PLAY">
<span class="canal-nombre">GOL PLAY</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 19. ONETORO -->
<div class="canal" data-url="acestream://onetoroabcdefabcdefabcdefabcdefabcdef" data-epg-id="OneToro TV" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/3/3f/OneToro_TV_logo.svg" alt="ONETORO">
<span class="canal-nombre">ONETORO</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 20. FOX SPORTS -->
<div class="canal" data-url="acestream://foxsportsabcdefabcdefabcdefabcdefabcdef" data-epg-id="FOX Sports" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/6/6e/FOX_Sports_logo.svg" alt="FOX SPORTS">
<span class="canal-nombre">FOX SPORTS</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 21. ESPN DEPORTES -->
<div class="canal" data-url="acestream://espndeportesabcdefabcdefabcdefabcdefabcd" data-epg-id="ESPN Deportes" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/2/2f/ESPN_Deportes_logo.svg" alt="ESPN DEPORTES">
<span class="canal-nombre">ESPN DEPORTES</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 22. ESPN -->
<div class="canal" data-url="acestream://espnabcdefabcdefabcdefabcdefabcdefabcd" data-epg-id="ESPN" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/2/2f/ESPN_wordmark.svg" alt="ESPN">
<span class="canal-nombre">ESPN</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 23. ESPN MEXICO -->
<div class="canal" data-url="acestream://espnmexicoabcdefabcdefabcdefabcdefabcd" data-epg-id="ESPN México" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/2/2f/ESPN_wordmark.svg" alt="ESPN MEXICO">
<span class="canal-nombre">ESPN MEXICO</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 24. FOX DEPORTES -->
<div class="canal" data-url="acestream://foxdeportesabcdefabcdefabcdefabcdefabcd" data-epg-id="FOX Deportes" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/6/6e/FOX_Deportes_logo.svg" alt="FOX DEPORTES">
<span class="canal-nombre">FOX DEPORTES</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 25. TUDN -->
<div class="canal" data-url="acestream://tudnabcdefabcdefabcdefabcdefabcdefabcd" data-epg-id="TUDN" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/3/3e/TUDN_Logo.svg" alt="TUDN">
<span class="canal-nombre">TUDN</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 26. UNIMAS -->
<div class="canal" data-url="acestream://unimasabcdefabcdefabcdefabcdefabcdef" data-epg-id="UniMás" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/1/1e/UniMas_logo_2021.svg" alt="UNIMAS">
<span class="canal-nombre">UNIMAS</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 27. UNIVISION -->
<div class="canal" data-url="acestream://univisionabcdefabcdefabcdefabcdefabcd" data-epg-id="Univision" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/3/3e/Univision_logo_2019.svg" alt="UNIVISION">
<span class="canal-nombre">UNIVISION</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 28. BEIN SPORTS Ñ -->
<div class="canal" data-url="acestream://beinsportsnabcdefabcdefabcdefabcdefabcd" data-epg-id="beIN Sports Ñ" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/2/2e/Bein_Sport_en_espa%C3%B1ol_logo.svg" alt="BEIN SPORTS Ñ">
<span class="canal-nombre">BEIN SPORTS Ñ</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 29. DSPORTS -->
<div class="canal" data-url="acestream://dsportsabcdefabcdefabcdefabcdefabcdef" data-epg-id="DSports" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/4/4f/DSports_logo.svg" alt="DSPORTS">
<span class="canal-nombre">DSPORTS</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
<!-- 30. TNT SPORTS -->
<div class="canal" data-url="acestream://tntsportsabcdefabcdefabcdefabcdefabcd" data-epg-id="TNT Sports" tabindex="0">
<div class="favorito">⭐</div>
<img src="https://upload.wikimedia.org/wikipedia/commons/9/9d/TNT_Sports_Argentina_logo.svg" alt="TNT SPORTS">
<span class="canal-nombre">TNT SPORTS</span>
<div class="epg"><span class="epg-cargando">Cargando… <span class="spinner"></span></span></div>
</div>
</main>
<!-- Reproductor de vídeo para streams .m3u8 -->
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
const video = document.createElement("video");
video.setAttribute("id", "reproductor-m3u8");
video.setAttribute("controls", true);
video.setAttribute("autoplay", true);
video.setAttribute("playsinline", true);
video.setAttribute("tabindex", "0");
video.style.position = "fixed";
video.style.top = "0";
video.style.left = "0";
video.style.width = "100vw";
video.style.height = "100vh";
video.style.zIndex = "99999";
video.style.display = "none";
video.style.backgroundColor = "#000";
document.body.appendChild(video);
let hlsPlayer = null;
let bloqueaPop = false;
let canalActivoAntesReproductor = null;
function reproducirM3U8(url) {
canalActivoAntesReproductor = document.activeElement;
video.pause();
video.src = "";
video.load();
video.style.display = "block";
document.body.classList.add("modo-reproductor");
video.muted = false;
if (window.hlsPlayer) hlsPlayer.destroy();
if (Hls.isSupported()) {
hlsPlayer = new Hls();
hlsPlayer.loadSource(url);
hlsPlayer.attachMedia(video);
window.hlsPlayer = hlsPlayer;
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
video.src = url;
} else {
alert("Tu navegador no soporta este tipo de vídeo.");
return;
}
history.pushState({ reproductor: true }, "reproductor");
bloqueaPop = true;
setTimeout(() => bloqueaPop = false, 300);
const esTV = /Android TV|BRAVIA|AFT/.test(navigator.userAgent);
setTimeout(() => {
video.play().catch(() => {
if (esTV) {
mostrarNotificacion("Presiona OK para iniciar la reproducción");
video.setAttribute("controls", true);
video.focus();
}
});
}, 300);
video.addEventListener("click", () => video.play());
video.addEventListener("keydown", e => {
if (e.key === "Enter") video.play();
});
}
function cerrarReproductor() {
if (hlsPlayer) {
hlsPlayer.destroy();
hlsPlayer = null;
}
video.pause();
video.removeAttribute("src");
video.load();
video.style.display = "none";
document.body.classList.remove("modo-reproductor");
if (canalActivoAntesReproductor && canalActivoAntesReproductor.classList.contains("canal")) {
canalActivoAntesReproductor.scrollIntoView({ behavior: "smooth", block: "center" });
setTimeout(() => canalActivoAntesReproductor.focus(), 100);
}
}
// Swipe ← para cerrar
let startX = 0;
video.addEventListener("touchstart", e => startX = e.touches[0].clientX);
video.addEventListener("touchend", e => {
if (e.changedTouches[0].clientX - startX < -60) cerrarReproductor();
});
// ESC o Backspace
document.addEventListener("keydown", e => {
if ((e.key === "Escape" || e.key === "Backspace") && video.style.display === "block") {
cerrarReproductor();
history.back();
}
});
// Botón Atrás Android TV
window.onpopstate = () => {
if (bloqueaPop) return;
if (video.style.display === "block") {
cerrarReproductor();
history.pushState({ reproductor: true }, "reproductor");
}
};
// Conexión con las tarjetas de canal
window.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".canal").forEach(canal => {
const url = canal.getAttribute("data-url");
if (!url?.endsWith(".m3u8")) return;
canal.addEventListener("click", e => {
if (e.target.classList.contains("favorito")) return;
e.stopPropagation();
reproducirM3U8(url);
});
});
});
</script>
<script>
function emojiParaEPG(t) {
const txt = t.toLowerCase();
if (/f[úu]tbol|liga|champions|premier|madrid|bar[çc]a/.test(txt)) return "⚽";
if (/nba|baloncesto|basket/.test(txt)) return "🏀";
if (/tenis|wta|atp/.test(txt)) return "🎾";
if (/moto ?gp|motogp/.test(txt)) return "🏍️";
if (/fórmula|formula|f1\b/.test(txt)) return "🏎️";
if (/boxeo|mma|ufc/.test(txt)) return "🥊";
if (/cine|pel[ií]cula/.test(txt)) return "🎬";
if (/serie|cap[íi]tulo|episodio/.test(txt)) return "📺";
if (/docu|documental/.test(txt)) return "📚";
return "📽️";
}
function mostrarNotificacion(texto) {
const toast = document.getElementById("notificacion-toast");
toast.textContent = texto;
toast.style.display = "block";
setTimeout(() => toast.style.display = "none", 2000);
}
function toggleFavorito(nombre, estrella) {
const favs = JSON.parse(localStorage.getItem("favs") || "[]");
const idx = favs.indexOf(nombre);
if (idx >= 0) {
favs.splice(idx, 1);
estrella?.classList.remove("activo");
mostrarNotificacion("Eliminado de favoritos");
} else {
favs.push(nombre);
estrella?.classList.add("activo");
mostrarNotificacion("Añadido a favoritos");
}
localStorage.setItem("favs", JSON.stringify(favs));
cargarFavoritos();
buscarCanalPorPrefijo();
}
function cargarFavoritos() {
const favs = JSON.parse(localStorage.getItem("favs") || "[]");
document.querySelectorAll("#contenedor-canales .canal").forEach(canal => {
const nombre = canal.querySelector(".canal-nombre")?.textContent.trim();
const estrella = canal.querySelector(".favorito");
const esFav = favs.includes(nombre);
estrella.classList.toggle("activo", esFav);
estrella.style.display = esFav ? "inline-block" : "none";
});
}
let soloFavoritos = false;
function toggleFavoritos() {
soloFavoritos = !soloFavoritos;
buscarCanalPorPrefijo();
}
function buscarCanalPorPrefijo() {
const texto = document.getElementById("buscador").value.trim().toLowerCase();
const favs = JSON.parse(localStorage.getItem("favs") || "[]");
document.querySelectorAll("#contenedor-canales .canal").forEach(canal => {
const nombreSpan = canal.querySelector(".canal-nombre");
if (!nombreSpan) return;
const nombre = nombreSpan.textContent.trim().toLowerCase();
const esFavorito = favs.includes(nombreSpan.textContent.trim());
const coincide = nombre.startsWith(texto);
const visible = coincide && (!soloFavoritos || esFavorito);
canal.style.display = visible ? "block" : "none";
});
}
function abrirEventos() {
location.href = "https://ciriaco-liart.vercel.app/";
}
function actualizarRelojLocal() {
const ahora = new Date();
const formato = ahora.toLocaleString("es-ES", {
weekday: "long",
day: "numeric",
month: "long",
hour: "2-digit",
minute: "2-digit"
}).toUpperCase();
document.getElementById("reloj-local-texto").textContent = formato;
const hora = ahora.getHours() % 12;
const min = ahora.getMinutes();
const anguloH = (hora + min / 60) * 30;
const anguloM = min * 6;
document.getElementById("hora").setAttribute("transform", `rotate(${anguloH} 50 50)`);
document.getElementById("minuto").setAttribute("transform", `rotate(${anguloM} 50 50)`);
}
setInterval(actualizarRelojLocal, 1000);
</script>
<script>
function parseFechaEPG(str) {
const y = str.slice(0, 4), m = str.slice(4, 6), d = str.slice(6, 8);
const h = str.slice(8, 10), min = str.slice(10, 12);
return new Date(`${y}-${m}-${d}T${h}:${min}:00`);
}
let epgXML = null;
function actualizarEPG() {
if (!epgXML) return;
const ahora = new Date();
document.querySelectorAll(".canal").forEach(canal => {
const id = canal.getAttribute("data-epg-id");
const contenedor = canal.querySelector(".epg");
if (!id || !contenedor) return;
const programas = [...epgXML.querySelectorAll(`programme[channel="${id}"]`)];
const eventos = programas.map(p => {
return {
ini: parseFechaEPG(p.getAttribute("start")),
fin: parseFechaEPG(p.getAttribute("stop")),
titulo: p.querySelector("title")?.textContent || "Sin título"
};
}).filter(e => e.fin > ahora).slice(0, 2);
if (!eventos.length) {
contenedor.textContent = "Sin datos";
return;
}
const [actual, siguiente] = eventos;
let html = "";
if (actual) {
const h1 = actual.ini.toTimeString().slice(0,5);
const h2 = actual.fin.toTimeString().slice(0,5);
html += `<span class="epg-actual">${emojiParaEPG(actual.titulo)} ${h1}-${h2} ${actual.titulo}</span>\n`;
}
if (siguiente) {
const h3 = siguiente.ini.toTimeString().slice(0,5);
const h4 = siguiente.fin.toTimeString().slice(0,5);
html += `<span>${emojiParaEPG(siguiente.titulo)} ${h3}-${h4} ${siguiente.titulo}</span>`;
}
contenedor.innerHTML = html;
});
}
function cargarEPG() {
fetch("https://raw.githubusercontent.com/davidmuma/EPG_dobleM/refs/heads/master/guiatv.xml")
.then(r => r.text())
.then(xml => {
epgXML = new DOMParser().parseFromString(xml, "text/xml");
actualizarEPG();
setInterval(actualizarEPG, 3600000);
})
.catch(err => {
console.warn("EPG no disponible:", err);
document.querySelectorAll(".epg").forEach(e => e.textContent = "Sin datos");
});
}
</script>
<script>
window.addEventListener("load", () => {
cargarFavoritos();
cargarEPG();
actualizarRelojLocal();
buscarCanalPorPrefijo();
const esAndroidTV = /Android TV|BRAVIA|AFT/.test(navigator.userAgent);
if (esAndroidTV) {
document.getElementById("buscador")?.setAttribute("inputmode", "none");
}
document.getElementById("buscador").addEventListener("input", () => {
buscarCanalPorPrefijo();
});
document.querySelectorAll(".canal").forEach(canal => {
const estrella = canal.querySelector(".favorito");
const nombre = canal.querySelector(".canal-nombre")?.textContent.trim();
let longPressTimer;
canal.addEventListener("touchstart", () => {
longPressTimer = setTimeout(() => toggleFavorito(nombre, estrella), 500);
});
canal.addEventListener("touchend", () => clearTimeout(longPressTimer));
canal.addEventListener("keydown", e => {
if (e.key === "Enter") {
e.preventDefault();
longPressTimer = setTimeout(() => toggleFavorito(nombre, estrella), 500);
}
});
canal.addEventListener("keyup", e => {
if (e.key === "Enter") {
clearTimeout(longPressTimer);
const url = canal.getAttribute("data-url");
if (url?.endsWith(".m3u8")) {
reproducirM3U8(url);
} else if (url) {
window.open(url, "_blank");
}
}
});
canal.addEventListener("click", e => {
if (e.target.classList.contains("favorito")) {
toggleFavorito(nombre, estrella);
e.stopPropagation();
return;
}
const url = canal.getAttribute("data-url");
if (url?.endsWith(".m3u8")) {
reproducirM3U8(url);
} else if (url) {
window.open(url, "_blank");
}
});
});
});
window.addEventListener("popstate", () => {
if (!document.body.classList.contains("modo-reproductor")) {
// No autoenfoque al volver: navegación libre
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment