Wer zur Zeitleiste greift, sieht jetzt das Standbild der Stelle — im Web beim Überfahren, in der App beim Ziehen. **Erzeugt wird beim Abspielen, nicht auf Vorrat.** Ein Lauf über 453 Filme und 4213 Folgen wäre ein Abend ffmpeg und mehrere Gigabyte für Titel, die niemand anfasst. Startet jemand einen Titel, stellt sich die Datei hinten an; bis zum ersten Griff zur Leiste ist der Lauf durch. Gemessen an einer erzeugten Datei: 191 ms für 250 Sekunden Film, ein Zweistünder liegt damit bei etwa fünf Sekunden. **Ein ffmpeg-Aufruf, nicht siebenhundert.** `fps=1/10,scale=320:-2, tile=10x10` macht aus einem Durchlauf fertige Blätter zu hundert Kacheln. Bezahlbar ist das durch `-skip_frame nokey` — dieselbe Abkürzung wie beim Abtasten der Abspannhelligkeit: nur Keyframes dekodieren. Dass die Vorschau dadurch um zwei Sekunden danebenliegen kann, sieht niemand. **Der Spieler lädt ein Blatt und verschiebt danach nur den Ausschnitt.** Beim Fahren über die Leiste wird kein einziger Abruf mehr fällig. Die Rechnung Sekunde → Kachel steht an drei Stellen — Server, Web, App — und deshalb auch dreimal dieselbe; weicht eine ab, hält man die Vorschau für ein unscharfes Bild statt für einen Fehler. Neun Prüfungen decken die Ränder ab: Sekunde null, letzte Kachel eines Blattes, erste des nächsten, und hinter dem Ende. **Die Kachelmasse werden gemessen, nicht gerechnet.** `scale=320:-2` hält das Seitenverhältnis, ein Breitbildfilm ergibt also flachere Kacheln. Geraten wäre jede Kachel um ein paar Bildpunkte verschoben. Aufgeräumt wird mit dem Transcode-Cache: der Namensanfang trägt Kennung und Änderungszeit der Mediendatei, verwaiste Blätter sind daran ohne Tabelle erkennbar. Der Ordner taucht unter „Speicher" auf. **Und die Pfeiltasten spulen wieder.** Hatte die Zeitleiste den Fokus, überliess der Spieler die Pfeile dem Browser — der verschiebt `value` und löst `input` aus, aber `change` kommt bei Tastatureingabe nicht verlässlich, also wurde nie gesprungen. Die Ausnahme fällt weg; Pfeil heisst überall ±10 Sekunden. Nativ wären es ohnehin 100 Millisekunden gewesen, weil `step` auf 0,1 steht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
56 lines
2.1 KiB
Swift
56 lines
2.1 KiB
Swift
import Foundation
|
|
|
|
/// Wie die Vorschaubilder eines Titels zusammengesetzt sind.
|
|
///
|
|
/// **Dieselbe Rechnung wie im Server und im Web.** Dort heisst der Typ
|
|
/// genauso; die Kachel zu einer Sekunde wird an drei Stellen ausgerechnet,
|
|
/// und wenn eine davon abweicht, sieht man es nicht als Fehler — man hält
|
|
/// die Vorschau für ein ungenaues Bild.
|
|
///
|
|
/// **Die Kachelmasse stehen nicht fest.** Skaliert wird auf eine feste
|
|
/// Breite, die Höhe folgt dem Seitenverhältnis des Films. Deshalb misst
|
|
/// der Server sie und teilt sie mit, statt dass sie hier stünden.
|
|
public struct Vorschauplan: Sendable, Equatable, Decodable {
|
|
public let intervall: Double
|
|
public let spalten: Int
|
|
public let zeilen: Int
|
|
public let kachelBreite: Double
|
|
public let kachelHoehe: Double
|
|
public let blaetter: Int
|
|
|
|
public init(
|
|
intervall: Double, spalten: Int, zeilen: Int,
|
|
kachelBreite: Double, kachelHoehe: Double, blaetter: Int
|
|
) {
|
|
self.intervall = intervall
|
|
self.spalten = spalten
|
|
self.zeilen = zeilen
|
|
self.kachelBreite = kachelBreite
|
|
self.kachelHoehe = kachelHoehe
|
|
self.blaetter = blaetter
|
|
}
|
|
|
|
/// Wie eine Kachel adressiert wird: welches Blatt, und wo darin.
|
|
public struct Stelle: Sendable, Equatable {
|
|
public let blatt: Int
|
|
public let spalte: Int
|
|
public let zeile: Int
|
|
}
|
|
|
|
public var proBlatt: Int { spalten * zeilen }
|
|
|
|
/// Welche Kachel die Stelle zeigt — oder nichts.
|
|
///
|
|
/// **Hinter dem letzten Bild bleibt das letzte stehen.** Das Raster
|
|
/// endet dort, wo ffmpeg aufgehört hat; eine Sekunde darüber hinaus —
|
|
/// bei ungenauer Laufzeit durchaus möglich — soll die Vorschau nicht
|
|
/// verschwinden.
|
|
public func stelle(bei sekunde: Double) -> Stelle? {
|
|
guard blaetter > 0, spalten > 0, zeilen > 0, intervall > 0 else { return nil }
|
|
|
|
let nummer = min(Int(max(0, sekunde) / intervall), proBlatt * blaetter - 1)
|
|
let rest = nummer % proBlatt
|
|
return Stelle(blatt: nummer / proBlatt, spalte: rest % spalten, zeile: rest / spalten)
|
|
}
|
|
}
|