mirror of
https://github.com/yattee/yattee.git
synced 2025-11-20 17:02:21 +00:00
This commit addresses crashes caused by accessing nil format values on streams: - QualityProfile.swift: Add guard check for stream.format to prevent nil access crash - MPVBackend.swift: Add nil check in canPlay method before comparing format - PlayerStreams.swift: Add nil check before comparing format in asset processing The crashes occurred when stream.format was nil and accessed as an implicitly unwrapped optional, causing "Unexpectedly found nil while implicitly unwrapping an Optional value" errors.
137 lines
4.2 KiB
Swift
137 lines
4.2 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
|
|
}
|
|
|
|
// Safety check: Ensure stream has a resolution
|
|
guard let streamResolution = stream.resolution else {
|
|
return false
|
|
}
|
|
|
|
// Safety check: Ensure stream has a format
|
|
guard let streamFormat = stream.format else {
|
|
return false
|
|
}
|
|
|
|
let defaultResolution = Stream.Resolution.custom(height: 720, refreshRate: 30)
|
|
let resolutionMatch = resolution.value ?? defaultResolution >= streamResolution
|
|
|
|
if resolutionMatch, formats.contains(.stream), stream.kind == .stream {
|
|
return true
|
|
}
|
|
|
|
let formatMatch = formats.compactMap(\.streamFormat).contains(streamFormat)
|
|
|
|
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)
|
|
}
|
|
}
|