2022-08-14 17:06:22 +00:00
|
|
|
import Defaults
|
|
|
|
import Foundation
|
|
|
|
|
|
|
|
struct QualityProfile: Hashable, Identifiable, Defaults.Serializable {
|
|
|
|
static var bridge = QualityProfileBridge()
|
2024-04-26 10:27:25 +00:00
|
|
|
static var defaultProfile = Self(id: "default", backend: .mpv, resolution: .hd720p60, formats: [.stream], order: Array(Format.allCases.indices))
|
2022-08-14 17:06:22 +00:00
|
|
|
|
|
|
|
enum Format: String, CaseIterable, Identifiable, Defaults.Serializable {
|
|
|
|
case avc1
|
2024-08-30 14:03:35 +00:00
|
|
|
case stream
|
2022-08-14 17:06:22 +00:00
|
|
|
case webm
|
2024-08-30 20:04:31 +00:00
|
|
|
case mp4
|
|
|
|
case av1
|
2024-08-30 14:03:35 +00:00
|
|
|
case hls
|
2022-08-14 17:06:22 +00:00
|
|
|
|
|
|
|
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 .avc1:
|
|
|
|
return .avc1
|
2024-08-30 20:04:31 +00:00
|
|
|
case .stream:
|
|
|
|
return nil
|
2024-04-26 10:27:25 +00:00
|
|
|
case .webm:
|
|
|
|
return .webm
|
2024-08-30 20:04:31 +00:00
|
|
|
case .mp4:
|
|
|
|
return .mp4
|
|
|
|
case .av1:
|
|
|
|
return .av1
|
2024-08-30 14:03:35 +00:00
|
|
|
case .hls:
|
|
|
|
return nil
|
2022-08-14 17:06:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var id = UUID().uuidString
|
|
|
|
|
|
|
|
var name: String?
|
|
|
|
var backend: PlayerBackendType
|
|
|
|
var resolution: ResolutionSetting
|
|
|
|
var formats: [Format]
|
2024-04-26 10:27:25 +00:00
|
|
|
var order: [Int]
|
2022-08-14 17:06:22 +00:00
|
|
|
var description: String {
|
2022-09-28 14:27:01 +00:00
|
|
|
if let name, !name.isEmpty { return name }
|
2022-08-16 22:33:23 +00:00
|
|
|
return "\(backend.label) - \(resolution.description) - \(formatsDescription)"
|
|
|
|
}
|
|
|
|
|
|
|
|
var formatsDescription: String {
|
2024-08-30 14:03:35 +00:00
|
|
|
switch formats.count {
|
|
|
|
case Format.allCases.count:
|
2022-09-27 14:44:15 +00:00
|
|
|
return "Any format".localized()
|
2024-08-30 14:03:35 +00:00
|
|
|
case 0:
|
|
|
|
return "No format selected".localized()
|
|
|
|
case 1 ... 3:
|
2022-08-16 22:33:23 +00:00
|
|
|
return formats.map(\.description).joined(separator: ", ")
|
2024-08-30 14:03:35 +00:00
|
|
|
default:
|
|
|
|
return String(format: "%@ formats".localized(), String(formats.count))
|
2022-08-16 22:33:23 +00:00
|
|
|
}
|
2022-08-14 17:06:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func isPreferred(_ stream: Stream) -> Bool {
|
|
|
|
if formats.contains(.hls), stream.kind == .hls {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-09-09 10:59:39 +00:00
|
|
|
let defaultResolution = Stream.Resolution.custom(height: 720, refreshRate: 30)
|
|
|
|
let resolutionMatch = resolution.value ?? defaultResolution >= stream.resolution
|
2022-08-14 17:06:22 +00:00
|
|
|
|
|
|
|
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? {
|
2022-09-28 14:27:01 +00:00
|
|
|
guard let value else { return nil }
|
2022-08-14 17:06:22 +00:00
|
|
|
|
|
|
|
return [
|
|
|
|
"id": value.id,
|
|
|
|
"name": value.name ?? "",
|
|
|
|
"backend": value.backend.rawValue,
|
|
|
|
"resolution": value.resolution.rawValue,
|
2024-08-18 12:46:51 +00:00
|
|
|
"formats": value.formats.map(\.rawValue).joined(separator: Self.formatsSeparator),
|
2024-04-26 10:27:25 +00:00
|
|
|
"order": value.order.map { String($0) }.joined(separator: Self.formatsSeparator) // New line
|
2022-08-14 17:06:22 +00:00
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
func deserialize(_ object: Serializable?) -> Value? {
|
2022-09-28 14:27:01 +00:00
|
|
|
guard let object,
|
2022-08-14 17:06:22 +00:00
|
|
|
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) }
|
2024-04-26 10:27:25 +00:00
|
|
|
let order = (object["order"] ?? "").components(separatedBy: Self.formatsSeparator).compactMap { Int($0) }
|
2022-08-14 17:06:22 +00:00
|
|
|
|
2024-04-26 10:27:25 +00:00
|
|
|
return .init(id: id, name: name, backend: backend, resolution: resolution, formats: formats, order: order)
|
2022-08-14 17:06:22 +00:00
|
|
|
}
|
|
|
|
}
|