Der Fortschritt ging beim Schließen verloren

Beim Schließen wurde das Video abgeräumt, und dabei fiel currentTime auf
null — bevor die Stopp-Meldung hinausging. Jellyfin bekam "gestoppt bei
0:00" und löschte den Fortschritt. Der Film war zur Hälfte gesehen und
stand trotzdem nicht unter "Weiterschauen".

Jetzt wird die Stelle getrennt vom Element gehalten und die Meldung
gesendet, bevor abgeräumt wird. Nachgemessen: bei 45 Sekunden
geschlossen, 45 Sekunden gemeldet. Dazu PlayMethod und CanSeek, ohne die
Jellyfin die Meldung keiner Art von Wiedergabe zuordnet, und sofortige
Meldung bei Anhalten und Weiterlaufen — wer pausiert und weggeht, hatte
sonst bis zur nächsten Zehn-Sekunden-Marke nichts hinterlassen.

Und das eingefrorene Bild bei laufendem Ton nach einem
Anwendungswechsel: Der Browser hält das Entpacken an, während der Tab
verdeckt ist, und hls.js findet danach nicht von selbst in den Puffer
zurück. Jetzt wird beim Zurückkommen an derselben Stelle neu angestoßen,
mit einem winzigen Sprung — ohne den bleibt das zuletzt gezeigte Bild
stehen, obwohl längst wieder Segmente ankommen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-14 18:56:10 +02:00
co-authored by Claude Opus 5
parent fa8d9de2e9
commit b51bb16de0
4 changed files with 104 additions and 7 deletions
+51 -7
View File
@@ -1754,8 +1754,14 @@ function openPlayer(item) {
// --- Schließen ---
function close() {
removeEventListener('keydown', keys);
document.removeEventListener('visibilitychange', wieder);
clearTimeout(hideTimer);
if (document.fullscreenElement) document.exitFullscreen?.();
// **Erst melden, dann abräumen.** Andersherum steht die Stelle schon auf
// null, wenn die Meldung hinausgeht.
lauf.sitzung?.melden?.('/Sessions/Playing/Stopped');
v.__hls?.destroy(); v.__hls = null;
v.pause(); v.removeAttribute('src'); v.load();
layer.remove();
@@ -1763,6 +1769,23 @@ function openPlayer(item) {
// Die Startseite kennt den neuen Fortschritt noch nicht.
if (current === 'home') renderHome();
}
/* **Zurück aus dem Hintergrund.**
Wer die Anwendung wechselt, kommt zu einem stehenden Bild bei laufendem
Ton zurück: der Browser hält das Entpacken an, während der Tab verdeckt
ist, und hls.js findet danach nicht von selbst zurück in den Puffer.
Ein Anstoß an derselben Stelle setzt das Bild wieder in Gang. */
const wieder = () => {
if (document.hidden || v.paused) return;
const h = v.__hls;
if (!h) return;
const stelle = v.currentTime;
h.startLoad(stelle);
// Ein winziger Sprung erzwingt einen neuen Schlüsselbildpunkt. Ohne ihn
// bleibt das zuletzt gezeigte Bild stehen, obwohl Segmente ankommen.
if (stelle > 0.5) v.currentTime = stelle - 0.1;
};
document.addEventListener('visibilitychange', wieder);
el2('pl-close').onclick = close;
// Womit gerade gespielt wird — und was die Auswahl hergibt.
@@ -1956,7 +1979,8 @@ async function startStream(item, v, fail, lauf) {
if (lauf.stelle > 1) v.currentTime = lauf.stelle;
v.play();
}, { once: true });
wireProgress(v, { id: info.PlaySessionId, sourceID: source.Id || item.Id, itemID: item.Id });
lauf.sitzung = { id: info.PlaySessionId, sourceID: source.Id || item.Id, itemID: item.Id };
wireProgress(v, lauf.sitzung);
if (v.canPlayType('application/vnd.apple.mpegurl')) { v.src = url; return; }
@@ -2019,7 +2043,15 @@ async function playbackInfo(item, lauf = {}) {
Fortschritt wird nie gespeichert — „Weiterschauen" wäre im Web für immer
eine leere Reihe. Alle zehn Sekunden genügt. */
function wireProgress(video, session) {
const send = (path, extra = {}) => {
// **Die zuletzt bekannte Stelle, getrennt vom Element gehalten.**
//
// Das ist der Kern der Sache: Beim Schließen wird das Video abgeräumt, und
// dabei fällt `currentTime` auf null — *bevor* die Stopp-Meldung hinausgeht.
// Jellyfin bekam dann „gestoppt bei 0:00" und löschte den Fortschritt. Der
// Film war gesehen und stand trotzdem nicht unter „Weiterschauen".
session.stelle = 0;
const send = (path, stelle = session.stelle, extra = {}) => {
// Absichtlich ohne await: eine verlorene Meldung kostet ein paar Sekunden
// Fortschritt. Dafür mitten im Film einen Fehler zu zeigen wäre die
// schlechtere Hälfte des Tauschs.
@@ -2028,23 +2060,35 @@ function wireProgress(video, session) {
body: JSON.stringify({
ItemId: session.itemID, PlaySessionId: session.id,
MediaSourceId: session.sourceID,
PositionTicks: Math.round(video.currentTime * 10_000_000), ...extra,
PositionTicks: Math.round(stelle * 10_000_000),
// Jellyfin ordnet die Meldung sonst keiner Art von Wiedergabe zu und
// verwirft sie stillschweigend.
PlayMethod: 'Transcode', CanSeek: true,
...extra,
}),
}).catch(() => {});
};
session.melden = send;
video.addEventListener('playing', () => send('/Sessions/Playing'), { once: true });
let last = 0;
video.addEventListener('timeupdate', () => {
if (video.currentTime > 0) session.stelle = video.currentTime;
if (video.currentTime - last < 10) return;
last = video.currentTime;
send('/Sessions/Playing/Progress', { IsPaused: video.paused });
send('/Sessions/Playing/Progress', session.stelle, { IsPaused: video.paused });
});
const stop = () => send('/Sessions/Playing/Stopped');
video.addEventListener('ended', stop);
video.addEventListener('emptied', stop);
// Anhalten und Weiterlaufen sofort melden: wer pausiert und weggeht, hat
// sonst bis zur nächsten Zehn-Sekunden-Marke nichts hinterlassen.
video.addEventListener('pause', () =>
send('/Sessions/Playing/Progress', session.stelle, { IsPaused: true }));
video.addEventListener('play', () =>
send('/Sessions/Playing/Progress', session.stelle, { IsPaused: false }));
// Am Ende steht die Stelle nicht mehr zur Verfügung — gemeint ist das Ende.
video.addEventListener('ended', () => send('/Sessions/Playing/Stopped', video.duration || session.stelle));
}
/* Eine Adresse mit Anmeldung im Anhang.