增加注解

This commit is contained in:
JinJiangHuang
2026-03-01 22:23:38 +08:00
parent 99a8cc799b
commit ee8c71dcd4
23 changed files with 374 additions and 24 deletions
+33
View File
@@ -2,9 +2,13 @@ import Foundation
import SwiftUI
struct PlaybackQualityOption: Identifiable, Hashable {
///
static let autoIdentifier = "auto"
/// 使 auto id
let id: String
/// UI 1080p / 720p /
let name: String
///
let url: String
var isAuto: Bool {
@@ -19,23 +23,39 @@ struct PlaybackQualityOption: Identifiable, Hashable {
/// ViewModel
@MainActor
class DetailViewModel: ObservableObject {
///
@Published var vodInfo: VodInfo?
///
@Published var isLoading = false
///
@Published var errorMessage: String?
/// 线
@Published var selectedFlag: String = ""
///
@Published var selectedEpisodeIndex: Int = 0
///
@Published var isPlaying = false
///
@Published var playUrl: String?
///
@Published var resumeSeconds: Double = 0
///
@Published var qualityOptions: [PlaybackQualityOption] = []
/// id
@Published var selectedQualityId: String = PlaybackQualityOption.autoIdentifier
/// UI
private var realtimeProgressSeconds: Double = 0
///
private let sourceService = SourceService.shared
private let network = NetworkManager.shared
///
private var qualityBaseEpisodeURL: String = ""
/// key URL
private var qualityOptionCache: [String: [PlaybackQualityOption]] = [:]
///
private var qualityResolveTask: Task<Void, Never>?
///
private var qualityResolveToken = UUID()
///
@@ -105,6 +125,7 @@ class DetailViewModel: ObservableObject {
realtimeProgressSeconds = 0
if let episode = vodInfo?.currentEpisode {
// URL
let shouldResetQuality = qualityBaseEpisodeURL != episode.url
updateQualityOptions(for: episode.url, resetSelection: shouldResetQuality)
playUrl = selectedPlayableURL(fallback: episode.url)
@@ -144,6 +165,7 @@ class DetailViewModel: ObservableObject {
selectedQualityId = option.id
guard isPlaying else { return }
// 使使
let targetURL = option.url.isEmpty ? qualityBaseEpisodeURL : option.url
guard !targetURL.isEmpty, playUrl != targetURL else { return }
@@ -208,6 +230,7 @@ class DetailViewModel: ObservableObject {
}
private func selectedPlayableURL(fallback: String) -> String {
// URL使退
let selected = qualityOptions.first(where: { $0.id == selectedQualityId })?.url
if let selected, !selected.isEmpty {
return selected
@@ -215,6 +238,7 @@ class DetailViewModel: ObservableObject {
return fallback
}
///
private func resetQualityState() {
qualityResolveTask?.cancel()
qualityResolveTask = nil
@@ -231,6 +255,7 @@ class DetailViewModel: ObservableObject {
return
}
//
qualityResolveTask?.cancel()
qualityResolveTask = nil
@@ -245,6 +270,7 @@ class DetailViewModel: ObservableObject {
qualityOptions = [autoOption]
if let cached = qualityOptionCache[trimmedEpisodeURL] {
//
qualityOptions = cached
if resetSelection || !cached.contains(where: { $0.id == selectedQualityId }) {
selectedQualityId = PlaybackQualityOption.autoIdentifier
@@ -277,12 +303,14 @@ class DetailViewModel: ObservableObject {
}
}
/// HLS
private func resolveQualityOptions(for episodeURL: String) async -> [PlaybackQualityOption] {
guard let url = URL(string: episodeURL), Self.looksLikeHLSURL(url) else { return [] }
guard let playlist = try? await network.getString(from: episodeURL) else { return [] }
return Self.parseMasterPlaylist(playlist, masterURL: url)
}
/// HLS
private struct HLSVariant {
let url: String
let name: String?
@@ -290,6 +318,7 @@ class DetailViewModel: ObservableObject {
let bandwidth: Int?
}
/// URL HLS
private static func looksLikeHLSURL(_ url: URL) -> Bool {
let lowercased = url.absoluteString.lowercased()
if lowercased.contains(".m3u8") { return true }
@@ -297,6 +326,8 @@ class DetailViewModel: ObservableObject {
return ext == "m3u8" || ext == "m3u"
}
/// HLS
/// 2
private static func parseMasterPlaylist(_ content: String, masterURL: URL) -> [PlaybackQualityOption] {
guard content.localizedCaseInsensitiveContains("#EXT-X-STREAM-INF") else { return [] }
@@ -407,6 +438,7 @@ class DetailViewModel: ObservableObject {
return merged
}
/// `EXT-X-STREAM-INF`
private static func parseAttributeMap(_ raw: String) -> [String: String] {
var result: [String: String] = [:]
let pairs = splitAttributes(raw)
@@ -426,6 +458,7 @@ class DetailViewModel: ObservableObject {
return result
}
///
private static func splitAttributes(_ raw: String) -> [String] {
var parts: [String] = []
var buffer = ""