From 78368db5821b4ba8f71e8fb7b2d9e4ec2d13d9f6 Mon Sep 17 00:00:00 2001 From: data Date: Mon, 9 Mar 2026 13:19:00 +0100 Subject: [PATCH] fix: PWA Direct-Play statt HLS + Template-Fix series_detail - Browser/PWA nutzt jetzt direkte MP4-Wiedergabe mit Range-Requests - Codec-Pruefung (H.264/HEVC/AV1) mit automatischem HLS-Fallback - direct_play_url zur Library Video-Info-Route hinzugefuegt - Doppeltes endblock in series_detail.html entfernt (500er Fehler) Co-Authored-By: Claude Opus 4.6 --- video-konverter/app/routes/library_api.py | 3 + video-konverter/app/static/tv/js/player.js | 85 ++++++++++++++++++- .../app/templates/tv/series_detail.html | 1 - 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/video-konverter/app/routes/library_api.py b/video-konverter/app/routes/library_api.py index 6df0381..77a4ab6 100644 --- a/video-konverter/app/routes/library_api.py +++ b/video-konverter/app/routes/library_api.py @@ -1717,6 +1717,9 @@ def setup_library_routes(app: web.Application, config: Config, ) ] + # Direct-Play URL fuer Browser/PWA (MP4 mit Range-Requests) + video["direct_play_url"] = f"/tv/api/direct-stream/{video_id}" + return web.json_response(video) # === Episoden-Thumbnails === diff --git a/video-konverter/app/static/tv/js/player.js b/video-konverter/app/static/tv/js/player.js index 0c910bd..b4a1d5f 100644 --- a/video-konverter/app/static/tv/js/player.js +++ b/video-konverter/app/static/tv/js/player.js @@ -88,11 +88,16 @@ function initPlayer(opts) { } }); } - // Prioritaet 3: HLS-Streaming (Browser, kein nativer Player) + // Prioritaet 3: Browser/PWA - Direct-Play (MP4) bevorzugt, HLS als Fallback else { - const infoReady = loadVideoInfo(); - startHLSStream(opts.startPos || 0); - infoReady.then(() => updatePlayerButtons()); + showLoading(); + const startPos = opts.startPos || 0; + loadVideoInfo().then(() => { + updatePlayerButtons(); + _tryBrowserDirectPlay(startPos); + }).catch(() => { + startHLSStream(startPos); + }); } // Events @@ -621,6 +626,78 @@ function _nativeFallbackToHLS(startPosSec) { infoReady.then(function() { updatePlayerButtons(); }); } +// === Browser Direct-Play (MP4 mit Range-Requests) === + +/** + * Versucht direkte MP4-Wiedergabe im Browser (PWA). + * Faellt auf HLS zurueck wenn Codec nicht unterstuetzt oder Fehler. + */ +function _tryBrowserDirectPlay(seekSec) { + if (!videoInfo || !videoInfo.direct_play_url) { + console.info("[Player] Keine Direct-Play-URL, Fallback auf HLS"); + startHLSStream(seekSec); + return; + } + + var directUrl = videoInfo.direct_play_url; + console.info("[Player] Browser Direct-Play: " + directUrl); + + // Pruefen ob der Browser den Codec abspielen kann + var codec = (videoInfo.video_codec || "").toLowerCase(); + var canPlay = false; + if (codec === "h264" || codec === "avc1") { + canPlay = !!videoEl.canPlayType('video/mp4; codecs="avc1.640028"'); + } else if (codec === "hevc" || codec === "h265" || codec === "hvc1") { + canPlay = !!videoEl.canPlayType('video/mp4; codecs="hvc1.1.6.L120"'); + } else if (codec === "av1") { + canPlay = !!videoEl.canPlayType('video/mp4; codecs="av01.0.08M.08"'); + } else { + // Unbekannter Codec - einfach versuchen + canPlay = true; + } + + if (!canPlay) { + console.info("[Player] Browser unterstuetzt Codec '" + codec + "' nicht, Fallback auf HLS"); + startHLSStream(seekSec); + return; + } + + // Direct-Play starten + useDirectPlay = true; + videoEl.src = directUrl; + + // Seek-Position setzen sobald Metadaten geladen + if (seekSec > 0) { + videoEl.addEventListener("loadedmetadata", function() { + videoEl.currentTime = seekSec; + }, { once: true }); + } + + // Fehler-Handler: Bei Fehler auf HLS wechseln + var directErrorHandler = function(e) { + console.warn("[Player] Direct-Play Fehler, wechsle zu HLS:", e); + videoEl.removeEventListener("error", directErrorHandler); + useDirectPlay = false; + videoEl.removeAttribute("src"); + videoEl.load(); + startHLSStream(seekSec); + }; + videoEl.addEventListener("error", directErrorHandler, { once: true }); + + // Abspielen sobald bereit + videoEl.addEventListener("canplay", function() { + videoEl.removeEventListener("error", directErrorHandler); + }, { once: true }); + + videoEl.play().catch(function(e) { + console.warn("[Player] Autoplay blockiert (Direct-Play):", e); + hideLoading(); + showControls(); + if (playBtn) playBtn.innerHTML = "▶"; + }); +} + + // === HLS Streaming === async function startHLSStream(seekSec) { diff --git a/video-konverter/app/templates/tv/series_detail.html b/video-konverter/app/templates/tv/series_detail.html index 7683026..5546f8b 100644 --- a/video-konverter/app/templates/tv/series_detail.html +++ b/video-konverter/app/templates/tv/series_detail.html @@ -385,4 +385,3 @@ document.addEventListener('focusin', function(e) { {% endif %} {% endblock %} -{% endblock %}