Discover how to download and play HLS offline
Audio & Video 进阶 17m

了解如何下载和离线播放 HLS 内容

Discover how to download and play HLS offline

2020年6月24日

在 Apple 官方观看视频

一句话判断

AVAssetDownloadSession 在 iOS 14 中获得了完整的功能升级——支持 FairPlay DRM 离线下载、多语言音频轨道选择、以及后台下载恢复,做视频类 app 的离线缓存方案终于不用自己造轮子了。

这场 Session 讲了什么

这场 Session 详细讲解了如何使用 AVFoundation 的 AVAssetDownloadSessionAVAggregateAssetDownloadSession 来下载 HLS(HTTP Live Streaming)内容供离线播放。

核心内容包括:HLS 离线下载的基本流程(创建 session -> 选择 variant -> 下载 -> 离线播放)、如何处理 FairPlay 加密内容的离线下载(需要先获取 FPS 租赁凭证)、多码率 variant 的选择策略、以及如何管理下载的磁盘空间。

iOS 14 新增的 AVAggregateAssetDownloadSession 允许你一次性下载多个 variant(不同分辨率)和多个备用音频轨道(不同语言),而不是像旧 API 那样只能下载单个 variant。这对于需要「下载高清 + 标清两个版本」或者「下载中英文双音轨」的场景非常实用。

值得深挖的点

FairPlay 离线下载的密钥管理

下载 FairPlay 加密的 HLS 内容时,离线播放需要本地的密钥凭证。流程是:先通过 FPS(FairPlay Streaming)服务端获取 CKC(Content Key Context),用 AVContentKeySession 把密钥存到本地密钥缓存中,然后才能启动下载。离线播放时,AVPlayer 会从本地密钥缓存中读取密钥,不需要网络。密钥有有效期,过期后需要联网续期。

AVAggregateAssetDownloadSession 的优势

旧的 AVAssetDownloadSession 一次只能下载一个 variant。如果你的 HLS 主播放列表有 4 个分辨率,想同时下载 1080p 和 720p,需要创建两个 session。AVAggregateAssetDownloadSession 可以在同一个 session 中指定多个 variant 和多个音频轨道,统一管理进度和状态。

代码片段

import AVFoundation

// 使用 AVAggregateAssetDownloadSession 下载 HLS
class HLSDownloadManager: NSObject, AVAssetDownloadDelegate {
    
    func downloadHLS(url: URL) {
        let configuration = URLSessionConfiguration.background(
            identifier: "com.app.hls.download"
        )
        let aggregateSession = AVAggregateAssetDownloadSession(
            configuration: configuration
        )
        
        // 解析 HLS 主播放列表
        let asset = AVURLAsset(url: url)
        asset.loadValuesAsynchronously(forKeys: ["variantGroup"]) {
            // 选择要下载的 variant(按分辨率筛选)
            let variantGroup = asset.variants
            let targetVariants = variantGroup.filter { variant in
                variant.videoAttributes?.contains(where: { attr in
                    // 选择 1080p 和 720p
                    attr.width == 1920 || attr.width == 1280
                }) ?? false
            }
            
            // 启动聚合下载
            aggregateSession.makeAssetDownloadTask(
                asset: asset,
                assetTitle: "示例视频",
                assetArtworkData: nil,
                options: [
                    AVAssetDownloadTaskMinimumRequiredMediaBitrateKey: 2000000
                ]
            )?.resume()
        }
    }
    
    // 下载进度回调
    func urlSession(_ session: URLSession, task: URLSessionTask, 
                    didCompleteWithError error: Error?) {
        if let error = error {
            print("下载失败: \(error)")
        } else {
            print("下载完成")
        }
    }
    
    func urlSession(_ session: URLSession, 
                    aggregateAssetDownloadTask: AVAggregateAssetDownloadTask,
                    didCompleteFor mediaSelection: AVMediaSelection) {
        // 单个媒体选择下载完成
    }
}
// 离线播放已下载的 HLS 内容
func playOfflineAsset() {
    // 从本地存储获取已下载的 asset URL
    let localURL = getDownloadedAssetURL()
    let asset = AVURLAsset(url: localURL)
    
    let playerItem = AVPlayerItem(asset: asset)
    let player = AVPlayer(playerItem: playerItem)
    
    let playerViewController = AVPlayerViewController()
    playerViewController.player = player
    player.play()
}

// 管理已下载内容
func listDownloadedAssets() {
    let storageManager = AVAssetDownloadStorageManager.shared
    let fileManager = FileManager.default
    
    // 遍历下载目录
    let downloadsDir = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
        .appendingPathComponent("com.apple.avassetdownloadmanager")
    
    // 检查磁盘空间占用
    if let attrs = try? fileManager.attributesOfFileSystem(forPath: downloadsDir.path),
       let size = attrs[.systemSize] as? Int64 {
        let mb = Double(size) / 1024 / 1024
        print("已用空间: \(mb) MB")
    }
}

最佳实践

  1. 下载前检查磁盘空间。一个 2 小时的 1080p HLS 视频大约 2-4GB。下载前用 FileManager 检查剩余空间,不够的话提示用户清理或选择低分辨率版本。
  2. 优先下载 720p 版本。离线场景通常是移动网络或飞行模式,手机屏幕上 720p 和 1080p 的观感差距不大,但存储占用差一倍。给用户「节省空间」和「高清画质」两个选项。
  3. 设置合理的密钥续期策略。FairPlay 离线密钥通常有效期 30 天。在 app 启动时检查即将过期的密钥,联网时提前续期,避免用户在飞行模式打开视频时才发现密钥过期看不了。

还有什么值得关注

  • AVAssetDownloadStorageManager 支持设置自动清理策略,可以在存储空间不足时自动删除最早的下载内容。
  • 下载支持后台继续,app 被杀掉或设备重启后下载任务会自动恢复。
  • iOS 14 的 AVAssetDownloadSession 支持下载字幕轨道(WebVTT),离线播放时可以显示字幕。
WWDC 2020