// // LivePlaybackViewModel.swift // suixinkan // // Created by Codex on 2026/6/25. // import AVFoundation import Foundation import Combine /// 直播播放地址解析器,避免把 RTMP 推流地址误当作播放地址。 enum LivePlaybackURLResolver { static func playableURL(from live: LiveEntity) -> URL? { playableURL(from: live.playbackURLCandidates) } static func playableURL(from candidates: [String]) -> URL? { for candidate in candidates { if let url = playableURL(from: candidate) { return url } } return nil } static func playableURL(from rawValue: String) -> URL? { let value = rawValue.liveTrimmed guard !value.isEmpty, let url = URL(string: value) else { return nil } guard let scheme = url.scheme?.lowercased(), scheme == "http" || scheme == "https" else { return nil } if url.pathExtension.lowercased() == "flv" { return nil } return url } } /// 播放器状态。 enum LivePlaybackState: Equatable { case empty case ready(URL) case playing(URL) case failed(String) } @MainActor /// 直播播放器 ViewModel,管理系统播放器的 URL、播放和释放状态。 final class LivePlaybackViewModel: ObservableObject { @Published var state: LivePlaybackState = .empty @Published var errorMessage: String? @Published private(set) var player: AVPlayer? var playableURL: URL? { switch state { case .ready(let url), .playing(let url): url case .empty, .failed: nil } } var isPlaying: Bool { if case .playing = state { return true } return false } init(live: LiveEntity? = nil, urlString: String? = nil) { if let live { load(live: live) } else if let urlString { load(urlString: urlString) } } func load(live: LiveEntity) { load(url: LivePlaybackURLResolver.playableURL(from: live)) } func load(urlString: String) { load(url: LivePlaybackURLResolver.playableURL(from: urlString)) } func play() { guard let url = playableURL else { return } if player == nil { player = AVPlayer(url: url) } player?.play() state = .playing(url) } func pause() { guard let url = playableURL else { return } player?.pause() state = .ready(url) } func reload() { guard let url = playableURL else { return } releasePlayer() player = AVPlayer(url: url) state = .ready(url) } func release() { releasePlayer() if let url = playableURL { state = .ready(url) } else { state = .empty } } private func load(url: URL?) { releasePlayer() guard let url else { errorMessage = "暂无可播放地址" state = .empty return } errorMessage = nil player = AVPlayer(url: url) state = .ready(url) } private func releasePlayer() { player?.pause() player = nil } }