mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 05:23:41 +00:00
Merge pull request #807 from yattee/more-robust-resolution-handling
more robust resolution handling
This commit is contained in:
commit
4202b27c03
@ -515,7 +515,8 @@ final class PeerTubeAPI: Service, ObservableObject, VideosAPI {
|
||||
.dictionaryValue["files"]?.arrayValue.first?
|
||||
.dictionaryValue["fileUrl"]?.url
|
||||
{
|
||||
streams.append(SingleAssetStream(instance: account.instance, avAsset: AVURLAsset(url: fileURL), resolution: .hd720p30, kind: .stream))
|
||||
let resolution = Stream.Resolution.predefined(.hd720p30)
|
||||
streams.append(SingleAssetStream(instance: account.instance, avAsset: AVURLAsset(url: fileURL), resolution: resolution, kind: .stream))
|
||||
}
|
||||
|
||||
return streams
|
||||
|
@ -204,7 +204,7 @@ final class MPVBackend: PlayerBackend {
|
||||
typealias AreInIncreasingOrder = (Stream, Stream) -> Bool
|
||||
|
||||
func canPlay(_ stream: Stream) -> Bool {
|
||||
stream.resolution != .unknown && stream.format != .av1
|
||||
stream.format != .av1
|
||||
}
|
||||
|
||||
func playStream(_ stream: Stream, of video: Video, preservingTime: Bool, upgrading: Bool) {
|
||||
|
@ -153,8 +153,9 @@ extension PlayerBackend {
|
||||
// Filter out non-HLS streams and streams with resolution more than maxResolution
|
||||
let nonHLSStreams = streams.filter {
|
||||
let isHLS = $0.kind == .hls
|
||||
// Safely unwrap resolution and maxResolution.value to avoid crashes
|
||||
let isWithinResolution = ($0.resolution != nil && maxResolution.value != nil) ? $0.resolution! <= maxResolution.value! : false
|
||||
// Check if the stream's resolution is within the maximum allowed resolution
|
||||
let isWithinResolution = $0.resolution.map { $0 <= maxResolution.value } ?? false
|
||||
|
||||
logger.info("Stream ID: \($0.id) - Kind: \(String(describing: $0.kind)) - Resolution: \(String(describing: $0.resolution)) - Bitrate: \($0.bitrate ?? 0)")
|
||||
logger.info("Is HLS: \(isHLS), Is within resolution: \(isWithinResolution)")
|
||||
return !isHLS && isWithinResolution
|
||||
@ -188,8 +189,8 @@ extension PlayerBackend {
|
||||
}
|
||||
|
||||
let filteredStreams = adjustedStreams.filter { stream in
|
||||
// Safely unwrap resolution and maxResolution.value to avoid crashes
|
||||
let isWithinResolution = (stream.resolution != nil && maxResolution.value != nil) ? stream.resolution! <= maxResolution.value! : false
|
||||
// Check if the stream's resolution is within the maximum allowed resolution
|
||||
let isWithinResolution = stream.resolution <= maxResolution.value
|
||||
logger.info("Filtered stream ID: \(stream.id) - Is within max resolution: \(isWithinResolution)")
|
||||
return isWithinResolution
|
||||
}
|
||||
|
@ -76,7 +76,8 @@ struct QualityProfile: Hashable, Identifiable, Defaults.Serializable {
|
||||
return true
|
||||
}
|
||||
|
||||
let resolutionMatch = !stream.resolution.isNil && resolution.value >= stream.resolution
|
||||
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
|
||||
|
@ -4,292 +4,126 @@ import Foundation
|
||||
|
||||
// swiftlint:disable:next final_class
|
||||
class Stream: Equatable, Hashable, Identifiable {
|
||||
enum Resolution: String, CaseIterable, Comparable, Defaults.Serializable {
|
||||
// Some 16:19 and 16:10 resolutions are also used in 2:1 videos
|
||||
enum Resolution: Comparable, Codable, Defaults.Serializable {
|
||||
case predefined(PredefinedResolution)
|
||||
case custom(height: Int, refreshRate: Int)
|
||||
|
||||
enum PredefinedResolution: String, CaseIterable, Codable {
|
||||
// 8K UHD (16:9) Resolutions
|
||||
case hd4320p60
|
||||
case hd4320p50
|
||||
case hd4320p48
|
||||
case hd4320p30
|
||||
case hd4320p25
|
||||
case hd4320p24
|
||||
case hd4320p60, hd4320p30
|
||||
|
||||
// 5K (16:9) Resolutions
|
||||
case hd2560p60
|
||||
case hd2560p50
|
||||
case hd2560p48
|
||||
case hd2560p30
|
||||
case hd2560p25
|
||||
case hd2560p24
|
||||
// 4K UHD (16:9) Resolutions
|
||||
case hd2160p60, hd2160p30
|
||||
|
||||
// 2:1 Aspect Ratio (Univisium) Resolutions
|
||||
case hd2880p60
|
||||
case hd2880p50
|
||||
case hd2880p48
|
||||
case hd2880p30
|
||||
case hd2880p25
|
||||
case hd2880p24
|
||||
// 1440p (16:9) Resolutions
|
||||
case hd1440p60, hd1440p30
|
||||
|
||||
// 16:10 Resolutions
|
||||
case hd2400p60
|
||||
case hd2400p50
|
||||
case hd2400p48
|
||||
case hd2400p30
|
||||
case hd2400p25
|
||||
case hd2400p24
|
||||
// 1080p (Full HD, 16:9) Resolutions
|
||||
case hd1080p60, hd1080p30
|
||||
|
||||
// 16:9 Resolutions
|
||||
case hd2160p60
|
||||
case hd2160p50
|
||||
case hd2160p48
|
||||
case hd2160p30
|
||||
case hd2160p25
|
||||
case hd2160p24
|
||||
|
||||
// 16:10 Resolutions
|
||||
case hd1600p60
|
||||
case hd1600p50
|
||||
case hd1600p48
|
||||
case hd1600p30
|
||||
case hd1600p25
|
||||
case hd1600p24
|
||||
|
||||
// 16:9 Resolutions
|
||||
case hd1440p60
|
||||
case hd1440p50
|
||||
case hd1440p48
|
||||
case hd1440p30
|
||||
case hd1440p25
|
||||
case hd1440p24
|
||||
|
||||
// 16:10 Resolutions
|
||||
case hd1280p60
|
||||
case hd1280p50
|
||||
case hd1280p48
|
||||
case hd1280p30
|
||||
case hd1280p25
|
||||
case hd1280p24
|
||||
|
||||
// 16:10 Resolutions
|
||||
case hd1200p60
|
||||
case hd1200p50
|
||||
case hd1200p48
|
||||
case hd1200p30
|
||||
case hd1200p25
|
||||
case hd1200p24
|
||||
|
||||
// 16:9 Resolutions
|
||||
case hd1080p60
|
||||
case hd1080p50
|
||||
case hd1080p48
|
||||
case hd1080p30
|
||||
case hd1080p25
|
||||
case hd1080p24
|
||||
|
||||
// 16:10 Resolutions
|
||||
case hd1050p60
|
||||
case hd1050p50
|
||||
case hd1050p48
|
||||
case hd1050p30
|
||||
case hd1050p25
|
||||
case hd1050p24
|
||||
|
||||
// 16:9 Resolutions
|
||||
case hd960p60
|
||||
case hd960p50
|
||||
case hd960p48
|
||||
case hd960p30
|
||||
case hd960p25
|
||||
case hd960p24
|
||||
|
||||
// 16:10 Resolutions
|
||||
case hd900p60
|
||||
case hd900p50
|
||||
case hd900p48
|
||||
case hd900p30
|
||||
case hd900p25
|
||||
case hd900p24
|
||||
|
||||
// 16:10 Resolutions
|
||||
case hd800p60
|
||||
case hd800p50
|
||||
case hd800p48
|
||||
case hd800p30
|
||||
case hd800p25
|
||||
case hd800p24
|
||||
|
||||
// 16:9 Resolutions
|
||||
case hd720p60
|
||||
case hd720p50
|
||||
case hd720p48
|
||||
case hd720p30
|
||||
case hd720p25
|
||||
case hd720p24
|
||||
// 720p (HD, 16:9) Resolutions
|
||||
case hd720p60, hd720p30
|
||||
|
||||
// Standard Definition (SD) Resolutions
|
||||
case sd854p30
|
||||
case sd854p25
|
||||
case sd768p30
|
||||
case sd768p25
|
||||
case sd640p30
|
||||
case sd640p25
|
||||
case sd480p30
|
||||
case sd480p25
|
||||
|
||||
case sd428p30
|
||||
case sd428p25
|
||||
case sd426p30
|
||||
case sd426p25
|
||||
case sd360p30
|
||||
case sd360p25
|
||||
case sd320p30
|
||||
case sd320p25
|
||||
case sd256p30
|
||||
case sd256p25
|
||||
case sd240p30
|
||||
case sd240p25
|
||||
case sd214p30
|
||||
case sd214p25
|
||||
case sd144p30
|
||||
case sd144p25
|
||||
case sd128p30
|
||||
case sd128p25
|
||||
|
||||
case unknown
|
||||
}
|
||||
|
||||
var name: String {
|
||||
"\(height)p\(refreshRate != -1 && refreshRate != 30 ? ", \(refreshRate) fps" : "")"
|
||||
switch self {
|
||||
case let .predefined(predefined):
|
||||
return predefined.rawValue
|
||||
case let .custom(height, refreshRate):
|
||||
return "\(height)p\(refreshRate != 30 ? ", \(refreshRate) fps" : "")"
|
||||
}
|
||||
}
|
||||
|
||||
var height: Int {
|
||||
if self == .unknown {
|
||||
return -1
|
||||
switch self {
|
||||
case let .predefined(predefined):
|
||||
return predefined.height
|
||||
case let .custom(height, _):
|
||||
return height
|
||||
}
|
||||
|
||||
let resolutionPart = rawValue.components(separatedBy: "p").first!
|
||||
return Int(resolutionPart.components(separatedBy: CharacterSet.decimalDigits.inverted).joined())!
|
||||
}
|
||||
|
||||
var refreshRate: Int {
|
||||
if self == .unknown {
|
||||
return -1
|
||||
switch self {
|
||||
case let .predefined(predefined):
|
||||
return predefined.refreshRate
|
||||
case let .custom(_, refreshRate):
|
||||
return refreshRate
|
||||
}
|
||||
|
||||
let refreshRatePart = rawValue.components(separatedBy: "p")[1]
|
||||
|
||||
if refreshRatePart.isEmpty {
|
||||
return 30
|
||||
}
|
||||
|
||||
return Int(refreshRatePart.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()) ?? -1
|
||||
}
|
||||
|
||||
// These values are an approximation.
|
||||
// https://support.google.com/youtube/answer/1722171?hl=en#zippy=%2Cbitrate
|
||||
|
||||
var bitrate: Int {
|
||||
switch self {
|
||||
// 8K UHD (16:9) Resolutions
|
||||
case .hd4320p60, .hd4320p50, .hd4320p48, .hd4320p30, .hd4320p25, .hd4320p24:
|
||||
return 85_000_000 // 85 Mbit/s
|
||||
|
||||
// 5K (16:9) Resolutions
|
||||
case .hd2880p60, .hd2880p50, .hd2880p48, .hd2880p30, .hd2880p25, .hd2880p24:
|
||||
return 45_000_000 // 45 Mbit/s
|
||||
|
||||
// 2:1 Aspect Ratio (Univisium) Resolutions
|
||||
case .hd2560p60, .hd2560p50, .hd2560p48, .hd2560p30, .hd2560p25, .hd2560p24:
|
||||
return 30_000_000 // 30 Mbit/s
|
||||
|
||||
// 16:10 Resolutions
|
||||
case .hd2400p60, .hd2400p50, .hd2400p48, .hd2400p30, .hd2400p25, .hd2400p24:
|
||||
return 35_000_000 // 35 Mbit/s
|
||||
|
||||
// 4K UHD (16:9) Resolutions
|
||||
case .hd2160p60, .hd2160p50, .hd2160p48, .hd2160p30, .hd2160p25, .hd2160p24:
|
||||
return 56_000_000 // 56 Mbit/s
|
||||
|
||||
// 16:10 Resolutions
|
||||
case .hd1600p60, .hd1600p50, .hd1600p48, .hd1600p30, .hd1600p25, .hd1600p24:
|
||||
return 20_000_000 // 20 Mbit/s
|
||||
|
||||
// 1440p (16:9) Resolutions
|
||||
case .hd1440p60, .hd1440p50, .hd1440p48, .hd1440p30, .hd1440p25, .hd1440p24:
|
||||
return 24_000_000 // 24 Mbit/s
|
||||
|
||||
// 1280p (16:10) Resolutions
|
||||
case .hd1280p60, .hd1280p50, .hd1280p48, .hd1280p30, .hd1280p25, .hd1280p24:
|
||||
return 15_000_000 // 15 Mbit/s
|
||||
|
||||
// 1200p (16:10) Resolutions
|
||||
case .hd1200p60, .hd1200p50, .hd1200p48, .hd1200p30, .hd1200p25, .hd1200p24:
|
||||
return 18_000_000 // 18 Mbit/s
|
||||
|
||||
// 1080p (16:9) Resolutions
|
||||
case .hd1080p60, .hd1080p50, .hd1080p48, .hd1080p30, .hd1080p25, .hd1080p24:
|
||||
return 12_000_000 // 12 Mbit/s
|
||||
|
||||
// 1050p (16:10) Resolutions
|
||||
case .hd1050p60, .hd1050p50, .hd1050p48, .hd1050p30, .hd1050p25, .hd1050p24:
|
||||
return 10_000_000 // 10 Mbit/s
|
||||
|
||||
// 960p Resolutions
|
||||
case .hd960p60, .hd960p50, .hd960p48, .hd960p30, .hd960p25, .hd960p24:
|
||||
return 8_000_000 // 8 Mbit/s
|
||||
|
||||
// 900p (16:10) Resolutions
|
||||
case .hd900p60, .hd900p50, .hd900p48, .hd900p30, .hd900p25, .hd900p24:
|
||||
return 7_000_000 // 7 Mbit/s
|
||||
|
||||
// 800p (16:10) Resolutions
|
||||
case .hd800p60, .hd800p50, .hd800p48, .hd800p30, .hd800p25, .hd800p24:
|
||||
return 6_000_000 // 6 Mbit/s
|
||||
|
||||
// 720p (16:9) Resolutions
|
||||
case .hd720p60, .hd720p50, .hd720p48, .hd720p30, .hd720p25, .hd720p24:
|
||||
return 9_500_000 // 9.5 Mbit/s
|
||||
|
||||
// Standard Definition (SD) Resolutions
|
||||
case .sd854p30, .sd854p25, .sd768p30, .sd768p25, .sd640p30, .sd640p25:
|
||||
return 4_000_000 // 4 Mbit/s
|
||||
|
||||
case .sd480p30, .sd480p25:
|
||||
return 2_500_000 // 2.5 Mbit/s
|
||||
|
||||
case .sd428p30, .sd428p25, .sd426p30, .sd426p25:
|
||||
return 2_000_000 // 2 Mbit/s
|
||||
|
||||
case .sd360p30, .sd360p25:
|
||||
return 1_500_000 // 1.5 Mbit/s
|
||||
|
||||
case .sd320p30, .sd320p25:
|
||||
return 1_200_000 // 1.2 Mbit/s
|
||||
|
||||
case .sd256p30, .sd256p25, .sd240p30, .sd240p25:
|
||||
return 1_000_000 // 1 Mbit/s
|
||||
|
||||
case .sd214p30, .sd214p25:
|
||||
return 800_000 // 0.8 Mbit/s
|
||||
|
||||
case .sd144p30, .sd144p25:
|
||||
return 600_000 // 0.6 Mbit/s
|
||||
|
||||
case .sd128p30, .sd128p25:
|
||||
return 400_000 // 0.4 Mbit/s
|
||||
|
||||
case .unknown:
|
||||
return 0
|
||||
case let .predefined(predefined):
|
||||
return predefined.bitrate
|
||||
case let .custom(height, refreshRate):
|
||||
// Find the closest predefined resolution based on height and refresh rate
|
||||
let closestPredefined = Stream.Resolution.PredefinedResolution.allCases.min {
|
||||
abs($0.height - height) + abs($0.refreshRate - refreshRate) <
|
||||
abs($1.height - height) + abs($1.refreshRate - refreshRate)
|
||||
}
|
||||
// Return the bitrate of the closest predefined resolution or a default bitrate if no close match is found
|
||||
return closestPredefined?.bitrate ?? 5_000_000
|
||||
}
|
||||
}
|
||||
|
||||
static func from(resolution: String, fps: Int? = nil) -> Self {
|
||||
allCases.first { $0.rawValue.contains(resolution) && $0.refreshRate == (fps ?? 30) } ?? .unknown
|
||||
if let predefined = PredefinedResolution(rawValue: resolution) {
|
||||
return .predefined(predefined)
|
||||
}
|
||||
|
||||
// Attempt to parse height and refresh rate
|
||||
if let height = Int(resolution.components(separatedBy: "p").first ?? ""), height > 0 {
|
||||
let refreshRate = fps ?? 30
|
||||
return .custom(height: height, refreshRate: refreshRate)
|
||||
}
|
||||
|
||||
// Default behavior if parsing fails
|
||||
return .custom(height: 720, refreshRate: 30)
|
||||
}
|
||||
|
||||
static func < (lhs: Self, rhs: Self) -> Bool {
|
||||
lhs.height == rhs.height ? (lhs.refreshRate < rhs.refreshRate) : (lhs.height < rhs.height)
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case predefined
|
||||
case custom
|
||||
case height
|
||||
case refreshRate
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
if let predefinedValue = try? container.decode(PredefinedResolution.self, forKey: .predefined) {
|
||||
self = .predefined(predefinedValue)
|
||||
} else if let height = try? container.decode(Int.self, forKey: .height),
|
||||
let refreshRate = try? container.decode(Int.self, forKey: .refreshRate)
|
||||
{
|
||||
self = .custom(height: height, refreshRate: refreshRate)
|
||||
} else {
|
||||
// Set default resolution to 720p 30 if decoding fails
|
||||
self = .custom(height: 720, refreshRate: 30)
|
||||
}
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
switch self {
|
||||
case let .predefined(predefinedValue):
|
||||
try container.encode(predefinedValue, forKey: .predefined)
|
||||
case let .custom(height, refreshRate):
|
||||
try container.encode(height, forKey: .height)
|
||||
try container.encode(refreshRate, forKey: .refreshRate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Kind: String, Comparable {
|
||||
@ -482,3 +316,97 @@ class Stream: Equatable, Hashable, Identifiable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Stream.Resolution.PredefinedResolution {
|
||||
var height: Int {
|
||||
switch self {
|
||||
// 8K UHD (16:9) Resolutions
|
||||
case .hd4320p60, .hd4320p30:
|
||||
return 4320
|
||||
|
||||
// 4K UHD (16:9) Resolutions
|
||||
case .hd2160p60, .hd2160p30:
|
||||
return 2160
|
||||
|
||||
// 1440p (16:9) Resolutions
|
||||
case .hd1440p60, .hd1440p30:
|
||||
return 1440
|
||||
|
||||
// 1080p (Full HD, 16:9) Resolutions
|
||||
case .hd1080p60, .hd1080p30:
|
||||
return 1080
|
||||
|
||||
// 720p (HD, 16:9) Resolutions
|
||||
case .hd720p60, .hd720p30:
|
||||
return 720
|
||||
|
||||
// Standard Definition (SD) Resolutions
|
||||
case .sd480p30:
|
||||
return 480
|
||||
|
||||
case .sd360p30:
|
||||
return 360
|
||||
|
||||
case .sd240p30:
|
||||
return 240
|
||||
|
||||
case .sd144p30:
|
||||
return 144
|
||||
}
|
||||
}
|
||||
|
||||
var refreshRate: Int {
|
||||
switch self {
|
||||
// 60 fps Resolutions
|
||||
case .hd4320p60, .hd2160p60, .hd1440p60, .hd1080p60, .hd720p60:
|
||||
return 60
|
||||
|
||||
// 30 fps Resolutions
|
||||
case .hd4320p30, .hd2160p30, .hd1440p30, .hd1080p30, .hd720p30,
|
||||
.sd480p30, .sd360p30, .sd240p30, .sd144p30:
|
||||
return 30
|
||||
}
|
||||
}
|
||||
|
||||
// These values are an approximation.
|
||||
// https://support.google.com/youtube/answer/1722171?hl=en#zippy=%2Cbitrate
|
||||
|
||||
var bitrate: Int {
|
||||
switch self {
|
||||
// 8K UHD (16:9) Resolutions
|
||||
case .hd4320p60:
|
||||
return 180_000_000 // Midpoint between 120 Mbps and 240 Mbps
|
||||
case .hd4320p30:
|
||||
return 120_000_000 // Midpoint between 80 Mbps and 160 Mbps
|
||||
// 4K UHD (16:9) Resolutions
|
||||
case .hd2160p60:
|
||||
return 60_500_000 // Midpoint between 53 Mbps and 68 Mbps
|
||||
case .hd2160p30:
|
||||
return 40_000_000 // Midpoint between 35 Mbps and 45 Mbps
|
||||
// 1440p (2K) Resolutions
|
||||
case .hd1440p60:
|
||||
return 24_000_000 // 24 Mbps
|
||||
case .hd1440p30:
|
||||
return 16_000_000 // 16 Mbps
|
||||
// 1080p (Full HD, 16:9) Resolutions
|
||||
case .hd1080p60:
|
||||
return 12_000_000 // 12 Mbps
|
||||
case .hd1080p30:
|
||||
return 8_000_000 // 8 Mbps
|
||||
// 720p (HD, 16:9) Resolutions
|
||||
case .hd720p60:
|
||||
return 7_500_000 // 7.5 Mbps
|
||||
case .hd720p30:
|
||||
return 5_000_000 // 5 Mbps
|
||||
// Standard Definition (SD) Resolutions
|
||||
case .sd480p30:
|
||||
return 2_500_000 // 2.5 Mbps
|
||||
case .sd360p30:
|
||||
return 1_000_000 // 1 Mbps
|
||||
case .sd240p30:
|
||||
return 1_000_000 // 1 Mbps
|
||||
case .sd144p30:
|
||||
return 600_000 // 0.6 Mbps
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -424,18 +424,34 @@ enum ResolutionSetting: String, CaseIterable, Defaults.Serializable {
|
||||
case sd240p30
|
||||
case sd144p30
|
||||
|
||||
var value: Stream.Resolution! {
|
||||
.init(rawValue: rawValue)
|
||||
var value: Stream.Resolution {
|
||||
if let predefined = Stream.Resolution.PredefinedResolution(rawValue: rawValue) {
|
||||
return .predefined(predefined)
|
||||
}
|
||||
// Provide a default value of 720p 30
|
||||
return .custom(height: 720, refreshRate: 30)
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .hd2160p60:
|
||||
return "4K, 60fps"
|
||||
case .hd2160p30:
|
||||
return "4K"
|
||||
let resolution = value
|
||||
let height = resolution.height
|
||||
let refreshRate = resolution.refreshRate
|
||||
|
||||
// Superscript labels
|
||||
let superscript4K = "⁴ᴷ"
|
||||
let superscriptHD = "ᴴᴰ"
|
||||
|
||||
// Special handling for specific resolutions
|
||||
switch height {
|
||||
case 2160:
|
||||
// 4K superscript after the refresh rate
|
||||
return refreshRate == 30 ? "2160p \(superscript4K)" : "2160p\(refreshRate) \(superscript4K)"
|
||||
case 1440, 1080:
|
||||
// HD superscript after the refresh rate
|
||||
return refreshRate == 30 ? "\(height)p \(superscriptHD)" : "\(height)p\(refreshRate) \(superscriptHD)"
|
||||
default:
|
||||
return value.name
|
||||
// Default formatting for other resolutions
|
||||
return refreshRate == 30 ? "\(height)p" : "\(height)p\(refreshRate)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -315,7 +315,9 @@ struct QualityProfileForm: View {
|
||||
func isResolutionDisabled(_ resolution: ResolutionSetting) -> Bool {
|
||||
guard backend == .appleAVPlayer else { return false }
|
||||
|
||||
return resolution.value > .hd720p30
|
||||
let hd720p30 = Stream.Resolution.predefined(.hd720p30)
|
||||
|
||||
return resolution.value > hd720p30
|
||||
}
|
||||
|
||||
func initializeForm() {
|
||||
|
Loading…
Reference in New Issue
Block a user