Ein Knopf, von dem niemand weiss, dass es ihn gibt, ist keiner
„Intro überspringen" lag im Web in der Bedienleiste und verschwand mit ihr. Am Fernseher sieht man die Leiste gar nicht — der Knopf war also da und trotzdem unsichtbar. Jetzt liegt er als eigene Schicht über dem Bild und bleibt stehen, solange die Marke läuft. **Im Abspann ist „überspringen" ohnehin die falsche Frage.** Was man dort will, ist die nächste Folge; ans Ende dieser zu springen bringt niemanden weiter. Gibt es eine, heisst der Knopf jetzt so — im Web wie in der App, und in der App nicht mehr erst, wenn alles durchgelaufen ist. Bei einem Film gibt es nichts danach: dort heisst „Abspann überspringen" jetzt wirklich Schluss, statt auf ein stehendes Bild zu spulen und den Spieler offen zu lassen. Dazu der Titel über dem Spieler. Dort stand bei einer Folge „S1 E6" und bei einer aus der Detailseite gestarteten sogar nur „Wiedergabe". Auf einer Kachel ist die Kurzform richtig, dort zählt jeder Buchstabe; über dem Spieler steht eine Zeile in einer leeren Fläche. Jetzt steht da „Küstenwache · Staffel 1 · Folge 6 · Der Nebel kommt". In der App hiess es „Episode" statt „Folge" — das eine Wort, das sonst nirgends steht. Und drei Prüfungen für die Marken auf dem Weg in die App: aus einer Serverantwort mit Vorspann und Abspann werden zwei Marken an der richtigen Stelle. Die Übersetzung war nicht der Grund, warum der Knopf dort fehlte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7673c73757
commit
afa78f9ee0
+55
-13
@@ -3,7 +3,7 @@ import {
|
||||
stromStarten, stromBeenden, untertitelHolen, fortschritt, nachbarfolge, markenHolen,
|
||||
type Stromauskunft, type Spurauskunft, type Folge, type Marke,
|
||||
} from './api';
|
||||
import { dauer, knopf, meldung, hinweisZeigen } from './util';
|
||||
import { dauer, knopf, meldung, hinweisZeigen, folgenname } from './util';
|
||||
import { serienton, serientext, spurFuer } from './sprachwahl';
|
||||
import {
|
||||
GROESSE_GROESST, GROESSE_KLEINST, GROESSE_SCHRITT, HINTERGRUENDE, SCHRIFTFARBEN,
|
||||
@@ -364,7 +364,16 @@ export function spieler(anfrage: Spieleranfrage): void {
|
||||
// Ein Klick auf die Leiste selbst holt den Film zurück — wie bei Plex.
|
||||
kleinleiste.onclick = () => kleinSetzen(false);
|
||||
|
||||
schicht.append(video, dreher, mitte, kopf, bedienung, meldungsfeld, kleinleiste);
|
||||
// **„Intro überspringen" steht für sich, nicht in der Bedienleiste.**
|
||||
//
|
||||
// Dort stand er, und damit verschwand er mit ihr — am Fernseher sieht man
|
||||
// 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';
|
||||
|
||||
schicht.append(video, dreher, mitte, kopf, bedienung, ueberspringen, meldungsfeld, kleinleiste);
|
||||
document.body.appendChild(schicht);
|
||||
document.body.classList.add('pl-offen');
|
||||
|
||||
@@ -597,6 +606,13 @@ 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;
|
||||
@@ -629,15 +645,13 @@ export function spieler(anfrage: Spieleranfrage): void {
|
||||
unten.className = 'pl-unten';
|
||||
unten.append(links, zeitmitte, rechts);
|
||||
|
||||
// **„Intro überspringen" über der Leiste, rechtsbündig.**
|
||||
// **„Intro überspringen" steht für sich, nicht in der Bedienleiste.**
|
||||
//
|
||||
// Dort erwartet man ihn — so macht es jeder Spieler —, und dort verdeckt
|
||||
// er weder das Bild noch die Zeitleiste. Er steht im selben Block wie die
|
||||
// Bedienung und blendet mit ihr ein und aus.
|
||||
const ueberspringen = document.createElement('button');
|
||||
ueberspringen.className = 'btn pl-ueberspringen hidden';
|
||||
|
||||
bedienung.append(ueberspringen, zeitleiste, unten);
|
||||
// Dort stand er, und damit verschwand er mit ihr — am Fernseher sieht man
|
||||
// die Leiste gar nicht, und ein Knopf, von dem niemand weiss, dass es ihn
|
||||
// gibt, ist keiner. Jetzt liegt er als eigene Schicht über dem Bild,
|
||||
// rechts unten, und bleibt sichtbar, solange die Marke läuft.
|
||||
bedienung.append(zeitleiste, unten);
|
||||
|
||||
// **Beide Nachbarn gleich zu Beginn holen.** Zwei kleine Anfragen, einmal
|
||||
// je Wiedergabe. Erst beim Antippen zu fragen hiesse, einen Knopf zu
|
||||
@@ -688,7 +702,7 @@ export function spieler(anfrage: Spieleranfrage): void {
|
||||
spieler({
|
||||
dateiId: datei.id,
|
||||
titelId: ziel.id,
|
||||
name: `S${ziel.season} E${ziel.episode}${ziel.title ? ` · ${ziel.title}` : ''}`,
|
||||
name: folgenname(serienname, ziel.season, ziel.episode, ziel.title),
|
||||
ab: stelle,
|
||||
laenge: datei.durationSeconds,
|
||||
tonspur: ton,
|
||||
@@ -769,9 +783,37 @@ export function spieler(anfrage: Spieleranfrage): void {
|
||||
ueberspringen.classList.toggle('hidden', !drin);
|
||||
if (!drin) return;
|
||||
|
||||
const text = drin.kind === 'intro' ? 'Intro überspringen' : 'Abspann überspringen';
|
||||
// **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 weiter = abspann && naechste?.files[0] != null;
|
||||
|
||||
const text = !abspann ? 'Intro überspringen'
|
||||
: weiter ? 'Nächste Folge'
|
||||
: 'Abspann überspringen';
|
||||
if (ueberspringen.textContent !== text) ueberspringen.textContent = text;
|
||||
ueberspringen.onclick = e => { e.stopPropagation(); void springen(drin.endSeconds); };
|
||||
|
||||
ueberspringen.onclick = e => {
|
||||
e.stopPropagation();
|
||||
if (weiter) { folgeWechseln(naechste); return; }
|
||||
if (abspann) { void beenden(); return; }
|
||||
void springen(drin.endSeconds);
|
||||
};
|
||||
}
|
||||
|
||||
/** Ans Ende springen und zumachen — für den Abspann ohne Nachfolger.
|
||||
*
|
||||
* **Nicht bloss springen.** Auf die letzte Sekunde zu spulen hiess: ein
|
||||
* stehendes Bild und ein Spieler, der offen bleibt. Gemeint ist „fertig",
|
||||
* und dazu gehört, dass der Server die Stelle als Schluss verbucht — das
|
||||
* tut `schliessen` beim Melden von selbst. */
|
||||
async function beenden(): Promise<void> {
|
||||
await springen(Math.max(0, (anfrage.laenge ?? 0) - 1));
|
||||
schliessen();
|
||||
}
|
||||
|
||||
function umschalten(): void {
|
||||
|
||||
+11
-3
@@ -922,10 +922,18 @@ textarea.feld {
|
||||
*
|
||||
* Als eigene Zeile im Bedienblock, nicht als Überzug über dem Bild: so
|
||||
* blendet er mit der Bedienung ein und aus und verdeckt nie die Leiste. */
|
||||
/* **Eigene Schicht, nicht Teil der Bedienung.**
|
||||
*
|
||||
* In der Leiste verschwand er mit ihr — und am Fernseher sieht man die
|
||||
* 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 {
|
||||
align-self: flex-end;
|
||||
margin: 0 0 2px auto;
|
||||
display: block;
|
||||
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);
|
||||
}
|
||||
|
||||
.pl-unten {
|
||||
|
||||
@@ -147,3 +147,27 @@ export function hinweisZeigen(text: string, art: 'gut' | 'fehler' = 'gut'): void
|
||||
}, 200);
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
/** Wie eine Folge über dem Spieler heisst.
|
||||
*
|
||||
* **Ausgeschrieben, nicht abgekürzt.** Auf einer Kachel ist „S1 E6" richtig:
|
||||
* dort zählt jeder Buchstabe. Über dem Spieler steht eine Zeile in einer
|
||||
* leeren Fläche — dort gibt es keinen Grund zu sparen, und „Staffel 1 ·
|
||||
* Folge 6" liest sich, ohne dass man es entziffern muss.
|
||||
*
|
||||
* Fehlt die Serie, bleibt sie weg; fehlt der Folgentitel, trägt die Nummer
|
||||
* die Zeile allein.
|
||||
*/
|
||||
export function folgenname(
|
||||
serie: string | null | undefined,
|
||||
staffel: number | null | undefined,
|
||||
folge: number | null | undefined,
|
||||
titel: string | null | undefined,
|
||||
): string {
|
||||
const teile: string[] = [];
|
||||
if (serie) teile.push(serie);
|
||||
if (staffel != null) teile.push(`Staffel ${staffel}`);
|
||||
if (folge != null) teile.push(`Folge ${folge}`);
|
||||
if (titel) teile.push(titel);
|
||||
return teile.length > 0 ? teile.join(' · ') : 'Wiedergabe';
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ApiFehler,
|
||||
} from '../api';
|
||||
import {
|
||||
esc, dauer, groesse, knopf, feld, fehlerzeile, leer, anteil, meldung, hinweisZeigen,
|
||||
folgenname,
|
||||
} from '../util';
|
||||
import { icons, icon } from '../icons';
|
||||
import { kachel, alsKachel, reihe } from '../card';
|
||||
@@ -925,6 +926,7 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
|
||||
async function staffeln(serie: Serie): Promise<HTMLElement> {
|
||||
const bereich = document.createElement('section');
|
||||
serieKennung = serie.id;
|
||||
serienname = serie.title;
|
||||
const alle = await folgen(serie.id);
|
||||
|
||||
// **Eine Staffel zur Zeit, über ein Menü.**
|
||||
@@ -1080,6 +1082,8 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
|
||||
|
||||
/** Zu welcher Serie die Folgen gehören — für die Sprachwahl beim Start. */
|
||||
let serieKennung: string | undefined;
|
||||
/** Und wie sie heisst — für die Zeile über dem Spieler. */
|
||||
let serienname: string | undefined;
|
||||
|
||||
function folgenblock(folge: Folge): HTMLElement {
|
||||
const block = document.createElement('div');
|
||||
@@ -1137,7 +1141,10 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
|
||||
block.appendChild(welche);
|
||||
}
|
||||
block.appendChild(aktionsreihe(
|
||||
// **Mit Titel, und ausgeschrieben.** Ohne ihn stand über dem
|
||||
// Spieler „Wiedergabe" — bei jeder Folge jeder Serie dasselbe Wort.
|
||||
{ ...({} as Film), id: folge.id, files: folge.files,
|
||||
title: folgenname(serienname, folge.season, folge.episode, folge.title),
|
||||
publishAt: folge.publishAt ?? null } as Film,
|
||||
folge.state, datei, undefined, serieKennung));
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
inhalt, weiterschauen, naechsteFolgen, empfehlungen, saisonal,
|
||||
type Bibliothek, type Angefangen,
|
||||
} from '../api';
|
||||
import { leer, fehlerzeile, meldung } from '../util';
|
||||
import { leer, fehlerzeile, meldung, folgenname } from '../util';
|
||||
import { kachel, alsKachel, angefangenAlsKachel, reihe } from '../card';
|
||||
import { hero, alsHero, angefangenAlsHero, type Heroeintrag } from '../hero';
|
||||
import { spieler } from '../spieler';
|
||||
@@ -85,7 +85,9 @@ export function startseite(
|
||||
spieler({
|
||||
dateiId: e.fileId,
|
||||
titelId: e.id,
|
||||
name: e.subTitle ?? e.title,
|
||||
name: e.kind === 'episode'
|
||||
? folgenname(e.title, e.season, e.episode, e.subTitle)
|
||||
: e.title,
|
||||
ab: e.state.positionSeconds,
|
||||
laenge: e.durationSeconds ?? null,
|
||||
danach: () => void aufbauen(),
|
||||
|
||||
Reference in New Issue
Block a user