Erst für sich, dann mit der Bedienung
Die Acht-Sekunden-Frist war geraten, und sie war die falsche Antwort auf eine richtige Beobachtung. Ein Zeitgeber entscheidet für den Zuschauer, wann er genug gesehen hat; das kann er selbst besser. Jetzt gilt für Vorspann und Abspann, Web wie App, dieselbe Regel: eine neue Marke zeigt ihre Knöpfe von selbst, ohne dass man das Bild anfassen müsste. Sobald die Bedienung einmal eingeblendet war, gehören sie zu ihr und gehen mit ihr — ein- wie ausblenden. Eine neue Marke fängt wieder von vorn an; sie ist ein neues Angebot. **Der Übergang zählt, nicht der Zustand.** Beim Start liegt die Bedienung ohnehin da, und `zeigen()` läuft im Web bei jeder Mausbewegung. Beides als „einmal gezeigt" zu werten hiesse, dem Intro seinen Knopf zu nehmen, bevor es ihn je hatte. Gewertet wird deshalb nur der Sprung von verborgen nach sichtbar. Am Ende des Films bleiben die Knöpfe stehen: dort ist „Nächste Folge" die einzige noch offene Frage, und es gibt kein Bild mehr, das sie verdecken könnten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
820789b7f6
commit
ef667a9f39
@@ -52,16 +52,15 @@ struct PlayerView: View {
|
||||
@State private var trackPicker: TrackPickerSheet.Kind?
|
||||
@State private var showsQualityChoice = false
|
||||
@State private var hideTask: Task<Void, Never>?
|
||||
/// Ob die Marken-Knöpfe gerade von sich aus liegen.
|
||||
/// Ob die Marken-Knöpfe inzwischen an der Bedienung hängen.
|
||||
///
|
||||
/// **Eine Marke dauert Minuten, ein Angebot nicht.** „Intro
|
||||
/// überspringen" soll erscheinen, ohne dass man den Bildschirm
|
||||
/// antippen muss — aber ein Abspann läuft sechs Minuten, und so lange
|
||||
/// gehört kein Knopf über das Bild. Also hat er eine eigene Frist:
|
||||
/// einmal von selbst, danach nur noch zusammen mit der übrigen
|
||||
/// Bedienung.
|
||||
@State private var zeigtMarken = false
|
||||
@State private var markenTask: Task<Void, Never>?
|
||||
/// **Erst für sich, dann mit der Bedienung.** Eine neue Marke zeigt
|
||||
/// ihre Knöpfe von selbst — sonst müsste man erst das Bild antippen,
|
||||
/// um das Intro überspringen zu können, und dann hätte man die
|
||||
/// Bedienung ohnehin. Sobald man sie aber einmal eingeblendet hat,
|
||||
/// gehören die Knöpfe dazu und gehen mit ihr. Keine Frist, kein
|
||||
/// Zeitgeber: der Zuschauer sagt selbst, wann er genug gesehen hat.
|
||||
@State private var markenGebunden = false
|
||||
@State private var scrubbing: Double?
|
||||
/// Welche Richtung gerade als Hinweis eingeblendet ist.
|
||||
@State private var seekHint: Double?
|
||||
@@ -372,8 +371,16 @@ struct PlayerView: View {
|
||||
.environment(settings)
|
||||
.environment(\.flaroAccent, accent)
|
||||
}
|
||||
.onChange(of: model.activeMarker?.id, initial: true) { _, marke in
|
||||
markenFristSetzen(marke)
|
||||
// Eine neue Marke ist ein neues Angebot: sie steht wieder für sich,
|
||||
// bis die Bedienung das nächste Mal auftaucht.
|
||||
.onChange(of: model.activeMarker?.id, initial: true) { _, _ in
|
||||
markenGebunden = false
|
||||
}
|
||||
// **Der Übergang zählt, nicht der Zustand.** Beim Start liegt die
|
||||
// Bedienung ohnehin da; das als „einmal gezeigt" zu werten hiesse,
|
||||
// dem Intro seinen Knopf zu nehmen, bevor es ihn je hatte.
|
||||
.onChange(of: showsControls) { vorher, jetzt in
|
||||
if !vorher && jetzt { markenGebunden = true }
|
||||
}
|
||||
.onChange(of: showsQualityChoice) { _, isShown in
|
||||
// Nach dem Schließen die Bedienelemente stehen lassen und die
|
||||
@@ -814,43 +821,17 @@ struct PlayerView: View {
|
||||
}
|
||||
.animation(.snappy, value: model.activeMarker?.id)
|
||||
.animation(.snappy, value: model.hasReachedEnd)
|
||||
.animation(.easeIn(duration: 0.28), value: zeigtMarken)
|
||||
.animation(.easeIn(duration: 0.28), value: markenGebunden)
|
||||
}
|
||||
|
||||
/// **Dieselbe Frist für Vorspann und Abspann.**
|
||||
/// **Dieselbe Regel für Vorspann und Abspann, Web wie App.**
|
||||
///
|
||||
/// Der Abspann hatte kurz eine Ausnahme: die Knöpfe blieben dort
|
||||
/// stehen, solange er lief. Das war die Antwort auf „beim Abspann
|
||||
/// kommt kein Knopf" — und die Frage war falsch gestellt: es kam
|
||||
/// keiner, weil dem Staffelfinale die Marke fehlte, nicht weil er zu
|
||||
/// früh ging. Die Ausnahme löste also nichts und liess dafür einen
|
||||
/// Knopf sechs Minuten über dem Bild stehen.
|
||||
///
|
||||
/// Am Ende des Films bleiben sie trotzdem: dort ist „Nächste Folge"
|
||||
/// die einzige Frage, die noch offen ist, und es gibt kein Bild mehr,
|
||||
/// das sie verdecken könnten.
|
||||
/// Sichtbar, solange die Knöpfe noch für sich stehen; danach genau
|
||||
/// dann, wenn die Bedienung da ist. Am Ende des Films bleiben sie
|
||||
/// trotzdem: dort ist „Nächste Folge" die einzige noch offene Frage,
|
||||
/// und es gibt kein Bild mehr, das sie verdecken könnten.
|
||||
private func markenSichtbar(_ model: PlayerModel) -> Bool {
|
||||
model.hasReachedEnd || showsControls || zeigtMarken
|
||||
}
|
||||
|
||||
/// Eine neue Marke zeigt ihre Knöpfe von selbst — für acht Sekunden.
|
||||
///
|
||||
/// Lang genug, um sie zu bemerken und zu treffen, kurz genug, dass sie
|
||||
/// nicht das halbe Intro überdecken. Wer später doch überspringen will,
|
||||
/// tippt einmal: dann liegt die Bedienung ohnehin wieder da, und die
|
||||
/// Knöpfe mit ihr.
|
||||
private func markenFristSetzen(_ marke: Int?) {
|
||||
markenTask?.cancel()
|
||||
guard marke != nil else {
|
||||
zeigtMarken = false
|
||||
return
|
||||
}
|
||||
zeigtMarken = true
|
||||
markenTask = Task {
|
||||
try? await Task.sleep(for: .seconds(8))
|
||||
guard !Task.isCancelled else { return }
|
||||
zeigtMarken = false
|
||||
}
|
||||
model.hasReachedEnd || showsControls || !markenGebunden
|
||||
}
|
||||
|
||||
/// Die Abdeckung während des Umstellens.
|
||||
|
||||
+23
-14
@@ -381,8 +381,8 @@ export function spieler(anfrage: Spieleranfrage): void {
|
||||
//
|
||||
// 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.
|
||||
// gibt, ist keiner. Als eigene Schicht über dem Bild erscheint er von
|
||||
// selbst — und hängt sich an die Bedienung, sobald die einmal da war.
|
||||
// **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.
|
||||
@@ -659,7 +659,8 @@ export function spieler(anfrage: Spieleranfrage): void {
|
||||
// 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.
|
||||
// rechts unten, und erscheint von selbst — bis man die Bedienung einmal
|
||||
// eingeblendet hat, danach kommt und geht er mit ihr.
|
||||
bedienung.append(zeitleiste, unten);
|
||||
|
||||
// **Beide Nachbarn gleich zu Beginn holen.** Zwei kleine Anfragen, einmal
|
||||
@@ -841,7 +842,15 @@ export function spieler(anfrage: Spieleranfrage): void {
|
||||
* Klick mitten im Drücken zu verlieren.
|
||||
*/
|
||||
let markenstandJetzt = '';
|
||||
let markenAb = 0;
|
||||
/** Ob die Marken-Knöpfe inzwischen an der Bedienung hängen.
|
||||
*
|
||||
* **Erst für sich, dann mit der Bedienung.** Eine neue Marke zeigt ihre
|
||||
* Knöpfe von selbst — sonst müsste man erst das Bild anfassen, um das
|
||||
* Intro überspringen zu können, und dann läge die Bedienung ohnehin da.
|
||||
* Sobald man sie aber einmal eingeblendet hat, gehören die Knöpfe dazu
|
||||
* und gehen mit ihr. Keine Frist, kein Zeitgeber: der Zuschauer sagt
|
||||
* selbst, wann er genug gesehen hat. */
|
||||
let markenGebunden = false;
|
||||
let markeLaeuft = false;
|
||||
|
||||
function markenstand(jetzt: number): void {
|
||||
@@ -850,19 +859,14 @@ export function spieler(anfrage: Spieleranfrage): void {
|
||||
const weiter = abspann && naechste?.files[0] != null;
|
||||
const kennung = `${drin?.startSeconds ?? ''}|${weiter}`;
|
||||
|
||||
if (kennung !== markenstandJetzt) markenAb = performance.now();
|
||||
// Eine neue Marke ist ein neues Angebot: sie steht wieder für sich,
|
||||
// bis die Bedienung das nächste Mal auftaucht.
|
||||
if (kennung !== markenstandJetzt) markenGebunden = false;
|
||||
|
||||
// **Ein Intro ist ein Angebot, ein Abspann eine Frage.**
|
||||
//
|
||||
// „Intro überspringen" kommt von selbst und geht nach acht Sekunden
|
||||
// wieder: wer es nicht genommen hat, will zusehen, und dann gehört
|
||||
// kein Knopf über das Bild. Im Abspann ist es umgekehrt. „Nächste
|
||||
// Folge" ist das, wofür man dort überhaupt hinsieht — der bleibt
|
||||
// stehen, bis die Folge zu Ende ist.
|
||||
const bleibt = abspann || performance.now() - markenAb < 8000;
|
||||
markeLaeuft = drin != null;
|
||||
markenreihe.classList.toggle(
|
||||
'hidden', !drin || (!bleibt && schicht.classList.contains('pl-still')));
|
||||
'hidden',
|
||||
!drin || (markenGebunden && schicht.classList.contains('pl-still')));
|
||||
|
||||
if (!drin || kennung === markenstandJetzt) return;
|
||||
markenstandJetzt = kennung;
|
||||
@@ -1350,6 +1354,11 @@ export function spieler(anfrage: Spieleranfrage): void {
|
||||
|
||||
let verstecker: number | undefined;
|
||||
function zeigen(): void {
|
||||
// **Der Übergang zählt, nicht der Zustand.** `zeigen` läuft bei jeder
|
||||
// Mausbewegung, auch wenn die Bedienung längst da ist. Das jedes Mal
|
||||
// als „einmal gezeigt" zu werten hiesse, dem Intro seinen Knopf zu
|
||||
// nehmen, bevor es ihn je hatte.
|
||||
if (schicht.classList.contains('pl-still')) markenGebunden = true;
|
||||
schicht.classList.remove('pl-still');
|
||||
// Im Stehen rechnet `markenstand` nicht weiter: wer pausiert im Intro
|
||||
// die Maus bewegt, bekäme die Bedienung zurück und den Knopf nicht.
|
||||
|
||||
Reference in New Issue
Block a user