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>
98 lines
3.4 KiB
Swift
98 lines
3.4 KiB
Swift
import Foundation
|
|
|
|
/// Eine Mediathek auf dem Server.
|
|
public struct MediaLibrary: Sendable, Equatable, Identifiable, Codable {
|
|
public enum Kind: String, Sendable, Equatable, Codable {
|
|
case movie, show, music, photo, liveTV, other
|
|
}
|
|
|
|
public let id: String
|
|
public let title: String
|
|
public let kind: Kind
|
|
public let thumb: String?
|
|
public let art: String?
|
|
|
|
public init(
|
|
id: String,
|
|
title: String,
|
|
kind: Kind,
|
|
thumb: String? = nil,
|
|
art: String? = nil
|
|
) {
|
|
self.id = id
|
|
self.title = title
|
|
self.kind = kind
|
|
self.thumb = thumb
|
|
self.art = art
|
|
}
|
|
|
|
/// Ob die App mit dieser Mediathek etwas anfangen kann.
|
|
///
|
|
/// Musik und Fotos sind bewusst außen vor: sie bräuchten eigene Ansichten
|
|
/// und eigene Wiedergabe, und eine halbherzige Musikansicht ist schlechter
|
|
/// als gar keine. Live-TV hat einen eigenen Bildschirm und gehört nicht
|
|
/// noch einmal in die Mediathekenliste.
|
|
///
|
|
/// **`other` zählt dazu.** Hier stand vorher `movie || show`, und alles
|
|
/// Übrige fiel wortlos heraus — auch eine Mediathek mit gemischtem
|
|
/// Inhalt, die Jellyfin ohne `CollectionType` meldet. Wer Filme und
|
|
/// Serien nicht getrennt hat, sah sie nirgends: nicht in der
|
|
/// Seitenleiste, nicht auf der Startseite, nirgends eine Meldung. Eine
|
|
/// unbekannte Art ist kein Grund, etwas zu verschweigen — sie ist nur
|
|
/// ein Grund, nichts über den Inhalt anzunehmen.
|
|
public var isSupported: Bool {
|
|
switch kind {
|
|
case .movie, .show, .other: true
|
|
case .music, .photo, .liveTV: false
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Eine Reihe auf der Startseite oder in den Suchergebnissen.
|
|
public struct MediaRowGroup: Sendable, Equatable, Identifiable, Codable {
|
|
/// Eine stabile Kennung für diese Reihe.
|
|
///
|
|
/// Sie entscheidet, welche Reihe der Nutzer ausgeblendet oder verschoben
|
|
/// hat — und muss deshalb über Abrufe hinweg gleich bleiben, auch wenn
|
|
/// sich der angezeigte Titel ändert.
|
|
public let id: String
|
|
public let title: String
|
|
/// Der Pfad, unter dem es „alles davon" gäbe.
|
|
public let moreKey: String?
|
|
/// Aus welcher Mediathek diese Reihe stammt.
|
|
///
|
|
/// **`nil` heisst: lässt sich nicht zuordnen**, nicht „keine". „Neu in
|
|
/// Filme" gehört zu einer Mediathek; „Weiterschauen" und „Favoriten"
|
|
/// gehen quer durch alle, und der Server sagt an keiner Stelle, aus
|
|
/// welcher ein einzelner angefangener Titel kommt.
|
|
///
|
|
/// Gebraucht wird es für die Auswahl des Aufmachers: wer „Musikvideos"
|
|
/// dort nicht sehen will, muss die Reihe benennen können, die sie
|
|
/// mitbringt.
|
|
public let libraryID: String?
|
|
public let items: [MediaItem]
|
|
|
|
public init(
|
|
id: String,
|
|
title: String,
|
|
moreKey: String? = nil,
|
|
libraryID: String? = nil,
|
|
items: [MediaItem] = []
|
|
) {
|
|
self.id = id
|
|
self.title = title
|
|
self.moreKey = moreKey
|
|
self.libraryID = libraryID
|
|
self.items = items
|
|
}
|
|
|
|
/// Ob diese Reihe das Weiterschauen ist.
|
|
///
|
|
/// Sie wird anders dargestellt: dort zeigt das Szenenbild, wo man
|
|
/// aufgehört hat, während man sonst nach Plakaten sucht.
|
|
public var isContinueWatching: Bool {
|
|
let lowered = id.lowercased()
|
|
return lowered.contains("continue") || lowered.contains("resume") || lowered.contains("nextup")
|
|
}
|
|
}
|