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 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-03-09 13:19:00 +01:00
parent 0d1619c6c9
commit 78368db582
3 changed files with 84 additions and 5 deletions

View file

@ -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) return web.json_response(video)
# === Episoden-Thumbnails === # === Episoden-Thumbnails ===

View file

@ -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 { else {
const infoReady = loadVideoInfo(); showLoading();
startHLSStream(opts.startPos || 0); const startPos = opts.startPos || 0;
infoReady.then(() => updatePlayerButtons()); loadVideoInfo().then(() => {
updatePlayerButtons();
_tryBrowserDirectPlay(startPos);
}).catch(() => {
startHLSStream(startPos);
});
} }
// Events // Events
@ -621,6 +626,78 @@ function _nativeFallbackToHLS(startPosSec) {
infoReady.then(function() { updatePlayerButtons(); }); 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 = "&#9654;";
});
}
// === HLS Streaming === // === HLS Streaming ===
async function startHLSStream(seekSec) { async function startHLSStream(seekSec) {

View file

@ -385,4 +385,3 @@ document.addEventListener('focusin', function(e) {
{% endif %} {% endif %}
</script> </script>
{% endblock %} {% endblock %}
{% endblock %}