You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中HLS直播流离线下载功能实现咨询

在SwiftUI中实现HLS直播流离线下载功能

iOS原生的AVFoundation框架已经支持HLS流的离线下载,SwiftUI作为UI层只需配合响应式状态管理即可完成集成。以下是具体实现步骤、代码示例和关键注意事项:

一、项目配置

  1. 权限声明:在Info.plist中添加以下键值对,说明下载用途(若涉及DRM保护内容,需额外添加NSVideoSubscriberAccountUsageDescription):

    • NSDownloadsUsageDescription: "需要下载直播流以便离线观看"
    • NSAppleMusicUsageDescription: "需要访问媒体内容权限以完成下载"(可选,视内容而定)
  2. 后台模式开启:在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 17:32:54