SwiftUI中HLS直播流离线下载功能实现咨询
在SwiftUI中实现HLS直播流离线下载功能
iOS原生的AVFoundation框架已经支持HLS流的离线下载,SwiftUI作为UI层只需配合响应式状态管理即可完成集成。以下是具体实现步骤、代码示例和关键注意事项:
一、项目配置
权限声明:在
Info.plist中添加以下键值对,说明下载用途(若涉及DRM保护内容,需额外添加NSVideoSubscriberAccountUsageDescription):NSDownloadsUsageDescription: "需要下载直播流以便离线观看"NSAppleMusicUsageDescription: "需要访问媒体内容权限以完成下载"(可选,视内容而定)
后台模式开启:在Xcode项目的Capabilities中,开启
Background Modes下的Audio, AirPlay, and Picture in Picture,确保后台下载任务能持续执行。
二、封装下载管理类
创建遵循ObservableObject的管理类,封装AVAssetDownloadTask的核心逻辑,实现下载的启动、暂停、取消和进度监听:
import AVFoundation import Combine class HLSDownloadManager: ObservableObject { @Published var activeTasks: [AVAssetDownloadTask] = [] @Published var progressMap: [String: Double] = [:] // 用assetID映射下载进度 private var cancellables = Set<AnyCancellable>() // 初始化后台下载会话 private static let downloadSession: AVAssetDownloadURLSession = { let config = URLSessionConfiguration.background(withIdentifier: "com.yourapp.hls.download") return AVAssetDownloadURLSession(configuration: config, assetDownloadDelegate: nil, delegateQueue: OperationQueue.main) }() func startDownload(hlsURL: URL, assetID: String) { let asset = AVURLAsset(url: hlsURL) guard let downloadTask = Self.downloadSession.makeAssetDownloadTask( asset: asset, assetTitle: "直播回放", assetArtworkData: nil, options: [AVAssetDownloadTaskMinimumRequiredMediaBitrateKey: 2500000] // 指定最低码率,可选 ) else { return } downloadTask.taskDescription = assetID downloadTask.resume() activeTasks.append(downloadTask) // 监听进度更新 NotificationCenter.default.publisher(for: .AVAssetDownloadTaskDidUpdateProgress) .compactMap { $0.object as? AVAssetDownloadTask } .filter { $0.taskDescription == assetID } .sink { [weak self] task in self?.progressMap[assetID] = task.progress.fractionCompleted } .store(in: &cancellables) // 监听下载完成 NotificationCenter.default.publisher(for: .AVAssetDownloadTaskDidFinishDownloading) .compactMap { $0.object as? AVAssetDownloadTask } .filter { $0.taskDescription == assetID } .sink { [weak self] task in guard let self = self else { return } self.progressMap[assetID] = 1.0 if let index = self.activeTasks.firstIndex(of: task) { self.activeTasks.remove(at: index) } // 保存本地文件路径到UserDefaults if let localURL = task.url { UserDefaults.standard.set(localURL.path, forKey: "downloaded_\(assetID)") } } .store(in: &cancellables) } func pauseDownload(assetID: String) { guard let task = activeTasks.first(where: { $0.taskDescription == assetID }) else { return } task.suspend() } func cancelDownload(assetID: String) { guard let task = activeTasks.first(where: { $0.taskDescription == assetID }) else { return } task.cancel() if let index = activeTasks.firstIndex(of: task) { activeTasks.remove(at: index) } progressMap.removeValue(forKey: assetID) // 删除本地文件 if let path = UserDefaults.standard.string(forKey: "downloaded_\(assetID)") { try? FileManager.default.removeItem(atPath: path) UserDefaults.standard.removeObject(forKey: "downloaded_\(assetID)") } } func getLocalAssetURL(assetID: String) -> URL? { guard let path = UserDefaults.standard.string(forKey: "downloaded_\(assetID)") else { return nil } return URL(fileURLWithPath: path) } }
三、SwiftUI界面集成
在View中使用@StateObject注入下载管理器,实现下载控制、进度展示和本地播放:
import SwiftUI import AVKit struct HLSDownloadView: View { @StateObject private var downloadManager = HLSDownloadManager() @State private var hlsURLInput = "https://your-stream-url.m3u8" @State private var currentAssetID = "live_stream_001" var body: some View { NavigationStack { VStack(spacing: 24) { // 输入HLS地址和资源ID TextField("HLS直播地址", text: $hlsURLInput) .textFieldStyle(.roundedBorder) .padding(.horizontal) TextField("资源ID", text: $currentAssetID) .textFieldStyle(.roundedBorder) .padding(.horizontal) // 启动下载按钮 Button("启动离线下载") { guard let url = URL(string: hlsURLInput) else { return } downloadManager.startDownload(hlsURL: url, assetID: currentAssetID) } .buttonStyle(.borderedProminent) // 进度展示与控制按钮 if let progress = downloadManager.progressMap[currentAssetID] { ProgressView(value: progress) .padding(.horizontal) .progressViewStyle(.linear) HStack(spacing: 20) { Button("暂停下载") { downloadManager.pauseDownload(assetID: currentAssetID) } Button("取消并删除") { downloadManager.cancelDownload(assetID: currentAssetID) } .foregroundColor(.red) } } // 本地播放按钮 if let localURL = downloadManager.getLocalAssetURL(assetID: currentAssetID) { Button("播放本地缓存") { let player = AVPlayer(url: localURL) let playerVC = AVPlayerViewController() playerVC.player = player // 在SwiftUI中弹出播放器 if let rootVC = UIApplication.shared.connectedScenes .compactMap({ $0 as? UIWindowScene }) .first?.windows .first?.rootViewController { rootVC.present(playerVC, animated: true) { player.play() } } } .buttonStyle(.bordered) } } .padding() .navigationTitle("HLS离线下载") } } } struct HLSDownloadView_Previews: PreviewProvider { static var previews: some View { HLSDownloadView() } }
四、关键注意事项
- 直播流兼容性:并非所有HLS直播流都支持离线下载,需要流的m3u8 playlist包含
EXT-X-PLAYLIST-TYPE:VOD标记,或流媒体服务器允许片段缓存。 - DRM处理:若HLS流受FairPlay DRM保护,需额外配置
AVAssetDownloadTask的options参数,并实现DRM授权逻辑。 - 后台任务恢复:App重启后,需在
App结构体或SceneDelegate中重新获取未完成的下载任务,避免进度丢失。 - 存储空间管理:定期检查下载文件大小,提供清理功能,避免占用过多设备空间。
可选第三方库
若原生封装过于繁琐,可考虑以下库简化开发:
HLSCachingReverseProxy:提供HLS流的缓存和下载能力,支持直播和点播Alamofire:配合AVFoundation实现更灵活的网络请求和下载管理
内容的提问来源于stack exchange,提问作者nazar41
相关产品推荐
相关产品推荐

