Heruntergeladen heisst nicht: ohne Angaben

Bei einer lokalen Datei kehrte der Spieler zurück, sobald sie lief — und
liess `item` leer. Daran hing mehr als der Titel: keine Marken, also kein
„Intro überspringen"; keine Nachbarfolgen, also keine Knöpfe zum Blättern.
Nur bei Heruntergeladenem, und darum sah es so lange nach einem Fehler in
der Erkennung aus. Jetzt werden die Angaben nachgeholt, während das Bild
schon läuft.

Dazu, was derselbe Weg betrifft: der Spieler sagt „Staffel 1 · Folge 6"
statt „S1 E6" — der Folgenname wird an einer Stelle gebaut, nicht an
dreien. Im Abspann einer Serie stehen zwei Knöpfe statt einem, weil der
Abspann zwei Fragen stellt.

Und die Abdrücke, mit denen der Tonabgleich geprüft wird, sind jetzt
echte: zwei Aufnahmen desselben dreissigsekündigen Stücks, verschiedene
Bitraten, durch fpcalc gedreht. Vorher war jede Prüfung mit erfundenen
Zahlen bestanden — und genau deshalb war nie belegt, dass der Abgleich an
echten Daten etwas findet. Er findet: 17,8–51,0 s und 72,7–105,9 s, bei
20–50 und 75–105.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-13 16:03:57 +02:00
co-authored by Claude Opus 5
parent afa78f9ee0
commit fbd865e93b
9 changed files with 167 additions and 41 deletions
+57 -29
View File
@@ -178,7 +178,18 @@ export type Spieleranfrage = {
dateiId: string;
/** Die Kennung des Titels — dorthin geht der Fortschritt. */
titelId: string;
/** Wie der Titel heisst — bei einem Film reicht das.
*
* **Bei einer Folge nicht.** Dort braucht die Zeile über dem Spieler
* Serie, Staffel, Nummer und Folgentitel, und jede Startstelle hat sie
* anders zusammengesetzt: mal „S1 E6 · Name", mal nur „Wiedergabe", mal
* bloss den Serienamen. Deshalb kommen die Teile jetzt einzeln, und die
* Zeile entsteht genau einmal — hier. */
name: string;
serie?: string | null;
staffel?: number | null;
folge?: number | null;
folgentitel?: string | null;
/** Ab welcher Sekunde des Films. */
ab: number;
/** Wie lang der Film ist. **Aus der Mediathek, nicht aus dem Strom** — der
@@ -274,7 +285,9 @@ export function spieler(anfrage: Spieleranfrage): void {
kopf.className = 'pl-kopf';
const name = document.createElement('span');
name.className = 'pl-titel';
name.textContent = anfrage.name;
name.textContent = anfrage.staffel != null || anfrage.folge != null
? folgenname(anfrage.serie, anfrage.staffel, anfrage.folge, anfrage.folgentitel)
: anfrage.name;
const kopfknoepfe = document.createElement('div');
kopfknoepfe.className = 'pl-gruppe';
@@ -370,10 +383,13 @@ export function spieler(anfrage: Spieleranfrage): void {
// die Leiste gar nicht, und ein Knopf, von dem niemand weiss, dass es ihn
// gibt, ist keiner. Als eigene Schicht über dem Bild bleibt er sichtbar,
// solange die Marke läuft.
const ueberspringen = document.createElement('button');
ueberspringen.className = 'btn btn-primary pl-ueberspringen hidden';
// **Zwei Knöpfe, nicht einer.** Im Abspann einer Serie gibt es zwei
// vernünftige Antworten — weiterschauen oder den Abspann überspringen —
// und welche gemeint ist, weiss nur der Zuschauer.
const markenreihe = document.createElement('div');
markenreihe.className = 'pl-marken hidden';
schicht.append(video, dreher, mitte, kopf, bedienung, ueberspringen, meldungsfeld, kleinleiste);
schicht.append(video, dreher, mitte, kopf, bedienung, markenreihe, meldungsfeld, kleinleiste);
document.body.appendChild(schicht);
document.body.classList.add('pl-offen');
@@ -606,13 +622,6 @@ export function spieler(anfrage: Spieleranfrage): void {
//
// Jetzt stehen sie dort, wo man hinsieht: links und rechts vom Knopf, den
// man drückt.
/** Wie die Serie heisst — für die Zeile über dem Spieler.
*
* Aus dem übergebenen Namen gewonnen: wer den Spieler startet, kennt sie,
* der Folgenvertrag nennt sie nicht. Steht dort nichts Brauchbares,
* bleibt die Zeile eben ohne Serie. */
const serienname = anfrage.name.includes(' · ') ? anfrage.name.split(' · ')[0] : null;
/** Die Nachbarfolgen, sobald der Server sie genannt hat. */
let vorige: Folge | null = null;
let naechste: Folge | null = null;
@@ -702,7 +711,12 @@ export function spieler(anfrage: Spieleranfrage): void {
spieler({
dateiId: datei.id,
titelId: ziel.id,
name: folgenname(serienname, ziel.season, ziel.episode, ziel.title),
// Die Serie bleibt dieselbe; sie steht schon im laufenden Wunsch.
name: ziel.title ?? '',
serie: anfrage.serie,
staffel: ziel.season,
folge: ziel.episode,
folgentitel: ziel.title,
ab: stelle,
laenge: datei.durationSeconds,
tonspur: ton,
@@ -778,31 +792,45 @@ export function spieler(anfrage: Spieleranfrage): void {
* **Keine eigene Uhr.** Die Zeit wird ohnehin bei jedem Fortschreiten
* nachgeführt; ein zweiter Zeitgeber dafür liefe neben dem ersten her und
* wäre eine zweite Stelle, an der etwas hängen bleibt. */
/** Was gerade zu tun ist, als ein oder zwei Knöpfe.
*
* **Neu gezeichnet nur, wenn sich etwas ändert.** `zeitAuffrischen` läuft
* mehrmals je Sekunde; die Knöpfe jedesmal neu zu bauen hiesse, einen
* Klick mitten im Drücken zu verlieren.
*/
let markenstandJetzt = '';
function markenstand(jetzt: number): void {
const drin = marken.find(m => jetzt >= m.startSeconds && jetzt < m.endSeconds);
ueberspringen.classList.toggle('hidden', !drin);
if (!drin) return;
// **Beim Abspann einer Serie ist „überspringen" die falsche Frage.**
//
// Was man dort will, ist die nächste Folge — ans Ende dieser zu
// springen bringt niemanden weiter. Bei einem Film gibt es nichts
// danach; dort heisst es, ihn zu beenden, und der Server hakt ihn
// beim Melden der Stelle ohnehin ab.
const abspann = drin.kind === 'credits';
const abspann = drin?.kind === 'credits';
const weiter = abspann && naechste?.files[0] != null;
const kennung = `${drin?.startSeconds ?? ''}|${weiter}`;
const text = !abspann ? 'Intro überspringen'
: weiter ? 'Nächste Folge'
: 'Abspann überspringen';
if (ueberspringen.textContent !== text) ueberspringen.textContent = text;
markenreihe.classList.toggle('hidden', !drin);
if (!drin || kennung === markenstandJetzt) return;
markenstandJetzt = kennung;
markenreihe.replaceChildren();
ueberspringen.onclick = e => {
// **Im Abspann einer Serie zuerst die nächste Folge.** Das ist, was man
// dort will; „überspringen" steht daneben für den, der den Abspann
// sehen wollte und es sich anders überlegt.
if (weiter) {
const w = knopf('Nächste Folge', 'btn btn-primary');
w.onclick = e => { e.stopPropagation(); folgeWechseln(naechste); };
markenreihe.appendChild(w);
}
const u = knopf(
abspann ? 'Abspann überspringen' : 'Intro überspringen',
weiter ? 'btn' : 'btn btn-primary');
u.onclick = e => {
e.stopPropagation();
if (weiter) { folgeWechseln(naechste); return; }
if (abspann) { void beenden(); return; }
// Bei einem Film und bei der letzten Folge heisst „überspringen"
// Schluss: ans Ende zu spulen liesse ein stehendes Bild zurück.
if (abspann && !weiter) { void beenden(); return; }
void springen(drin.endSeconds);
};
markenreihe.appendChild(u);
}
/** Ans Ende springen und zumachen — für den Abspann ohne Nachfolger.
+4 -2
View File
@@ -928,13 +928,15 @@ textarea.feld {
* Leiste gar nicht. Hier liegt er über dem Bild, rechts über der Stelle,
* an der die Leiste erscheinen würde, und bleibt stehen, solange die Marke
* läuft. */
.pl-ueberspringen {
.pl-marken {
position: absolute;
right: var(--s-section);
bottom: calc(var(--s-section) * 3.5);
z-index: 3;
box-shadow: 0 2px 12px rgba(0, 0, 0, .6);
display: flex;
gap: var(--s-snug);
}
.pl-marken .btn { box-shadow: 0 2px 12px rgba(0, 0, 0, .6); }
.pl-unten {
display: grid; grid-template-columns: 1fr auto 1fr;
+6 -4
View File
@@ -2,7 +2,7 @@ import {
inhalt, weiterschauen, naechsteFolgen, empfehlungen, saisonal,
type Bibliothek, type Angefangen,
} from '../api';
import { leer, fehlerzeile, meldung, folgenname } from '../util';
import { leer, fehlerzeile, meldung } from '../util';
import { kachel, alsKachel, angefangenAlsKachel, reihe } from '../card';
import { hero, alsHero, angefangenAlsHero, type Heroeintrag } from '../hero';
import { spieler } from '../spieler';
@@ -85,9 +85,11 @@ export function startseite(
spieler({
dateiId: e.fileId,
titelId: e.id,
name: e.kind === 'episode'
? folgenname(e.title, e.season, e.episode, e.subTitle)
: e.title,
name: e.title,
serie: e.kind === 'episode' ? e.title : null,
staffel: e.season,
folge: e.episode,
folgentitel: e.subTitle,
ab: e.state.positionSeconds,
laenge: e.durationSeconds ?? null,
danach: () => void aufbauen(),