fix: VideoKonverter v4.0.3 - JSON-Import-Fix, Player D-Pad-Navigation, Overlay-Bugfix

- import json in library_api.py ergänzt (fehlte, Video-Info-API crashte)
- Player: D-Pad-Navigation für Samsung TV Fernbedienung eingebaut
- Player: Samsung Farbtasten (Rot=Audio, Grün=Subs, Gelb=Qualität, Blau=Speed)
- Player: Overlay zeigt nur noch die zum Button passende Sektion
- Player: Auto-Fokus beim Öffnen von Overlays

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-03-01 10:20:30 +01:00
parent e8f2d49949
commit 75bb5d796d
2 changed files with 171 additions and 13 deletions

View file

@ -1,5 +1,6 @@
"""REST API Endpoints fuer die Video-Bibliothek""" """REST API Endpoints fuer die Video-Bibliothek"""
import asyncio import asyncio
import json
import logging import logging
import aiomysql import aiomysql
from aiohttp import web from aiohttp import web

View file

@ -285,6 +285,15 @@ function toggleOverlay() {
if (overlayOpen) { if (overlayOpen) {
renderOverlay(); renderOverlay();
showControls(); showControls();
// Nur Geschwindigkeit (Audio/Subs/Qualitaet haben eigene Buttons)
overlay.querySelectorAll(".player-overlay-section").forEach(s => {
s.style.display = s.id === "overlay-speed" ? "" : "none";
});
var el = document.getElementById("overlay-speed");
if (el) {
var firstBtn = el.querySelector("[data-focusable]");
if (firstBtn) firstBtn.focus();
}
} }
} }
@ -295,6 +304,9 @@ function openOverlaySection(section) {
// Bereits offen -> schliessen // Bereits offen -> schliessen
overlayOpen = false; overlayOpen = false;
overlay.style.display = "none"; overlay.style.display = "none";
// Alle Sektionen wieder sichtbar machen
overlay.querySelectorAll(".player-overlay-section").forEach(
s => s.style.display = "");
return; return;
} }
overlayOpen = true; overlayOpen = true;
@ -302,8 +314,25 @@ function openOverlaySection(section) {
renderOverlay(); renderOverlay();
showControls(); showControls();
if (section) { if (section) {
// Nur die gewaehlte Sektion anzeigen, andere verstecken
overlay.querySelectorAll(".player-overlay-section").forEach(s => {
s.style.display = s.id === "overlay-" + section ? "" : "none";
});
var el = document.getElementById("overlay-" + section); var el = document.getElementById("overlay-" + section);
if (el) el.scrollIntoView({ behavior: "smooth" }); if (el) {
var firstBtn = el.querySelector("[data-focusable]");
if (firstBtn) firstBtn.focus();
}
} else {
// Settings-Button: nur Geschwindigkeit (Audio/Subs/Qualitaet haben eigene Buttons)
overlay.querySelectorAll(".player-overlay-section").forEach(s => {
s.style.display = s.id === "overlay-speed" ? "" : "none";
});
var el = document.getElementById("overlay-speed");
if (el) {
var firstBtn = el.querySelector("[data-focusable]");
if (firstBtn) firstBtn.focus();
}
} }
} }
@ -435,6 +464,45 @@ function cancelNext() {
setTimeout(() => window.history.back(), 500); setTimeout(() => window.history.back(), 500);
} }
// === D-Pad Navigation fuer Fernbedienung ===
/**
* Fokussierbare Elemente im aktuellen Kontext finden.
* Im Overlay: nur Overlay-Buttons. Sonst: Player-Control-Buttons.
*/
function _getFocusables() {
if (overlayOpen) {
const overlay = document.getElementById("player-overlay");
return overlay ? Array.from(overlay.querySelectorAll("[data-focusable]")) : [];
}
// "Naechste Episode" oder "Schaust du noch" Overlay?
const nextOv = document.getElementById("next-overlay");
if (nextOv && nextOv.style.display !== "none") {
return Array.from(nextOv.querySelectorAll("[data-focusable]"));
}
const stillOv = document.getElementById("still-watching-overlay");
if (stillOv && stillOv.style.display !== "none") {
return Array.from(stillOv.querySelectorAll("[data-focusable]"));
}
// Player-Controls
const controls = document.getElementById("player-controls");
return controls ? Array.from(controls.querySelectorAll("[data-focusable]")) : [];
}
function _focusNext(direction) {
const items = _getFocusables();
if (!items.length) return false;
const cur = items.indexOf(document.activeElement);
let next;
if (direction === 1) {
next = cur < 0 ? 0 : Math.min(cur + 1, items.length - 1);
} else {
next = cur < 0 ? items.length - 1 : Math.max(cur - 1, 0);
}
items[next].focus();
return true;
}
// === Tastatur-Steuerung === // === Tastatur-Steuerung ===
function onKeyDown(e) { function onKeyDown(e) {
@ -443,29 +511,109 @@ function onKeyDown(e) {
10009: "Escape", 10182: "Escape", 10009: "Escape", 10182: "Escape",
415: "Play", 19: "Pause", 413: "Stop", 415: "Play", 19: "Pause", 413: "Stop",
417: "FastForward", 412: "Rewind", 417: "FastForward", 412: "Rewind",
// Samsung Farbtasten
403: "ColorRed", 404: "ColorGreen",
405: "ColorYellow", 406: "ColorBlue",
}; };
const key = keyMap[e.keyCode] || e.key; const key = keyMap[e.keyCode] || e.key;
// Overlay offen? -> Navigation im Overlay const active = document.activeElement;
if (overlayOpen && (key === "Escape" || key === "Backspace")) { const buttonFocused = active && active.hasAttribute("data-focusable") &&
toggleOverlay(); active.tagName === "BUTTON";
e.preventDefault();
return; // --- Overlay offen: D-Pad navigiert im Overlay ---
if (overlayOpen) {
switch (key) {
case "Escape": case "Backspace":
toggleOverlay();
// Focus zurueck auf Settings-Button
const btnSettings = document.getElementById("btn-settings");
if (btnSettings) btnSettings.focus();
e.preventDefault(); return;
case "ArrowUp":
_focusNext(-1); e.preventDefault(); return;
case "ArrowDown":
_focusNext(1); e.preventDefault(); return;
case "ArrowLeft":
_focusNext(-1); e.preventDefault(); return;
case "ArrowRight":
_focusNext(1); e.preventDefault(); return;
case "Enter":
if (buttonFocused) active.click();
e.preventDefault(); return;
}
} }
// --- "Naechste Episode" / "Schaust du noch" Overlay ---
const nextOv = document.getElementById("next-overlay");
const stillOv = document.getElementById("still-watching-overlay");
const modalOpen = (nextOv && nextOv.style.display !== "none") ||
(stillOv && stillOv.style.display !== "none");
if (modalOpen) {
switch (key) {
case "ArrowLeft": case "ArrowRight":
_focusNext(key === "ArrowRight" ? 1 : -1);
e.preventDefault(); return;
case "Enter":
if (buttonFocused) active.click();
e.preventDefault(); return;
}
}
// --- Controls sichtbar + Button fokussiert: D-Pad navigiert ---
if (controlsVisible && buttonFocused) {
switch (key) {
case "ArrowLeft":
_focusNext(-1); showControls(); e.preventDefault(); return;
case "ArrowRight":
_focusNext(1); showControls(); e.preventDefault(); return;
case "ArrowUp":
// Vom Button weg = Controls ausblenden, Video steuern
active.blur(); showControls(); e.preventDefault(); return;
case "ArrowDown":
active.blur(); showControls(); e.preventDefault(); return;
case "Enter":
active.click(); showControls(); e.preventDefault(); return;
}
}
// --- Standard Player-Tasten ---
switch (key) { switch (key) {
case " ": case "Enter": case "Play": case "Pause": case " ": case "Play": case "Pause":
togglePlay(); e.preventDefault(); break; togglePlay(); e.preventDefault(); break;
case "Enter":
// Kein Button fokussiert: Controls einblenden + Focus auf Play
if (!controlsVisible) {
showControls();
if (playBtn) playBtn.focus();
} else {
togglePlay();
}
e.preventDefault(); break;
case "ArrowLeft": case "Rewind": case "ArrowLeft": case "Rewind":
seekRelative(-10); e.preventDefault(); break; seekRelative(-10); showControls(); e.preventDefault(); break;
case "ArrowRight": case "FastForward": case "ArrowRight": case "FastForward":
seekRelative(10); e.preventDefault(); break; seekRelative(10); showControls(); e.preventDefault(); break;
case "ArrowUp": case "ArrowUp":
if (videoEl) videoEl.volume = Math.min(1, videoEl.volume + 0.1); // Erster Druck: Controls + Focus auf Buttons
showControls(); e.preventDefault(); break; if (!controlsVisible) {
showControls();
if (playBtn) playBtn.focus();
} else {
// Controls sichtbar aber kein Button fokussiert: Focus setzen
if (playBtn) playBtn.focus();
showControls();
}
e.preventDefault(); break;
case "ArrowDown": case "ArrowDown":
if (videoEl) videoEl.volume = Math.max(0, videoEl.volume - 0.1); if (!controlsVisible) {
showControls(); e.preventDefault(); break; showControls();
if (playBtn) playBtn.focus();
} else {
if (playBtn) playBtn.focus();
showControls();
}
e.preventDefault(); break;
case "Escape": case "Backspace": case "Stop": case "Escape": case "Backspace": case "Stop":
saveProgress(); saveProgress();
setTimeout(() => window.history.back(), 100); setTimeout(() => window.history.back(), 100);
@ -477,6 +625,15 @@ function onKeyDown(e) {
case "n": case "n":
if (cfg.nextVideoId) playNextEpisode(); if (cfg.nextVideoId) playNextEpisode();
e.preventDefault(); break; e.preventDefault(); break;
// Samsung Farbtasten: Direkt-Zugriff auf Overlay-Sektionen
case "ColorRed":
openOverlaySection("audio"); e.preventDefault(); break;
case "ColorGreen":
openOverlaySection("subs"); e.preventDefault(); break;
case "ColorYellow":
openOverlaySection("quality"); e.preventDefault(); break;
case "ColorBlue":
openOverlaySection("speed"); e.preventDefault(); break;
} }
} }