diff --git a/desktop/src/scripts/anime/player.js b/desktop/src/scripts/anime/player.js index 9b34956..39adba2 100644 --- a/desktop/src/scripts/anime/player.js +++ b/desktop/src/scripts/anime/player.js @@ -463,18 +463,12 @@ const AnimePlayer = (function() { hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => { attachSubtitles(subtitles); - els.video.addEventListener('loadedmetadata', () => { - applyAniSkip(_malId, _currentEpisode); - }, { once: true }); initPlyr(); - els.video.addEventListener('canplay', () => { - els.video.play().catch(() => {}); - }, { once: true }); - if (els.loader) els.loader.style.display = 'none'; - }); - hlsInstance.on(Hls.Events.ERROR, function (event, data) { - if (data.fatal) setLoading("Playback Error: " + data.details); + + els.video.play().catch(() => {}); + els.loader.style.display = 'none'; }); + } else { els.video.src = url; attachSubtitles(subtitles); @@ -499,10 +493,44 @@ const AnimePlayer = (function() { }); } - function initPlyr() { + function createAudioSelector(hls) { + if (!hls.audioTracks || hls.audioTracks.length < 2) return; + const plyrEl = els.video.closest('.plyr'); + const controls = plyrEl.querySelector('.plyr__controls'); + if (!controls) return; + + if (controls.querySelector('#audio-select')) return; + + const wrapper = document.createElement('div'); + wrapper.className = 'plyr__control'; + + const select = document.createElement('select'); + select.id = 'audio-select'; + + hls.audioTracks.forEach((t, i) => { + const opt = document.createElement('option'); + opt.value = i; + opt.textContent = t.name || t.lang || `Audio ${i + 1}`; + select.appendChild(opt); + }); + + select.value = hls.audioTrack; + + select.onchange = () => { + hls.audioTrack = Number(select.value); + }; + + wrapper.appendChild(select); + controls.insertBefore(wrapper, controls.children[4]); // antes del volumen + } + + function initPlyr(enableAudio = false) { if (plyrInstance) return; + const settings = ['captions', 'quality', 'speed']; + if (enableAudio) settings.unshift('audio'); + plyrInstance = new Plyr(els.video, { captions: { active: true, @@ -520,7 +548,7 @@ const AnimePlayer = (function() { 'mute', 'volume', 'captions', 'settings', 'fullscreen', 'airplay' ], - settings: ['captions', 'quality', 'speed'] + settings }); const container = document.querySelector('.player-container'); @@ -530,6 +558,10 @@ const AnimePlayer = (function() { const tracks = els.video.textTracks; if (tracks && tracks.length) tracks[0].mode = 'showing'; + plyrInstance.on('ready', () => { + if (hlsInstance) createAudioSelector(hlsInstance); + }); + plyrInstance.on('timeupdate', (event) => { const instance = event.detail.plyr; if (!instance.duration || _progressUpdated) return; diff --git a/desktop/views/css/anime/player.css b/desktop/views/css/anime/player.css index c0e2030..82fbe88 100644 --- a/desktop/views/css/anime/player.css +++ b/desktop/views/css/anime/player.css @@ -536,4 +536,15 @@ body.stop-scrolling { .glass-btn-mpv:active { transform: scale(0.95); -} \ No newline at end of file +} + +#audio-select { + background: transparent; + color: white; + border: none; + font-size: 13px; + padding: 4px; +} +#audio-select option { + color: black; +} diff --git a/docker/src/scripts/anime/player.js b/docker/src/scripts/anime/player.js index bed170d..2524669 100644 --- a/docker/src/scripts/anime/player.js +++ b/docker/src/scripts/anime/player.js @@ -386,26 +386,13 @@ const AnimePlayer = (function() { }); hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => { - subtitles.forEach((sub, i) => { - const track = document.createElement('track'); - track.kind = 'subtitles'; - track.label = sub.label; - track.srclang = sub.srclang; - track.src = sub.src; - track.default = i === 0; - video.appendChild(track); - }); - - els.video.addEventListener('loadedmetadata', () => { - applyAniSkip(_malId, _currentEpisode); - }, { once: true }); - + attachSubtitles(subtitles); initPlyr(); - video.addEventListener('canplay', () => { - video.play().catch(() => {}); - }, { once: true }); - if (els.loader) els.loader.style.display = 'none'; + + els.video.play().catch(() => {}); + els.loader.style.display = 'none'; }); + hlsInstance.on(Hls.Events.ERROR, function (event, data) { console.error("HLS Error:", data); if (data.fatal) { @@ -413,33 +400,65 @@ const AnimePlayer = (function() { } }); } else { - console.log("Using Native Player (MP4/WebM)"); - video.src = url; - - subtitles.forEach((sub, i) => { - const track = document.createElement('track'); - track.kind = 'subtitles'; - track.label = sub.label; - track.srclang = sub.srclang; - track.src = sub.src; - track.default = i === 0; - video.appendChild(track); - }); - + els.video.src = url; + attachSubtitles(subtitles); initPlyr(); - - video.play().catch(e => console.log("Autoplay blocked", e)); - + els.video.play().catch(e => console.log("Autoplay blocked", e)); els.video.addEventListener('loadedmetadata', () => { applyAniSkip(_malId, _currentEpisode); }, { once: true }); - if(els.loader) els.loader.style.display = 'none'; } } - function initPlyr() { + function attachSubtitles(subtitles) { + subtitles.forEach((sub, i) => { + const track = document.createElement('track'); + track.kind = 'subtitles'; + track.label = sub.label; + track.srclang = sub.srclang; + track.src = sub.src; + track.default = i === 0; + els.video.appendChild(track); + }); + } + + function createAudioSelector(hls) { + if (!hls.audioTracks || hls.audioTracks.length < 2) return; + + const plyrEl = els.video.closest('.plyr'); + const controls = plyrEl.querySelector('.plyr__controls'); + if (!controls) return; + + if (controls.querySelector('#audio-select')) return; + + const wrapper = document.createElement('div'); + wrapper.className = 'plyr__control'; + + const select = document.createElement('select'); + select.id = 'audio-select'; + + hls.audioTracks.forEach((t, i) => { + const opt = document.createElement('option'); + opt.value = i; + opt.textContent = t.name || t.lang || `Audio ${i + 1}`; + select.appendChild(opt); + }); + + select.value = hls.audioTrack; + + select.onchange = () => { + hls.audioTrack = Number(select.value); + }; + + wrapper.appendChild(select); + controls.insertBefore(wrapper, controls.children[4]); // antes del volumen + } + + function initPlyr(enableAudio = false) { if (plyrInstance) return; + const settings = ['captions', 'quality', 'speed']; + if (enableAudio) settings.unshift('audio'); plyrInstance = new Plyr(els.video, { captions: { @@ -451,26 +470,26 @@ const AnimePlayer = (function() { enabled: true, fallback: true, iosNative: true, - container: '.player-container' // IMPORTANTE: El contenedor padre entra en fullscreen + container: '.player-container' }, controls: [ 'play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'settings', 'fullscreen', 'airplay' ], - settings: ['captions', 'quality', 'speed'] + settings }); - // --- MAGIA NUEVA AQUƍ --- - // Sincronizar la UI personalizada con los eventos de Plyr const container = document.querySelector('.player-container'); - // Cuando Plyr esconde sus controles (inactividad) plyrInstance.on('controlshidden', () => { container.classList.add('ui-hidden'); }); - // Cuando Plyr muestra sus controles (movimiento de mouse) + plyrInstance.on('ready', () => { + if (hlsInstance) createAudioSelector(hlsInstance); + }); + plyrInstance.on('controlsshown', () => { container.classList.remove('ui-hidden'); }); diff --git a/docker/views/css/anime/player.css b/docker/views/css/anime/player.css index 27215b6..81a519b 100644 --- a/docker/views/css/anime/player.css +++ b/docker/views/css/anime/player.css @@ -504,4 +504,47 @@ body.stop-scrolling { opacity: 1 !important; visibility: visible !important; pointer-events: auto !important; +} + +.glass-btn-mpv { + display: flex; + align-items: center; + gap: 6px; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.15); + color: white; + padding: 6px 12px; + border-radius: 8px; + font-weight: 700; + font-size: 0.85rem; + cursor: pointer; + transition: all 0.2s ease; + backdrop-filter: blur(10px); + height: 36px; +} + +.glass-btn-mpv:hover { + background: white; + color: black; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(255, 255, 255, 0.2); +} + +.glass-btn-mpv svg { + margin-top: -1px; +} + +.glass-btn-mpv:active { + transform: scale(0.95); +} + +#audio-select { + background: transparent; + color: white; + border: none; + font-size: 13px; + padding: 4px; +} +#audio-select option { + color: black; } \ No newline at end of file