better handling on web player for local

This commit is contained in:
2026-01-02 19:17:56 +01:00
parent 942cab2f25
commit f72fff982c
4 changed files with 274 additions and 66 deletions

View File

@@ -121,8 +121,8 @@ const AnimePlayer = (function() {
const closeBtn = document.getElementById('close-player-btn');
if(closeBtn) closeBtn.addEventListener('click', closePlayer);
if(els.prevBtn) els.prevBtn.addEventListener('click', () => playEpisode(_currentEpisode - 1));
if(els.nextBtn) els.nextBtn.addEventListener('click', () => playEpisode(_currentEpisode + 1));
if(els.prevBtn) els.prevBtn.onclick = () => playEpisode(_currentEpisode - 1);
if(els.nextBtn) els.nextBtn.onclick = () => playEpisode(_currentEpisode + 1);
if (!document.getElementById('skip-overlay-btn')) {
const btn = document.createElement('button');
@@ -249,17 +249,12 @@ const AnimePlayer = (function() {
}
}
function playEpisode(episodeNumber) {
async function playEpisode(episodeNumber) {
const targetEp = parseInt(episodeNumber);
if (targetEp < 1 || targetEp > _totalEpisodes) return;
_currentEpisode = targetEp;
if (els.downloadBtn) {
els.downloadBtn.style.display = _isLocal ? 'none' : 'flex';
resetDownloadButtonIcon();
}
if(els.epTitle) els.epTitle.innerText = `Episode ${targetEp}`;
if(els.prevBtn) els.prevBtn.disabled = (_currentEpisode <= 1);
if(els.nextBtn) els.nextBtn.disabled = (_currentEpisode >= _totalEpisodes);
@@ -269,6 +264,7 @@ const AnimePlayer = (function() {
_skipBtn.classList.remove('is-next');
}
// Actualizar URL y Botones
const newUrl = new URL(window.location);
newUrl.searchParams.set('episode', targetEp);
window.history.pushState({}, '', newUrl);
@@ -276,19 +272,100 @@ const AnimePlayer = (function() {
if(els.playerWrapper) els.playerWrapper.style.display = 'block';
document.body.classList.add('stop-scrolling');
// Pausar trailer si existe
const trailer = document.querySelector('#trailer-player iframe');
if(trailer) trailer.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
_rpcActive = false;
if (els.extSelect.value === 'local') {
loadStream();
return;
// Mostrar carga mientras verificamos disponibilidad
setLoading("Checking availability...");
// --- LÓGICA DE AUTO-DETECCIÓN LOCAL ---
let shouldPlayLocal = false;
try {
// Consultamos a la API si ESTE episodio específico existe localmente
const check = await fetch(`/api/library/${_animeId}/units`);
const data = await check.json();
// Buscamos el episodio en la respuesta
const localUnit = data.units ? data.units.find(u => u.number === targetEp) : null;
if (localUnit) {
shouldPlayLocal = true;
}
} catch (e) {
console.warn("Availability check failed:", e);
// Si falla el check (ej: error de red), mantenemos el modo actual por seguridad
shouldPlayLocal = (els.extSelect.value === 'local');
}
if (els.serverSelect.options.length === 0) {
handleExtensionChange(true);
} else {
if (shouldPlayLocal) {
els.manualMatchBtn.style.display = 'none';
}
else{
els.manualMatchBtn.style.display = 'flex';
}
if (shouldPlayLocal) {
// CASO 1: El episodio EXISTE localmente
console.log(`Episode ${targetEp} found locally. Switching to Local.`);
// 1. Asegurar que 'local' está en el dropdown y seleccionarlo
let localOption = els.extSelect.querySelector('option[value="local"]');
if (!localOption) {
localOption = document.createElement('option');
localOption.value = 'local';
localOption.innerText = 'Local';
els.extSelect.appendChild(localOption);
}
els.extSelect.value = 'local';
// 2. Ocultar controles que no son para local
if(els.subDubToggle) els.subDubToggle.style.display = 'none';
if(els.serverSelect) els.serverSelect.style.display = 'none';
// 3. Cargar stream
loadStream();
} else {
// CASO 2: El episodio NO existe localmente (es Remoto)
// Si estábamos en modo 'local', tenemos que cambiar a una extensión
if (els.extSelect.value === 'local') {
console.log(`Episode ${targetEp} not local. Switching to Extension.`);
// 1. Quitar la opción local para evitar errores (opcional)
const localOption = els.extSelect.querySelector('option[value="local"]');
if (localOption) localOption.remove();
// 2. Restaurar la fuente original (Anilist, Gogo, etc)
// Usamos _entrySource, pero si era 'local', forzamos 'anilist' para evitar bucle
let fallbackSource = (_entrySource !== 'local') ? _entrySource : 'anilist';
// Verificar si esa fuente existe en el select, si no, usar la primera disponible
if (!els.extSelect.querySelector(`option[value="${fallbackSource}"]`)) {
if (els.extSelect.options.length > 0) {
fallbackSource = els.extSelect.options[0].value;
}
}
els.extSelect.value = fallbackSource;
// 3. Como cambiamos de Local -> Extensión, necesitamos cargar los servidores de nuevo
// handleExtensionChange(true) se encarga de cargar settings, servers y luego hacer play.
handleExtensionChange(true);
} else {
// Ya estábamos en modo remoto.
// Si por alguna razón no hay servidores cargados, recargamos la extensión.
if (els.serverSelect.options.length === 0) {
handleExtensionChange(true);
} else {
loadStream();
}
}
}
}
@@ -600,16 +677,18 @@ const AnimePlayer = (function() {
async function handleExtensionChange(shouldPlay = true) {
const selectedExt = els.extSelect.value;
if (els.manualMatchBtn) {
// Si es local, lo ocultamos. Si es extensión, lo mostramos.
els.manualMatchBtn.style.display = (selectedExt === 'local') ? 'none' : 'flex';
}
if (selectedExt === 'local') {
els.subDubToggle.style.display = 'none';
els.serverSelect.style.display = 'none';
if (shouldPlay && _currentEpisode > 0) loadStream();
return;
}
if (els.manualMatchBtn) {
// No mostrar en local, sí en extensiones
els.manualMatchBtn.style.display = (selectedExt === 'local') ? 'none' : 'flex';
}
_manualExtensionId = null;
setLoading("Loading Extension Settings...");
@@ -679,40 +758,65 @@ const AnimePlayer = (function() {
_isLocal = false;
_rawVideoData = null;
}
if (currentExt === 'local') {
try {
const localId = await getLocalEntryId();
if (!localId) {
setLoading("Local entry not found in library.");
// Paso 1: Obtener lista de archivos
const check = await fetch(`/api/library/${_animeId}/units`);
const data = await check.json();
// Paso 2: Buscar si el episodio actual existe
const targetUnit = data.units ? data.units.find(u => u.number === parseInt(_currentEpisode)) : null;
// Paso 3: Si NO existe localmente
if (!targetUnit) {
console.log(`Episode ${_currentEpisode} not found locally. Removing option.`);
const localOption = els.extSelect.querySelector('option[value="local"]');
if (localOption) localOption.remove();
const fallbackSource = (_entrySource === 'local') ? 'anilist' : _entrySource;
if (els.extSelect.querySelector(`option[value="${fallbackSource}"]`)) {
els.extSelect.value = fallbackSource;
} else if (els.extSelect.options.length > 0) {
els.extSelect.selectedIndex = 0;
}
handleExtensionChange(true);
return;
}
const check = await fetch(`/api/library/anime/${localId}/episodes`);
const eps = await check.json();
if (!eps.includes(_currentEpisode)) {
els.extSelect.value = _entrySource;
return loadStream();
}
const ext = localUrl.split('.').pop().toLowerCase();
const ext = targetUnit.format || targetUnit.name.split('.').pop().toLowerCase();
// Validación de formato para reproductor web
if (!['mp4'].includes(ext)) {
setLoading(
`Currently the web player only supports mp4 files.`
`Format '${ext}' not supported in web player. Use MPV.`
);
// Aseguramos que el botón de MPV tenga la data necesaria aunque falle el web player
_rawVideoData = {
url: targetUnit.path, // O la URL de stream correspondiente
headers: {}
};
if (els.mpvBtn) els.mpvBtn.style.display = 'flex';
return;
}
const localUrl = `/api/library/stream/${targetUnit.id}`;
_rawVideoData = {
url: window.location.origin + localUrl,
url: localUrl, // O window.location.origin + localUrl si es relativa
headers: {}
};
_currentSubtitles = [];
initVideoPlayer(localUrl, 'mp4');
} catch(e) {
console.error(e);
setLoading("Local Error: " + e.message);
}
return;