mirror of
https://github.com/yattee/yattee.git
synced 2024-12-23 14:03:41 +00:00
9cb0325503
Currently, we have a hard-coded list of resolutions. Since Invidious reports the actual resolution of a stream and does not hard-code them to a fixed value anymore, resolutions that are not in the list won’t be handled, and the stream cannot be played back. Instead of hard-coding even more resolutions (and inadvertently might not cover all), we revert the list back to a finite set of resolutions, the users can select from. All other resolutions are handled dynamically and compared to the existing set of defined resolutions when selecting the best stream for playback. Signed-off-by: Toni Förster <toni.foerster@gmail.com>
127 lines
3.9 KiB
Swift
127 lines
3.9 KiB
Swift
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], order: Array(Format.allCases.indices))
|
|
|
|
enum Format: String, CaseIterable, Identifiable, Defaults.Serializable {
|
|
case avc1
|
|
case stream
|
|
case webm
|
|
case mp4
|
|
case av1
|
|
case hls
|
|
|
|
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
|
|
case .stream:
|
|
return nil
|
|
case .webm:
|
|
return .webm
|
|
case .mp4:
|
|
return .mp4
|
|
case .av1:
|
|
return .av1
|
|
case .hls:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
var id = UUID().uuidString
|
|
|
|
var name: String?
|
|
var backend: PlayerBackendType
|
|
var resolution: ResolutionSetting
|
|
var formats: [Format]
|
|
var order: [Int]
|
|
var description: String {
|
|
if let name, !name.isEmpty { return name }
|
|
return "\(backend.label) - \(resolution.description) - \(formatsDescription)"
|
|
}
|
|
|
|
var formatsDescription: String {
|
|
switch formats.count {
|
|
case Format.allCases.count:
|
|
return "Any format".localized()
|
|
case 0:
|
|
return "No format selected".localized()
|
|
case 1 ... 3:
|
|
return formats.map(\.description).joined(separator: ", ")
|
|
default:
|
|
return String(format: "%@ formats".localized(), String(formats.count))
|
|
}
|
|
}
|
|
|
|
func isPreferred(_ stream: Stream) -> Bool {
|
|
if formats.contains(.hls), stream.kind == .hls {
|
|
return true
|
|
}
|
|
|
|
let defaultResolution = Stream.Resolution.custom(height: 720, refreshRate: 30)
|
|
let resolutionMatch = resolution.value ?? defaultResolution >= 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 else { return nil }
|
|
|
|
return [
|
|
"id": value.id,
|
|
"name": value.name ?? "",
|
|
"backend": value.backend.rawValue,
|
|
"resolution": value.resolution.rawValue,
|
|
"formats": value.formats.map(\.rawValue).joined(separator: Self.formatsSeparator),
|
|
"order": value.order.map { String($0) }.joined(separator: Self.formatsSeparator) // New line
|
|
]
|
|
}
|
|
|
|
func deserialize(_ object: Serializable?) -> Value? {
|
|
guard let 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) }
|
|
let order = (object["order"] ?? "").components(separatedBy: Self.formatsSeparator).compactMap { Int($0) }
|
|
|
|
return .init(id: id, name: name, backend: backend, resolution: resolution, formats: formats, order: order)
|
|
}
|
|
}
|