import Foundation import MediaKit /// Was in einem Spurenmenü steht — und was ein Umschalten kostet. /// /// **Warum es diesen Typ gibt.** Auf dem Fernseher sitzt die Spurwahl in /// Apples Transportleiste, und die nimmt `UIMenu`-Einträge entgegen. Die /// Abbildung von Spuren auf Einträge hat mit UIKit aber nichts zu tun: sie /// beantwortet, welche Spuren zur Wahl stehen, welche gerade gilt, und — das /// ist der Teil, der sonst nirgends steht — **ob das Umschalten Sekunden mit /// schwarzem Bild kostet**. /// /// Diese Regel lag bisher nur mitten im Vollzug von /// `PlayerModel.selectAudioStream` und war damit weder ablesbar noch prüfbar: /// auslösen lässt sie sich nur an einem echten Server mit einer Datei, die /// mehrere Tonspuren hat. Hier steht sie als Auskunft — und die Oberfläche /// kann sie **vorher** anzeigen, statt den Nutzer hinterher vor ein schwarzes /// Bild zu setzen. /// /// Liegt neben `AudioTrackChooser` und `DowngradeRule`, weil es dasselbe ist: /// eine Regel über die Wiedergabe, ohne Oberfläche und ohne Netz. public enum TrackMenu { public struct Entry: Equatable, Sendable, Identifiable { /// Der Index der Spur beim Server. `nil` heisst „Aus". public let id: Int? public let title: String public let isSelected: Bool /// Ob das Umschalten den Stream neu aufbaut. public let rebuilds: Bool public init(id: Int?, title: String, isSelected: Bool, rebuilds: Bool) { self.id = id self.title = title self.isSelected = isSelected self.rebuilds = rebuilds } } /// Die Tonspuren **der Datei**, nicht die des Players. /// /// Rechnet der Server um, kennt der Player genau eine Spur — die, für die /// er sich entschieden hat. Zur Wahl steht, was in der Datei liegt. /// /// - Parameter playerTrackCount: Wie viele Tonspuren der Player meldet. /// Weicht die Zahl ab, stimmt die Zuordnung über die Reihenfolge nicht, /// und es muss neu aufgebaut werden — lieber eine Unterbrechung als die /// falsche Sprache. public static func audio( from streams: [MediaStream], selected: MediaStream?, isDirectPlay: Bool, playerTrackCount: Int ) -> [Entry] { let billig = isDirectPlay && playerTrackCount == streams.count return streams.enumerated().map { stelle, stream in Entry( id: stream.index ?? stelle, title: stream.name, isSelected: stream == selected, rebuilds: !billig ) } } /// Die Untertitelspuren, mit „Aus" an erster Stelle. /// /// - Parameter offLabel: Wie „Aus" in der Sprache des Nutzers heisst. /// Als Argument, weil dieses Paket keinen Textkatalog der App kennt. /// - Parameter playerTrackLanguages: Die Sprachkürzel der Spuren, die der /// Player wirklich hat. Nur was sich darin abbilden lässt, schaltet ohne /// Neuaufbau um — sonst stünde der falsche Untertitel im Bild, und das /// ist schlimmer als eine kurze Unterbrechung. public static func subtitles( from streams: [MediaStream], selected: MediaStream?, isDirectPlay: Bool, playerTrackLanguages: [String?] = [], offLabel: String ) -> [Entry] { // „Aus" kostet nie einen Neuaufbau: abschalten kann der Player immer, // auch eine Spur, die er nie hatte. var einträge = [ Entry(id: nil, title: offLabel, isSelected: selected == nil, rebuilds: false) ] einträge += streams.enumerated().map { stelle, stream in let abbildbar = isDirectPlay && playerTrackLanguages.contains { LanguageCode.matches(stream.languageCode, $0) } return Entry( id: stream.index ?? stelle, title: stream.name, isSelected: stream == selected, rebuilds: !abbildbar ) } return einträge } }