import Defaults import Foundation struct QualityProfile: Hashable, Identifiable, Defaults.Serializable { static var bridge = QualityProfileBridge() static var defaultProfile = Self(id: "default", backend: .mpv, resolution: .hd720p60, formats: [.stream]) static var highQualityProfile = Self(id: "highQuality", backend: .mpv, resolution: .hd2160p60, formats: [.webm, .mp4, .av1, .avc1]) enum Format: String, CaseIterable, Identifiable, Defaults.Serializable { case hls case stream case mp4 case avc1 case av1 case webm var id: String { rawValue } var description: String { switch self { case .stream: return "Stream" case .webm: return "WebM" default: return rawValue.uppercased() } } var streamFormat: Stream.Format? { switch self { case .hls: return nil case .stream: return nil case .mp4: return .mp4 case .webm: return .webm case .avc1: return .avc1 case .av1: return .av1 } } } var id = UUID().uuidString var name: String? var backend: PlayerBackendType var resolution: ResolutionSetting var formats: [Format] var description: String { if let name = name, !name.isEmpty { return name } return "\(backend.label) - \(resolution.description) - \(formats.map(\.description).joined(separator: ", "))" } func isPreferred(_ stream: Stream) -> Bool { if formats.contains(.hls), stream.kind == .hls { return true } let resolutionMatch = !stream.resolution.isNil && resolution.value >= stream.resolution if resolutionMatch, formats.contains(.stream), stream.kind == .stream { return true } let formatMatch = formats.compactMap(\.streamFormat).contains(stream.format) return resolutionMatch && formatMatch } } struct QualityProfileBridge: Defaults.Bridge { static let formatsSeparator = "," typealias Value = QualityProfile typealias Serializable = [String: String] func serialize(_ value: Value?) -> Serializable? { guard let value = value else { return nil } return [ "id": value.id, "name": value.name ?? "", "backend": value.backend.rawValue, "resolution": value.resolution.rawValue, "formats": value.formats.map { $0.rawValue }.joined(separator: Self.formatsSeparator) ] } func deserialize(_ object: Serializable?) -> Value? { guard let object = object, let id = object["id"], let backend = PlayerBackendType(rawValue: object["backend"] ?? ""), let resolution = ResolutionSetting(rawValue: object["resolution"] ?? "") else { return nil } let name = object["name"] let formats = (object["formats"] ?? "").components(separatedBy: Self.formatsSeparator).compactMap { QualityProfile.Format(rawValue: $0) } return .init(id: id, name: name, backend: backend, resolution: resolution, formats: formats) } }