Untertitel vorher wählen, wie den Ton auch

Auf der Detailseite stand „Ton: Deutsch" und liess sich vor dem Start
umstellen — aus gutem Grund: im Spieler umzuschalten heisst, den Strom
neu aufzubauen. Für Untertitel gab es das nicht, obwohl dort dieselbe
Überlegung gilt und die Spuren ohnehin schon dastehen.

Jetzt steht „Untertitel: Keine" daneben. Auch bei nur einer Spur: anders
als beim Ton gibt es hier immer mindestens zwei Möglichkeiten — diese
eine, oder gar keine.

Der Wunsch wird gemerkt wie beim Ton, mit einem dritten Zustand. „Keine"
ist eine Antwort und kein Fehlen einer: wer sie abgewählt hat, will sie
beim nächsten Film auch nicht, während „nie etwas gesagt" offen lässt,
ob eine erzwungene Spur erscheinen darf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-10 21:33:35 +02:00
co-authored by Claude Opus 5
parent ab43656f71
commit 2c51f639be
2 changed files with 92 additions and 2 deletions
+25 -1
View File
@@ -138,6 +138,26 @@ export function tonspracheMerken(sprache: string): void {
try { localStorage.setItem(SPRACHSCHLUESSEL, sprache); } catch { /* Privates Fenster. */ } try { localStorage.setItem(SPRACHSCHLUESSEL, sprache); } catch { /* Privates Fenster. */ }
} }
/** Dasselbe für den Untertitel — mit einem dritten Zustand.
*
* **„Keine" ist eine Antwort, nicht das Fehlen einer.** Wer Untertitel
* abgewählt hat, will sie beim nächsten Film auch nicht; ein leerer Eintrag
* hiesse dagegen „noch nie etwas gesagt", und dann darf eine erzwungene
* Spur durchaus erscheinen. */
const UNTERTITELSCHLUESSEL = 'flaro.untertitelsprache';
/** Der gemerkte Wunsch: eine Sprache, `'keine'` oder `null` für „nie gesagt". */
export function untertitelsprache(): string | null {
try { return localStorage.getItem(UNTERTITELSCHLUESSEL); } catch { return null; }
}
export function untertitelspracheMerken(wunsch: string): void {
try { localStorage.setItem(UNTERTITELSCHLUESSEL, wunsch); } catch { /* Privates Fenster. */ }
}
/** Was „keine Untertitel" im Speicher heisst. */
export const OHNE_UNTERTITEL = 'keine';
/** „Ohne Versatz", „Ton 200 ms später", „Ton 1 s früher". */ /** „Ohne Versatz", „Ton 200 ms später", „Ton 1 s früher". */
function versatzWort(ms: number): string { function versatzWort(ms: number): string {
if (ms === 0) return 'Ohne Versatz'; if (ms === 0) return 'Ohne Versatz';
@@ -166,6 +186,8 @@ export type Spieleranfrage = {
laenge: number | null; laenge: number | null;
/** Mit welcher Tonspur gestartet wird — gewählt auf der Detailseite. */ /** Mit welcher Tonspur gestartet wird — gewählt auf der Detailseite. */
tonspur?: number | null; tonspur?: number | null;
/** Mit welchem Untertitel gestartet wird. `null` heisst: keiner. */
untertitelspur?: number | null;
danach: () => void; danach: () => void;
}; };
@@ -213,7 +235,9 @@ export function spieler(anfrage: Spieleranfrage): void {
/** Die gewählte Stufe und Spuren — sie überleben einen Neustart des Stroms. */ /** Die gewählte Stufe und Spuren — sie überleben einen Neustart des Stroms. */
let stufe: number | null = null; let stufe: number | null = null;
let tonspur: number | null = anfrage.tonspur ?? null; let tonspur: number | null = anfrage.tonspur ?? null;
let untertitel: number | null = null; // **Die Wahl von der Detailseite gilt ab dem ersten Bild.** Sie erst im
// Spieler zu treffen hiesse, die ersten Sätze ohne Untertitel zu sehen.
let untertitel: number | null = anfrage.untertitelspur ?? null;
/** Ob der Server umkodieren soll, egal was das Profil sagt. /** Ob der Server umkodieren soll, egal was das Profil sagt.
* *
+67 -1
View File
@@ -10,7 +10,10 @@ import {
import { icons, icon } from '../icons'; import { icons, icon } from '../icons';
import { kachel, alsKachel, reihe } from '../card'; import { kachel, alsKachel, reihe } from '../card';
import { vergessen } from '../ansichten'; import { vergessen } from '../ansichten';
import { spieler, tonsprache, tonspracheMerken } from '../spieler'; import {
spieler, tonsprache, tonspracheMerken,
untertitelsprache, untertitelspracheMerken, OHNE_UNTERTITEL,
} from '../spieler';
import { menueAnZiel } from '../menue'; import { menueAnZiel } from '../menue';
import { meldedialog } from '../melden'; import { meldedialog } from '../melden';
@@ -409,6 +412,9 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
const tonwahl = tonspurwahl(datei); const tonwahl = tonspurwahl(datei);
if (tonwahl) reihe.appendChild(tonwahl); if (tonwahl) reihe.appendChild(tonwahl);
const textwahl = untertitelwahl(datei);
if (textwahl) reihe.appendChild(textwahl);
// **Kein „Herunterladen" im Browser.** // **Kein „Herunterladen" im Browser.**
// //
// Hier stand ein Knopf, der die Datei als MP4 herausgab. Das ist etwas // Hier stand ein Knopf, der die Datei als MP4 herausgab. Das ist etwas
@@ -518,6 +524,65 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
return k; return k;
} }
/** Welcher Untertitel beim Start laufen soll — `null` heisst: keiner. */
let gewaehlterText: number | null = null;
/** Ob oben schon eine Wahl getroffen wurde. Sonst gilt der gemerkte Wunsch. */
let textGewaehlt = false;
/** Dasselbe für den Untertitel — mit „Keine" als erster Wahl.
*
* **Auch bei nur einer Spur.** Anders als beim Ton gibt es hier immer
* mindestens zwei Möglichkeiten: diese eine, oder gar keine. Ein Knopf,
* der nur „Deutsch" bestätigt, wäre beim Ton überflüssig — hier ist er
* der einzige Weg, die Untertitel vorher abzuschalten. */
function untertitelwahl(datei: Datei): HTMLElement | null {
const spuren = datei.streams.filter(s => s.kind === 'Subtitle');
if (spuren.length === 0) return null;
if (!textGewaehlt) {
const wunsch = untertitelsprache();
gewaehlterText = wunsch === null || wunsch === OHNE_UNTERTITEL
? null
: spuren.find(s => s.language === wunsch)?.index ?? null;
textGewaehlt = true;
}
const name = (s: Spur): string =>
s.languageName ?? s.display?.split(' · ')[0] ?? s.language ?? `Spur ${s.index}`;
const k = knopf('', 'btn');
const zeichnen = (): void => {
const jetzt = spuren.find(s => s.index === gewaehlterText);
k.textContent = `Untertitel: ${jetzt ? name(jetzt) : 'Keine'}`;
k.prepend(icon('untertitel'));
};
zeichnen();
k.onclick = () => menueAnZiel(k, [
{
text: 'Keine',
icon: gewaehlterText === null ? ('haken' as const) : undefined,
tun: () => {
gewaehlterText = null;
untertitelspracheMerken(OHNE_UNTERTITEL);
zeichnen();
},
},
...spuren.map(s => ({
text: name(s),
icon: s.index === gewaehlterText ? ('haken' as const) : undefined,
tun: () => {
gewaehlterText = s.index;
// Wer hier wechselt, meint es auch beim nächsten Film so.
if (s.language) untertitelspracheMerken(s.language);
zeichnen();
},
})),
]);
return k;
}
function rundKnopf( function rundKnopf(
bild: keyof typeof icons, an: boolean, titel: string, tun: () => Promise<void>, bild: keyof typeof icons, an: boolean, titel: string, tun: () => Promise<void>,
): HTMLButtonElement { ): HTMLButtonElement {
@@ -874,6 +939,7 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
ab, ab,
laenge: datei.durationSeconds, laenge: datei.durationSeconds,
tonspur: gewaehlteSpur, tonspur: gewaehlteSpur,
untertitelspur: gewaehlterText,
danach: laden, danach: laden,
}); });
} }