import Foundation import PlexKit /// Eine wählbare Ton- oder Untertitelspur. /// /// Bewusst getrennt von `PlexStream`: VLCKit und AVPlayer nummerieren ihre /// Spuren selbst, und die Nummern stimmen nicht mit denen von Plex überein. /// `engineIdentifier` hält deshalb fest, wie der jeweilige Motor die Spur /// nennt, während `plexStreamID` für den Server gedacht ist. public struct MediaTrack: Sendable, Equatable, Identifiable { public enum Kind: Sendable, Equatable { case audio, subtitle } public let id: String public let kind: Kind public let displayName: String public let languageCode: String? public let plexStreamID: Int? /// Die Nummer, unter der der laufende Motor die Spur führt. public let engineIdentifier: Int? public let isForced: Bool public init( id: String, kind: Kind, displayName: String, languageCode: String? = nil, plexStreamID: Int? = nil, engineIdentifier: Int? = nil, isForced: Bool = false ) { self.id = id self.kind = kind self.displayName = displayName self.languageCode = languageCode self.plexStreamID = plexStreamID self.engineIdentifier = engineIdentifier self.isForced = isForced } } extension MediaTrack { /// Baut eine Spur aus dem, was Plex gemeldet hat. /// /// `displayTitle` ist das, was Plex selbst in seinen Oberflächen zeigt /// (etwa „Deutsch (AC3 5.1)"). Fehlt es, wird aus Sprache und Codec etwas /// Brauchbares gebaut, statt „Spur 3" anzuzeigen. public init?(stream: PlexStream) { guard let kind = stream.kind else { return nil } let trackKind: Kind switch kind { case .audio: trackKind = .audio case .subtitle: trackKind = .subtitle case .video: return nil } let name = stream.displayTitle ?? stream.title ?? [stream.language, stream.codec?.uppercased()] .compactMap(\.self) .joined(separator: " · ") self.init( id: "plex-\(stream.id)", kind: trackKind, displayName: name.isEmpty ? "—" : name, languageCode: stream.languageCode, plexStreamID: stream.id, isForced: stream.isForced ) } }