Ohne Server stand man vor der Tür, mit dem Film in der Tasche

Fiel der Server aus, endete der Start bei einem Fehlerbildschirm mit
zwei Knöpfen: „Erneut versuchen" und „Abmelden". Beide nützen im
Flugzeug nichts — und dahinter lagen die Dateien, die man genau für
diesen Fall heruntergeladen hatte.

Jetzt geht die App trotzdem auf: Token und Konto liegen auf dem Gerät,
die Mediathekenliste auch, und die Startseite kommt neuerdings aus
demselben Speicher. Oben die Downloads, darunter der Stand vom letzten
Mal, ausgegraut, mit einem Satz, der sagt warum. Die Detailseite nimmt
den Titel vom Gerät, wenn der Server schweigt, und der Abspielknopf
bleibt an, weil die Datei ja da ist.

Und was man offline geschaut hat, zählt: Fortschritt, den der Server
nicht annimmt, wird zurückgelegt und nachgereicht, sobald er wieder
antwortet. Dafür sagt `reportProgress` jetzt, ob die Meldung ankam.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-12 19:41:22 +02:00
co-authored by Claude Opus 5
parent 77593105de
commit 8f0568cb6c
17 changed files with 613 additions and 39 deletions
@@ -6,9 +6,13 @@ import Foundation
/// Anmeldung, Jellyfins Verwaltung, die Eigenheiten des Programmführers —
/// steht **nicht** hier, sondern als eigene Fähigkeit. Ein gemeinsamer Nenner,
/// der alles umfasst, wäre kein Nenner mehr.
public protocol MediaBackend: Sendable {
/// Wie der Server heißt, so wie er sich selbst nennt.
nonisolated var serverName: String { get }
/// Wer Sehfortschritt entgegennimmt.
///
/// **Ein Ausschnitt aus `MediaBackend`, und zwar genau der, den die
/// `Fortschrittspost` braucht.** Ohne ihn müsste jeder, der das Nachreichen
/// prüfen will, einen vollständigen Server nachbauen — vierzig Methoden für
/// die zwei, die zur Sache gehören.
public protocol Fortschrittsempfaenger: Sendable {
/// Wer dieser Server ist — für alles, was ihn über einen Start hinaus
/// wiedererkennen muss.
///
@@ -17,6 +21,24 @@ public protocol MediaBackend: Sendable {
/// beides überlebt und beim nächsten Start noch gleich lautet. Es ist
/// eine Kennung und geht nirgends an die Oberfläche.
nonisolated var identity: String { get }
/// Meldet, wo man gerade steht.
///
/// Wirft nicht: eine verlorene Meldung kostet ein paar Sekunden
/// Fortschritt, und dafür mitten im Film eine Fehlermeldung zu zeigen
/// wäre die schlechtere Hälfte des Tauschs.
///
/// **Gibt aber zurück, ob sie ankam.** Im Flugzeug geht jede einzelne
/// verloren, und am Ende stünde ein zur Hälfte gesehener Film auf dem
/// Server weiter bei null. Der Aufrufer legt sie dann beiseite und
/// meldet sie nach — siehe `Fortschrittspost`.
@discardableResult
func reportProgress(_ report: PlaybackReport) async -> Bool
}
public protocol MediaBackend: Sendable, Fortschrittsempfaenger {
/// Wie der Server heißt, so wie er sich selbst nennt.
nonisolated var serverName: String { get }
/// Was dieser Server darüber hinaus beherrscht.
nonisolated var capabilities: Set<BackendCapability> { get }
@@ -128,13 +150,6 @@ public protocol MediaBackend: Sendable {
/// niemand mehr zusieht.
func endPlayback(sessionID: String) async
/// Meldet, wo man gerade steht.
///
/// Wirft nicht: eine verlorene Meldung kostet ein paar Sekunden
/// Fortschritt, und dafür mitten im Film eine Fehlermeldung zu zeigen
/// wäre die schlechtere Hälfte des Tauschs.
func reportProgress(_ report: PlaybackReport) async
// MARK: - Zustand melden
func markWatched(id: String) async throws(MediaBackendError)