mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 21:43:41 +00:00
Improved stream resolution handling
Invidious now reports the actual resolution and doesn’t hardcode them anymore. See: https://github.com/iv-org/invidious/pull/4586 - Extended the list of possible resolutions in the StreamModel - trigger videoLoadFailureHandler if no streams are available - more logging for backend.bestPlayable Signed-off-by: Toni Förster <toni.foerster@gmail.com>
This commit is contained in:
parent
0b01adf6eb
commit
a45522f710
@ -1,6 +1,7 @@
|
|||||||
import CoreMedia
|
import CoreMedia
|
||||||
import Defaults
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Logging
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
#endif
|
#endif
|
||||||
@ -75,6 +76,10 @@ protocol PlayerBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension PlayerBackend {
|
extension PlayerBackend {
|
||||||
|
var logger: Logger {
|
||||||
|
return Logger(label: "stream.yattee.player.backend")
|
||||||
|
}
|
||||||
|
|
||||||
func seek(to time: CMTime, seekType: SeekType, completionHandler: ((Bool) -> Void)? = nil) {
|
func seek(to time: CMTime, seekType: SeekType, completionHandler: ((Bool) -> Void)? = nil) {
|
||||||
model.seek.registerSeek(at: time, type: seekType, restore: currentTime)
|
model.seek.registerSeek(at: time, type: seekType, restore: currentTime)
|
||||||
seek(to: time, seekType: seekType, completionHandler: completionHandler)
|
seek(to: time, seekType: seekType, completionHandler: completionHandler)
|
||||||
@ -140,55 +145,87 @@ extension PlayerBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func bestPlayable(_ streams: [Stream], maxResolution: ResolutionSetting, formatOrder: [QualityProfile.Format]) -> Stream? {
|
func bestPlayable(_ streams: [Stream], maxResolution: ResolutionSetting, formatOrder: [QualityProfile.Format]) -> Stream? {
|
||||||
// filter out non-HLS streams and streams with resolution more than maxResolution
|
logger.info("Starting bestPlayable function")
|
||||||
let nonHLSStreams = streams.filter {
|
logger.info("Total streams received: \(streams.count)")
|
||||||
$0.kind != .hls && $0.resolution <= maxResolution.value
|
logger.info("Max resolution allowed: \(String(describing: maxResolution.value))")
|
||||||
}
|
logger.info("Format order: \(formatOrder)")
|
||||||
|
|
||||||
// find max resolution and bitrate from non-HLS streams
|
// Filter out non-HLS streams and streams with resolution more than maxResolution
|
||||||
|
let nonHLSStreams = streams.filter {
|
||||||
|
let isHLS = $0.kind == .hls
|
||||||
|
let isWithinResolution = $0.resolution <= maxResolution.value
|
||||||
|
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
|
||||||
|
}
|
||||||
|
logger.info("Non-HLS streams after filtering: \(nonHLSStreams.count)")
|
||||||
|
|
||||||
|
// Find max resolution and bitrate from non-HLS streams
|
||||||
let bestResolutionStream = nonHLSStreams.max { $0.resolution < $1.resolution }
|
let bestResolutionStream = nonHLSStreams.max { $0.resolution < $1.resolution }
|
||||||
let bestBitrateStream = nonHLSStreams.max { $0.bitrate ?? 0 < $1.bitrate ?? 0 }
|
let bestBitrateStream = nonHLSStreams.max { $0.bitrate ?? 0 < $1.bitrate ?? 0 }
|
||||||
|
|
||||||
|
logger.info("Best resolution stream: \(String(describing: bestResolutionStream?.id)) with resolution: \(String(describing: bestResolutionStream?.resolution))")
|
||||||
|
logger.info("Best bitrate stream: \(String(describing: bestBitrateStream?.id)) with bitrate: \(String(describing: bestBitrateStream?.bitrate))")
|
||||||
|
|
||||||
let bestResolution = bestResolutionStream?.resolution ?? maxResolution.value
|
let bestResolution = bestResolutionStream?.resolution ?? maxResolution.value
|
||||||
let bestBitrate = bestBitrateStream?.bitrate ?? bestResolutionStream?.resolution.bitrate ?? maxResolution.value.bitrate
|
let bestBitrate = bestBitrateStream?.bitrate ?? bestResolutionStream?.resolution.bitrate ?? maxResolution.value.bitrate
|
||||||
|
|
||||||
return streams.map { stream in
|
logger.info("Final best resolution selected: \(String(describing: bestResolution))")
|
||||||
|
logger.info("Final best bitrate selected: \(bestBitrate)")
|
||||||
|
|
||||||
|
let adjustedStreams = streams.map { stream in
|
||||||
if stream.kind == .hls {
|
if stream.kind == .hls {
|
||||||
|
logger.info("Adjusting HLS stream ID: \(stream.id)")
|
||||||
stream.resolution = bestResolution
|
stream.resolution = bestResolution
|
||||||
stream.bitrate = bestBitrate
|
stream.bitrate = bestBitrate
|
||||||
stream.format = .hls
|
stream.format = .hls
|
||||||
} else if stream.kind == .stream {
|
} else if stream.kind == .stream {
|
||||||
|
logger.info("Adjusting non-HLS stream ID: \(stream.id)")
|
||||||
stream.format = .stream
|
stream.format = .stream
|
||||||
}
|
}
|
||||||
return stream
|
return stream
|
||||||
}
|
}
|
||||||
.filter { stream in
|
|
||||||
stream.resolution <= maxResolution.value
|
let filteredStreams = adjustedStreams.filter { stream in
|
||||||
|
let isWithinResolution = stream.resolution <= maxResolution.value
|
||||||
|
logger.info("Filtered stream ID: \(stream.id) - Is within max resolution: \(isWithinResolution)")
|
||||||
|
return isWithinResolution
|
||||||
}
|
}
|
||||||
.max { lhs, rhs in
|
|
||||||
|
logger.info("Filtered streams count after adjustments: \(filteredStreams.count)")
|
||||||
|
|
||||||
|
let bestStream = filteredStreams.max { lhs, rhs in
|
||||||
if lhs.resolution == rhs.resolution {
|
if lhs.resolution == rhs.resolution {
|
||||||
guard let lhsFormat = QualityProfile.Format(rawValue: lhs.format.rawValue),
|
guard let lhsFormat = QualityProfile.Format(rawValue: lhs.format.rawValue),
|
||||||
let rhsFormat = QualityProfile.Format(rawValue: rhs.format.rawValue)
|
let rhsFormat = QualityProfile.Format(rawValue: rhs.format.rawValue)
|
||||||
else {
|
else {
|
||||||
print("Failed to extract lhsFormat or rhsFormat")
|
logger.info("Failed to extract lhsFormat or rhsFormat for streams \(lhs.id) and \(rhs.id)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let lhsFormatIndex = formatOrder.firstIndex(of: lhsFormat) ?? Int.max
|
let lhsFormatIndex = formatOrder.firstIndex(of: lhsFormat) ?? Int.max
|
||||||
let rhsFormatIndex = formatOrder.firstIndex(of: rhsFormat) ?? Int.max
|
let rhsFormatIndex = formatOrder.firstIndex(of: rhsFormat) ?? Int.max
|
||||||
|
|
||||||
|
logger.info("Comparing formats for streams \(lhs.id) and \(rhs.id) - LHS Format Index: \(lhsFormatIndex), RHS Format Index: \(rhsFormatIndex)")
|
||||||
|
|
||||||
return lhsFormatIndex > rhsFormatIndex
|
return lhsFormatIndex > rhsFormatIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info("Comparing resolutions for streams \(lhs.id) and \(rhs.id) - LHS Resolution: \(String(describing: lhs.resolution)), RHS Resolution: \(String(describing: rhs.resolution))")
|
||||||
|
|
||||||
return lhs.resolution < rhs.resolution
|
return lhs.resolution < rhs.resolution
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info("Best stream selected: \(String(describing: bestStream?.id)) with resolution: \(String(describing: bestStream?.resolution)) and format: \(String(describing: bestStream?.format))")
|
||||||
|
|
||||||
|
return bestStream
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateControls(completionHandler: (() -> Void)? = nil) {
|
func updateControls(completionHandler: (() -> Void)? = nil) {
|
||||||
print("updating controls")
|
logger.info("updating controls")
|
||||||
|
|
||||||
guard model.presentingPlayer, !model.controls.presentingOverlays else {
|
guard model.presentingPlayer, !model.controls.presentingOverlays else {
|
||||||
print("ignored controls update")
|
logger.info("ignored controls update")
|
||||||
completionHandler?()
|
completionHandler?()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -196,7 +233,7 @@ extension PlayerBackend {
|
|||||||
DispatchQueue.main.async(qos: .userInteractive) {
|
DispatchQueue.main.async(qos: .userInteractive) {
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
guard UIApplication.shared.applicationState != .background else {
|
guard UIApplication.shared.applicationState != .background else {
|
||||||
print("not performing controls updates in background")
|
logger.info("not performing controls updates in background")
|
||||||
completionHandler?()
|
completionHandler?()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -127,6 +127,7 @@ extension PlayerModel {
|
|||||||
var streamByQualityProfile: Stream? {
|
var streamByQualityProfile: Stream? {
|
||||||
let profile = qualityProfile ?? .defaultProfile
|
let profile = qualityProfile ?? .defaultProfile
|
||||||
|
|
||||||
|
// First attempt: Filter by both `canPlay` and `isPreferred`
|
||||||
if let streamPreferredForProfile = backend.bestPlayable(
|
if let streamPreferredForProfile = backend.bestPlayable(
|
||||||
availableStreams.filter { backend.canPlay($0) && profile.isPreferred($0) },
|
availableStreams.filter { backend.canPlay($0) && profile.isPreferred($0) },
|
||||||
maxResolution: profile.resolution, formatOrder: profile.formats
|
maxResolution: profile.resolution, formatOrder: profile.formats
|
||||||
@ -134,7 +135,24 @@ extension PlayerModel {
|
|||||||
return streamPreferredForProfile
|
return streamPreferredForProfile
|
||||||
}
|
}
|
||||||
|
|
||||||
return backend.bestPlayable(availableStreams.filter { backend.canPlay($0) }, maxResolution: profile.resolution, formatOrder: profile.formats)
|
// Fallback: Filter by `canPlay` only
|
||||||
|
let fallbackStream = backend.bestPlayable(
|
||||||
|
availableStreams.filter { backend.canPlay($0) },
|
||||||
|
maxResolution: profile.resolution, formatOrder: profile.formats
|
||||||
|
)
|
||||||
|
|
||||||
|
// If no stream is found, trigger the error handler
|
||||||
|
guard let finalStream = fallbackStream else {
|
||||||
|
let error = RequestError(
|
||||||
|
userMessage: "No supported streams available.",
|
||||||
|
cause: NSError(domain: "stream.yatte.app", code: -1, userInfo: [NSLocalizedDescriptionKey: "No supported streams available"])
|
||||||
|
)
|
||||||
|
videoLoadFailureHandler(error, video: currentVideo)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the found stream
|
||||||
|
return finalStream
|
||||||
}
|
}
|
||||||
|
|
||||||
func advanceToNextItem() {
|
func advanceToNextItem() {
|
||||||
|
@ -5,26 +5,153 @@ import Foundation
|
|||||||
// swiftlint:disable:next final_class
|
// swiftlint:disable:next final_class
|
||||||
class Stream: Equatable, Hashable, Identifiable {
|
class Stream: Equatable, Hashable, Identifiable {
|
||||||
enum Resolution: String, CaseIterable, Comparable, Defaults.Serializable {
|
enum Resolution: String, CaseIterable, Comparable, Defaults.Serializable {
|
||||||
|
// Some 16:19 and 16:10 resolutions are also used in 2:1 videos
|
||||||
|
|
||||||
|
// 8K UHD (16:9) Resolutions
|
||||||
|
case hd4320p60
|
||||||
|
case hd4320p50
|
||||||
|
case hd4320p48
|
||||||
|
case hd4320p30
|
||||||
|
case hd4320p25
|
||||||
|
case hd4320p24
|
||||||
|
|
||||||
|
// 5K (16:9) Resolutions
|
||||||
|
case hd2560p60
|
||||||
|
case hd2560p50
|
||||||
|
case hd2560p48
|
||||||
|
case hd2560p30
|
||||||
|
case hd2560p25
|
||||||
|
case hd2560p24
|
||||||
|
|
||||||
|
// 2:1 Aspect Ratio (Univisium) Resolutions
|
||||||
|
case hd2880p60
|
||||||
|
case hd2880p50
|
||||||
|
case hd2880p48
|
||||||
|
case hd2880p30
|
||||||
|
case hd2880p25
|
||||||
|
case hd2880p24
|
||||||
|
|
||||||
|
// 16:10 Resolutions
|
||||||
|
case hd2400p60
|
||||||
|
case hd2400p50
|
||||||
|
case hd2400p48
|
||||||
|
case hd2400p30
|
||||||
|
case hd2400p25
|
||||||
|
case hd2400p24
|
||||||
|
|
||||||
|
// 16:9 Resolutions
|
||||||
case hd2160p60
|
case hd2160p60
|
||||||
case hd2160p50
|
case hd2160p50
|
||||||
case hd2160p48
|
case hd2160p48
|
||||||
case hd2160p30
|
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 hd1440p60
|
||||||
case hd1440p50
|
case hd1440p50
|
||||||
case hd1440p48
|
case hd1440p48
|
||||||
case hd1440p30
|
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 hd1080p60
|
||||||
case hd1080p50
|
case hd1080p50
|
||||||
case hd1080p48
|
case hd1080p48
|
||||||
case hd1080p30
|
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 hd720p60
|
||||||
case hd720p50
|
case hd720p50
|
||||||
case hd720p48
|
case hd720p48
|
||||||
case hd720p30
|
case hd720p30
|
||||||
|
case hd720p25
|
||||||
|
case hd720p24
|
||||||
|
|
||||||
|
// Standard Definition (SD) Resolutions
|
||||||
|
case sd854p30
|
||||||
|
case sd854p25
|
||||||
|
case sd768p30
|
||||||
|
case sd768p25
|
||||||
|
case sd640p30
|
||||||
|
case sd640p25
|
||||||
case sd480p30
|
case sd480p30
|
||||||
|
case sd480p25
|
||||||
|
|
||||||
|
case sd428p30
|
||||||
|
case sd428p25
|
||||||
case sd360p30
|
case sd360p30
|
||||||
|
case sd360p25
|
||||||
|
case sd320p30
|
||||||
|
case sd320p25
|
||||||
case sd240p30
|
case sd240p30
|
||||||
|
case sd240p25
|
||||||
|
case sd214p30
|
||||||
|
case sd214p25
|
||||||
case sd144p30
|
case sd144p30
|
||||||
|
case sd144p25
|
||||||
|
case sd128p30
|
||||||
|
case sd128p25
|
||||||
|
|
||||||
case unknown
|
case unknown
|
||||||
|
|
||||||
var name: String {
|
var name: String {
|
||||||
@ -59,22 +186,94 @@ class Stream: Equatable, Hashable, Identifiable {
|
|||||||
|
|
||||||
var bitrate: Int {
|
var bitrate: Int {
|
||||||
switch self {
|
switch self {
|
||||||
case .hd2160p60, .hd2160p50, .hd2160p48, .hd2160p30:
|
// 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
|
return 56_000_000 // 56 Mbit/s
|
||||||
case .hd1440p60, .hd1440p50, .hd1440p48, .hd1440p30:
|
|
||||||
|
// 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
|
return 24_000_000 // 24 Mbit/s
|
||||||
case .hd1080p60, .hd1080p50, .hd1080p48, .hd1080p30:
|
|
||||||
|
// 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
|
return 12_000_000 // 12 Mbit/s
|
||||||
case .hd720p60, .hd720p50, .hd720p48, .hd720p30:
|
|
||||||
|
// 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
|
return 9_500_000 // 9.5 Mbit/s
|
||||||
case .sd480p30:
|
|
||||||
|
// Standard Definition (SD) Resolutions
|
||||||
|
case .sd854p30, .sd854p25, .sd768p30, .sd768p25, .sd640p30, .sd640p25:
|
||||||
return 4_000_000 // 4 Mbit/s
|
return 4_000_000 // 4 Mbit/s
|
||||||
case .sd360p30:
|
|
||||||
|
case .sd480p30, .sd480p25:
|
||||||
|
return 2_500_000 // 2.5 Mbit/s
|
||||||
|
|
||||||
|
case .sd428p30, .sd428p25:
|
||||||
|
return 2_000_000 // 2 Mbit/s
|
||||||
|
|
||||||
|
case .sd360p30, .sd360p25:
|
||||||
return 1_500_000 // 1.5 Mbit/s
|
return 1_500_000 // 1.5 Mbit/s
|
||||||
case .sd240p30:
|
|
||||||
|
case .sd320p30, .sd320p25:
|
||||||
|
return 1_200_000 // 1.2 Mbit/s
|
||||||
|
|
||||||
|
case .sd240p30, .sd240p25:
|
||||||
return 1_000_000 // 1 Mbit/s
|
return 1_000_000 // 1 Mbit/s
|
||||||
case .sd144p30:
|
|
||||||
|
case .sd214p30, .sd214p25:
|
||||||
|
return 800_000 // 0.8 Mbit/s
|
||||||
|
|
||||||
|
case .sd144p30, .sd144p25:
|
||||||
return 600_000 // 0.6 Mbit/s
|
return 600_000 // 0.6 Mbit/s
|
||||||
|
|
||||||
|
case .sd128p30, .sd128p25:
|
||||||
|
return 400_000 // 0.4 Mbit/s
|
||||||
|
|
||||||
case .unknown:
|
case .unknown:
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user