import Foundation /// Woher das Bild kommt. public struct PlaybackSource: Sendable, Equatable { public enum Origin: Sendable, Equatable { /// Vom Server — per DirectPlay oder als HLS-Strom des Transcoders. case remote /// Aus dem Download-Ordner. case local } public let url: URL public let origin: Origin /// Ab wo gestartet werden soll. Kommt aus dem zuletzt gemeldeten /// Fortschritt; `.zero` heißt von vorn. public let startTime: Duration public init(url: URL, origin: Origin, startTime: Duration = .zero) { self.url = url self.origin = origin self.startTime = startTime } } /// Was ein Motor von sich aus meldet. /// /// Ein Strom statt beobachtbarer Eigenschaften: AVPlayer meldet über KVO, /// VLCKit über Delegat-Rückrufe. Beide in dieselbe Ereignisfolge zu /// übersetzen hält den Unterschied an genau einer Stelle je Motor — statt in /// jeder View, die den Zustand anzeigt. public enum PlaybackEvent: Sendable, Equatable { case stateChanged(PlaybackState) case timeChanged(Duration) case durationResolved(Duration) case tracksResolved(audio: [MediaTrack], subtitle: [MediaTrack]) case reachedEnd } /// Der gemeinsame Nenner von AVPlayer und VLCKit. /// /// Alles, was die Oberfläche zum Steuern braucht — und nichts darüber hinaus. /// Was hier nicht steht, gibt es für die Views nicht; dadurch bleibt der /// Austausch des Motors unsichtbar. @MainActor public protocol VideoPlayerEngine: AnyObject { var events: AsyncStream { get } var state: PlaybackState { get } var currentTime: Duration { get } var duration: Duration? { get } /// Ob dieser Motor Bild-in-Bild kann. Steuert, ob der Knopf überhaupt /// erscheint — ein Knopf, der bei manchen Dateien nichts tut, ist /// schlimmer als keiner. var supportsPictureInPicture: Bool { get } func load(_ source: PlaybackSource) async throws(PlaybackFailure) func play() func pause() func seek(to time: Duration) async func setRate(_ rate: Double) var audioTracks: [MediaTrack] { get } var subtitleTracks: [MediaTrack] { get } func selectAudioTrack(_ track: MediaTrack?) /// `nil` schaltet die Untertitel ab. func selectSubtitleTrack(_ track: MediaTrack?) /// Gibt Puffer und Dekoder frei. Muss beim Verlassen des Players laufen, /// sonst behält besonders VLCKit den Speicher. func teardown() }