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"""
import asyncio
import json
import logging
import aiomysql
from aiohttp import web

View file

@ -285,6 +285,15 @@ function toggleOverlay() {
if (overlayOpen) {
renderOverlay();
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
overlayOpen = false;
overlay.style.display = "none";
// Alle Sektionen wieder sichtbar machen
overlay.querySelectorAll(".player-overlay-section").forEach(
s => s.style.display = "");
return;
}
overlayOpen = true;
@ -302,8 +314,25 @@ function openOverlaySection(section) {
renderOverlay();
showControls();
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);
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);
}
// === 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 ===
function onKeyDown(e) {
@ -443,29 +511,109 @@ function onKeyDown(e) {
10009: "Escape", 10182: "Escape",
415: "Play", 19: "Pause", 413: "Stop",
417: "FastForward", 412: "Rewind",
// Samsung Farbtasten
403: "ColorRed", 404: "ColorGreen",
405: "ColorYellow", 406: "ColorBlue",
};
const key = keyMap[e.keyCode] || e.key;
// Overlay offen? -> Navigation im Overlay
if (overlayOpen && (key === "Escape" || key === "Backspace")) {
toggleOverlay();
e.preventDefault();
return;
const active = document.activeElement;
const buttonFocused = active && active.hasAttribute("data-focusable") &&
active.tagName === "BUTTON";
// --- 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) {
case " ": case "Enter": case "Play": case "Pause":
case " ": case "Play": case "Pause":
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":
seekRelative(-10); e.preventDefault(); break;
seekRelative(-10); showControls(); e.preventDefault(); break;
case "ArrowRight": case "FastForward":
seekRelative(10); e.preventDefault(); break;
seekRelative(10); showControls(); e.preventDefault(); break;
case "ArrowUp":
if (videoEl) videoEl.volume = Math.min(1, videoEl.volume + 0.1);
showControls(); e.preventDefault(); break;
// Erster Druck: Controls + Focus auf Buttons
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":
if (videoEl) videoEl.volume = Math.max(0, videoEl.volume - 0.1);
showControls(); e.preventDefault(); break;
if (!controlsVisible) {
showControls();
if (playBtn) playBtn.focus();
} else {
if (playBtn) playBtn.focus();
showControls();
}
e.preventDefault(); break;
case "Escape": case "Backspace": case "Stop":
saveProgress();
setTimeout(() => window.history.back(), 100);
@ -477,6 +625,15 @@ function onKeyDown(e) {
case "n":
if (cfg.nextVideoId) playNextEpisode();
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;
}
}