import Foundation import Observation import PlexKit /// Was abgespielt werden soll. struct PlaybackRequest: Identifiable, Hashable { let id = UUID() let ratingKey: String /// Für die Titelzeile, solange die Metadaten noch geladen werden. let title: String /// Welche Fassung, wenn ein Titel in mehreren vorliegt. let mediaIndex: Int /// Ab wo — aus dem gemeldeten Fortschritt. let startTime: Duration static func == (lhs: Self, rhs: Self) -> Bool { lhs.id == rhs.id } func hash(into hasher: inout Hasher) { hasher.combine(id) } } /// Nimmt Abspielwünsche von überall entgegen. /// /// Ohne diese Stelle bräuchte jeder Bildschirm, von dem aus sich etwas starten /// lässt — Aufmacher, Reihe, Detailseite, Suche — seinen eigenen /// Vollbild-Übergang. Vier Kopien desselben Ablaufs, die auseinanderdriften. @MainActor @Observable final class PlaybackCoordinator { var request: PlaybackRequest? func play(_ item: PlexItem, mediaIndex: Int = 0, resuming: Bool = true) { request = PlaybackRequest( ratingKey: item.ratingKey, title: item.playbackTitle, mediaIndex: mediaIndex, startTime: resuming ? item.resumeTime : .zero ) } func dismiss() { request = nil } } extension PlexItem { /// Wie der Titel im Player heißt. /// /// Bei Folgen ist der eigene Titel allein nutzlos — „Die erste Folge" /// sagt nichts darüber, welche Serie läuft. var playbackTitle: String { if kind == .episode, let series = grandparentTitle { return "\(series) · \(title)" } return title } /// Ab welcher Stelle fortgesetzt wird. var resumeTime: Duration { guard let viewOffset, viewOffset > 0 else { return .zero } return .milliseconds(viewOffset) } /// Die Beschriftung unter einer Kachel. var cardSubtitle: String? { switch kind { case .episode: if let season = parentIndex, let episode = index { return String(localized: "episode.short \(season) \(episode)") } return grandparentTitle case .movie: return year.map(String.init) default: return year.map(String.init) } } }