feat: VideoKonverter v5.8 - AVPlay-Overlay Fix, Debug-Stats, Focus-Ring Fix

- Tizen: Parent-Frame Transparenz + iframe z-index Fix fuer sichtbare Player-Controls ueber AVPlay
- Tizen: Farbtasten (Rot/Gruen/Gelb/Blau) werden bei aktivem AVPlay an iframe weitergeleitet
- Tizen: AVPlay Debug-Stats (State, Stream-Info, Codec, Bitrate) per postMessage abrufbar
- VKNative Bridge: requestStats() + vknative_stats Handler fuer AVPlay-Monitoring
- Player: Debug-Overlay zeigt AVPlay-spezifische Infos (Blaue Taste auf Fernbedienung)
- CSS: Episoden-Karten Focus-Ring von outline auf box-shadow umgestellt (kein Clipping mehr)
- CSS: Episode-Grid padding fuer Scale-Transform Platz
- SW Cache v16 -> v17

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eduard Wisch 2026-03-11 20:20:01 +01:00
parent dc9ee15ec3
commit 95df4d7a90
6 changed files with 100 additions and 12 deletions

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<widget xmlns="http://www.w3.org/ns/widgets" xmlns:tizen="http://tizen.org/ns/widgets"
id="http://data-it-solution.de/videokonverter" version="5.7.0" viewmodes="maximized">
id="http://data-it-solution.de/videokonverter" version="5.8.0" viewmodes="maximized">
<name>VideoKonverter</name>
<description>VideoKonverter TV-App - Serien und Filme streamen mit AVPlay Direct-Play</description>

View file

@ -103,7 +103,7 @@
<button id="connectBtn" data-focusable>Verbinden</button>
<p class="hint">Nur IP:Port eingeben (z.B. 192.168.155.12:8080).<br>
http:// und /tv/ werden automatisch ergaenzt.</p>
<p class="hint" style="margin-top:2rem;color:#555">v5.7.0 | Gruene Taste = Debug-Log</p>
<p class="hint" style="margin-top:2rem;color:#555">v5.8.0 | Gruene Taste = Debug-Log</p>
</div>
</div>
@ -127,7 +127,7 @@
<script>
/**
* VideoKonverter Tizen App v5.7
* VideoKonverter Tizen App v5.8
* Architektur: iframe (Server-UI) + AVPlay (Direct-Play im Parent-Frame)
* Kommunikation: postMessage zwischen iframe <-> Parent
* Debug: Gruene Taste = Log-Panel, Remote-Logging an /api/tizen-log
@ -199,7 +199,7 @@
_remoteQueue.push({l: level, m: String(msg).substr(0, 2000), t: new Date().toTimeString().substr(0,8)});
};
console.info("[TizenApp] v5.7.0 gestartet. Gruene Taste = Debug-Log. Remote-Logging aktiv.");
console.info("[TizenApp] v5.8.0 gestartet. Gruene Taste = Debug-Log. Remote-Logging aktiv.");
console.info("[TizenApp] localStorage=" + JSON.stringify(localStorage));
console.info("[TizenApp] userAgent=" + navigator.userAgent);
@ -453,6 +453,24 @@
// Server fordert Reset an (z.B. bei Logout)
resetToSetup();
break;
case "vknative_get_stats":
// Debug-Overlay fragt AVPlay-Stats ab
var stats = { active: _avplayActive, playing: _playing };
if (_avplayActive) {
try { stats.state = webapis.avplay.getState(); } catch (ex) { stats.state = "?"; }
try { stats.time_ms = webapis.avplay.getCurrentTime(); } catch (ex) {}
stats.duration_ms = _duration || 0;
try {
var si = webapis.avplay.getCurrentStreamInfo();
stats.streams = [];
for (var s = 0; s < si.length; s++) {
stats.streams.push({ type: si[s].type, extra: si[s].extra_info });
}
} catch (ex) {}
}
_sendToIframe({ type: "vknative_stats", stats: stats });
break;
}
});
@ -525,10 +543,15 @@
if (avEl) avEl.style.display = "block";
// iframe: Pointer deaktivieren (D-Pad kommt per postMessage)
// Opacity bleibt 1 → transparenter Hintergrund im iframe zeigt AVPlay-Video durch
// Hintergruende transparent machen damit AVPlay-Hardware-Layer durchscheint
// iframe z-index ueber avplayer (10) setzen damit Controls sichtbar bleiben
if (_iframe) {
_iframe.style.pointerEvents = "none";
_iframe.style.background = "transparent";
_iframe.style.zIndex = "20";
}
document.body.style.background = "transparent";
document.documentElement.style.background = "transparent";
// AVPlay oeffnen
console.info("[TizenApp] AVPlay oeffne: " + fullUrl);
@ -655,10 +678,15 @@
var avEl = document.getElementById("avplayer");
if (avEl) avEl.style.display = "block";
// iframe: Pointer deaktivieren (D-Pad kommt per postMessage)
// iframe: Pointer deaktivieren + Hintergruende transparent
// iframe z-index ueber avplayer (10) setzen damit Controls sichtbar bleiben
if (_iframe) {
_iframe.style.pointerEvents = "none";
_iframe.style.background = "transparent";
_iframe.style.zIndex = "20";
}
document.body.style.background = "transparent";
document.documentElement.style.background = "transparent";
// AVPlay mit HLS-URL oeffnen
console.info("[TizenApp] AVPlay HLS oeffne: " + fullUrl);
@ -754,10 +782,14 @@
var avEl = document.getElementById("avplayer");
if (avEl) avEl.style.display = "none";
// iframe wieder aktivieren
// iframe wieder aktivieren + Hintergrund/z-index wiederherstellen
if (_iframe) {
_iframe.style.pointerEvents = "auto";
_iframe.style.background = "";
_iframe.style.zIndex = "1";
}
document.body.style.background = "#0f0f0f";
document.documentElement.style.background = "";
}
function _avplay_togglePlay() {
@ -920,6 +952,14 @@
var cur2 = webapis.avplay.getCurrentTime();
_avplay_seek(Math.max(0, cur2 - 10000));
} catch (ex) {}
} else if (action.indexOf("color") === 0) {
// Farbtasten an iframe weiterleiten (Audio/Subs/Quality/Debug)
if (_iframe && _iframe.contentWindow) {
_sendToIframe({
type: "vknative_keyevent",
keyCode: e.keyCode
});
}
}
e.preventDefault();
return;

View file

@ -1818,6 +1818,7 @@ textarea.input-editing {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 0.8rem;
padding: 6px; /* Platz fuer Focus-Ring + Scale-Transform */
}
.tv-episode-tile {
position: relative;
@ -1839,12 +1840,11 @@ textarea.input-editing {
outline: none;
}
.tv-ep-tile-link:focus .tv-ep-thumb {
outline: 2px solid var(--accent);
outline-offset: -2px;
outline: none;
}
.tv-episode-tile:focus-within {
transform: scale(1.05);
box-shadow: 0 6px 20px rgba(0,0,0,0.5);
box-shadow: 0 0 0 3px var(--accent), 0 6px 20px rgba(0,0,0,0.5);
}
.tv-ep-tile-label {
padding: 0.4rem 0.5rem;

View file

@ -1907,6 +1907,41 @@ function _updateDebugInfo() {
} catch (e) {}
}
// AVPlay-Stats (Tizen: Stream-Info vom Parent-Frame)
if (useNativePlayer && window.VKNative && window.VKNative.requestStats) {
window.VKNative.requestStats(); // Async Abfrage, Ergebnis in _lastStats
var avStats = window.VKNative._lastStats;
if (avStats) {
lines.push('<hr class="dbg-sep">');
var stateClass = avStats.state === "PLAYING" ? "dbg-ok" :
avStats.state === "PAUSED" ? "dbg-val" : "dbg-warn";
lines.push(_dbgRow("AVPlay-Status", avStats.state || "?", stateClass));
if (avStats.streams && avStats.streams.length) {
for (var si = 0; si < avStats.streams.length; si++) {
var stream = avStats.streams[si];
var sType = stream.type || "?";
var sExtra = "";
if (stream.extra) {
try {
// extra_info ist JSON-String mit Codec-Details
var ex = typeof stream.extra === "string" ? JSON.parse(stream.extra) : stream.extra;
if (ex.fourCC) sExtra += ex.fourCC;
else if (ex.mimeType) sExtra += ex.mimeType;
if (ex.Width && ex.Height) sExtra += " " + ex.Width + "x" + ex.Height;
if (ex.Bit_rate) sExtra += " " + _formatBitrate(parseInt(ex.Bit_rate));
if (ex.channels) sExtra += " " + ex.channels + "ch";
if (ex.sample_rate) sExtra += " " + (parseInt(ex.sample_rate) / 1000).toFixed(1) + "kHz";
} catch (e) {
sExtra = String(stream.extra).substring(0, 60);
}
}
var sLabel = sType === "VIDEO" ? "AVP-Video" : sType === "AUDIO" ? "AVP-Audio" : "AVP-" + sType;
lines.push(_dbgRow(sLabel, sExtra || "-"));
}
}
}
}
// Client-Codecs
if (clientCodecs && clientCodecs.length) {
lines.push('<hr class="dbg-sep">');
@ -1926,7 +1961,9 @@ function _updateDebugInfo() {
} catch (e) {}
}
el.innerHTML = "<b>Debug-Info</b> <small>(i = schliessen)</small><hr class='dbg-sep'>" + lines.join("");
// Tasten-Hilfe
var helpText = window.VKNative ? "Blau = Debug" : "i = schliessen";
el.innerHTML = "<b>Debug-Info</b> <small>(" + helpText + ")</small><hr class='dbg-sep'>" + lines.join("");
}
function _dbgRow(label, value, cls) {

View file

@ -82,6 +82,11 @@
_handleParentEvent(data.event, data.detail || {});
break;
case "vknative_stats":
// AVPlay-Stats vom Parent (Antwort auf requestStats)
if (data.stats) window.VKNative._lastStats = data.stats;
break;
case "vknative_keyevent":
// Key-Event vom Parent weitergeleitet -> als KeyboardEvent dispatchen
if (data.keyCode) {
@ -296,6 +301,12 @@
_callParent("setPlaybackSpeed", [speed]);
return true;
},
/** AVPlay-Stats vom Parent abfragen (async, Ergebnis in _lastStats) */
requestStats: function() {
window.parent.postMessage({ type: "vknative_get_stats" }, "*");
},
_lastStats: null,
};
// Parent proben (wiederholt, falls Parent noch nicht bereit)

View file

@ -4,7 +4,7 @@
* Kein Offline-Caching noetig (Streaming braucht Netzwerk)
*/
const CACHE_NAME = "vk-tv-v16";
const CACHE_NAME = "vk-tv-v17";
const STATIC_ASSETS = [
"/static/tv/css/tv.css",
"/static/tv/js/tv.js",