Video Quality Prefrences Addition (#180)

This commit is contained in:
realdoomsboygaming 2025-06-12 12:31:11 -07:00 committed by GitHub
parent 2beaaf5575
commit b56ef52ae3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 189 additions and 4 deletions

View file

@ -6,6 +6,7 @@
//
import Foundation
import Network
class FetchDelegate: NSObject, URLSessionTaskDelegate {
private let allowRedirects: Bool
@ -70,3 +71,56 @@ extension URLSession {
return URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
}
}
enum NetworkType {
case wifi
case cellular
case unknown
}
@available(iOS 14.0, *)
class NetworkMonitor: ObservableObject {
static let shared = NetworkMonitor()
private let monitor = NWPathMonitor()
private let queue = DispatchQueue(label: "NetworkMonitor")
@Published var currentNetworkType: NetworkType = .unknown
@Published var isConnected: Bool = false
private init() {
startMonitoring()
}
private func startMonitoring() {
monitor.pathUpdateHandler = { [weak self] path in
DispatchQueue.main.async {
self?.isConnected = path.status == .satisfied
self?.currentNetworkType = self?.getNetworkType(from: path) ?? .unknown
}
}
monitor.start(queue: queue)
}
private func getNetworkType(from path: NWPath) -> NetworkType {
if path.usesInterfaceType(.wifi) {
return .wifi
} else if path.usesInterfaceType(.cellular) {
return .cellular
} else {
return .unknown
}
}
static func getCurrentNetworkType() -> NetworkType {
if #available(iOS 14.0, *) {
return shared.currentNetworkType
} else {
return .unknown
}
}
deinit {
monitor.cancel()
}
}

View file

@ -7,6 +7,63 @@
import UIKit
enum VideoQualityPreference: String, CaseIterable {
case best = "Best"
case p1080 = "1080p"
case p720 = "720p"
case p420 = "420p"
case p360 = "360p"
case worst = "Worst"
static let wifiDefaultKey = "videoQualityWiFi"
static let cellularDefaultKey = "videoQualityCellular"
static let defaultWiFiPreference: VideoQualityPreference = .best
static let defaultCellularPreference: VideoQualityPreference = .p720
static let qualityPriority: [VideoQualityPreference] = [.best, .p1080, .p720, .p420, .p360, .worst]
static func findClosestQuality(preferred: VideoQualityPreference, availableQualities: [(String, String)]) -> (String, String)? {
for (name, url) in availableQualities {
if isQualityMatch(preferred: preferred, qualityName: name) {
return (name, url)
}
}
let preferredIndex = qualityPriority.firstIndex(of: preferred) ?? qualityPriority.count
for i in 0..<qualityPriority.count {
let candidate = qualityPriority[i]
for (name, url) in availableQualities {
if isQualityMatch(preferred: candidate, qualityName: name) {
return (name, url)
}
}
}
return availableQualities.first
}
private static func isQualityMatch(preferred: VideoQualityPreference, qualityName: String) -> Bool {
let lowercaseName = qualityName.lowercased()
switch preferred {
case .best:
return lowercaseName.contains("best") || lowercaseName.contains("highest") || lowercaseName.contains("max")
case .p1080:
return lowercaseName.contains("1080") || lowercaseName.contains("1920")
case .p720:
return lowercaseName.contains("720") || lowercaseName.contains("1280")
case .p420:
return lowercaseName.contains("420") || lowercaseName.contains("480")
case .p360:
return lowercaseName.contains("360") || lowercaseName.contains("640")
case .worst:
return lowercaseName.contains("worst") || lowercaseName.contains("lowest") || lowercaseName.contains("min")
}
}
}
extension UserDefaults {
func color(forKey key: String) -> UIColor? {
guard let colorData = data(forKey: key) else { return nil }
@ -30,4 +87,19 @@ extension UserDefaults {
Logger.shared.log("Error archiving color: \(error)", type: "Error")
}
}
static func getVideoQualityPreference() -> VideoQualityPreference {
let networkType = NetworkMonitor.getCurrentNetworkType()
switch networkType {
case .wifi:
let rawValue = UserDefaults.standard.string(forKey: VideoQualityPreference.wifiDefaultKey)
return VideoQualityPreference(rawValue: rawValue ?? "") ?? VideoQualityPreference.defaultWiFiPreference
case .cellular:
let rawValue = UserDefaults.standard.string(forKey: VideoQualityPreference.cellularDefaultKey)
return VideoQualityPreference(rawValue: rawValue ?? "") ?? VideoQualityPreference.defaultCellularPreference
case .unknown:
return .p720
}
}
}

View file

@ -2211,7 +2211,13 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
private func switchToQuality(urlString: String) {
guard let url = URL(string: urlString),
currentQualityURL?.absoluteString != urlString else { return }
currentQualityURL?.absoluteString != urlString else {
Logger.shared.log("Quality Selection: Switch cancelled - same quality already selected", type: "General")
return
}
let qualityName = qualities.first(where: { $0.1 == urlString })?.0 ?? "Unknown"
Logger.shared.log("Quality Selection: Switching to quality: \(qualityName) (\(urlString))", type: "General")
let currentTime = player.currentTime()
let wasPlaying = player.rate > 0
@ -2270,7 +2276,10 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
qualityButton.menu = qualitySelectionMenu()
if let selectedQuality = qualities.first(where: { $0.1 == urlString })?.0 {
Logger.shared.log("Quality Selection: Successfully switched to: \(selectedQuality)", type: "General")
DropManager.shared.showDrop(title: "Quality: \(selectedQuality)", subtitle: "", duration: 0.5, icon: UIImage(systemName: "eye"))
} else {
Logger.shared.log("Quality Selection: Switch completed but quality name not found in list", type: "General")
}
}
@ -2320,11 +2329,34 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
baseM3U8URL = url
currentQualityURL = url
let networkType = NetworkMonitor.getCurrentNetworkType()
let networkTypeString = networkType == .wifi ? "WiFi" : networkType == .cellular ? "Cellular" : "Unknown"
Logger.shared.log("Quality Selection: Detected network type: \(networkTypeString)", type: "General")
parseM3U8(url: url) { [weak self] in
guard let self = self else { return }
if let last = UserDefaults.standard.string(forKey: "lastSelectedQuality"),
self.qualities.contains(where: { $0.1 == last }) {
self.switchToQuality(urlString: last)
Logger.shared.log("Quality Selection: Found \(self.qualities.count) available qualities", type: "General")
for (index, quality) in self.qualities.enumerated() {
Logger.shared.log("Quality Selection: Available [\(index + 1)]: \(quality.0) - \(quality.1)", type: "General")
}
let preferredQuality = UserDefaults.getVideoQualityPreference()
Logger.shared.log("Quality Selection: User preference for \(networkTypeString): \(preferredQuality.rawValue)", type: "General")
if let selectedQuality = VideoQualityPreference.findClosestQuality(preferred: preferredQuality, availableQualities: self.qualities) {
Logger.shared.log("Quality Selection: Selected quality: \(selectedQuality.0) (URL: \(selectedQuality.1))", type: "General")
self.switchToQuality(urlString: selectedQuality.1)
} else {
Logger.shared.log("Quality Selection: No matching quality found, using default", type: "General")
if let last = UserDefaults.standard.string(forKey: "lastSelectedQuality"),
self.qualities.contains(where: { $0.1 == last }) {
Logger.shared.log("Quality Selection: Falling back to last selected quality", type: "General")
self.switchToQuality(urlString: last)
} else if let firstQuality = self.qualities.first {
Logger.shared.log("Quality Selection: Falling back to first available quality: \(firstQuality.0)", type: "General")
self.switchToQuality(urlString: firstQuality.1)
}
}
self.qualityButton.isHidden = false
@ -2338,6 +2370,7 @@ class CustomMediaPlayerViewController: UIViewController, UIGestureRecognizerDele
isHLSStream = false
qualityButton.isHidden = true
updateMenuButtonConstraints()
Logger.shared.log("Quality Selection: Non-HLS stream detected, quality selection unavailable", type: "General")
}
}

View file

@ -205,7 +205,11 @@ struct SettingsViewPlayer: View {
@AppStorage("skipIntroOutroVisible") private var skipIntroOutroVisible: Bool = true
@AppStorage("pipButtonVisible") private var pipButtonVisible: Bool = true
@AppStorage("videoQualityWiFi") private var wifiQuality: String = VideoQualityPreference.defaultWiFiPreference.rawValue
@AppStorage("videoQualityCellular") private var cellularQuality: String = VideoQualityPreference.defaultCellularPreference.rawValue
private let mediaPlayers = ["Default", "Sora", "VLC", "OutPlayer", "Infuse", "nPlayer", "SenPlayer", "IINA", "TracyPlayer"]
private let qualityOptions = VideoQualityPreference.allCases.map { $0.rawValue }
var body: some View {
ScrollView {
@ -261,6 +265,28 @@ struct SettingsViewPlayer: View {
)
}
SettingsSection(
title: "Video Quality Preferences",
footer: "Choose preferred video resolution for WiFi and cellular connections. Higher resolutions use more data but provide better quality. If the exact quality isn't available, the closest option will be selected automatically.\n\nNote: Not all video sources and players support quality selection. This feature works best with HLS streams using the Sora player."
) {
SettingsPickerRow(
icon: "wifi",
title: "WiFi Quality",
options: qualityOptions,
optionToString: { $0 },
selection: $wifiQuality
)
SettingsPickerRow(
icon: "antenna.radiowaves.left.and.right",
title: "Cellular Quality",
options: qualityOptions,
optionToString: { $0 },
selection: $cellularQuality,
showDivider: false
)
}
SettingsSection(title: "Progress bar Marker Color") {
ColorPicker("Segments Color", selection: Binding(
get: {