了解如何下载和离线播放 HLS 内容
Discover how to download and play HLS offline
2020年6月24日
一句话判断
AVAssetDownloadSession 在 iOS 14 中获得了完整的功能升级——支持 FairPlay DRM 离线下载、多语言音频轨道选择、以及后台下载恢复,做视频类 app 的离线缓存方案终于不用自己造轮子了。
这场 Session 讲了什么
这场 Session 详细讲解了如何使用 AVFoundation 的 AVAssetDownloadSession 和 AVAggregateAssetDownloadSession 来下载 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")
}
}
最佳实践
- 下载前检查磁盘空间。一个 2 小时的 1080p HLS 视频大约 2-4GB。下载前用
FileManager检查剩余空间,不够的话提示用户清理或选择低分辨率版本。 - 优先下载 720p 版本。离线场景通常是移动网络或飞行模式,手机屏幕上 720p 和 1080p 的观感差距不大,但存储占用差一倍。给用户「节省空间」和「高清画质」两个选项。
- 设置合理的密钥续期策略。FairPlay 离线密钥通常有效期 30 天。在 app 启动时检查即将过期的密钥,联网时提前续期,避免用户在飞行模式打开视频时才发现密钥过期看不了。
还有什么值得关注
AVAssetDownloadStorageManager支持设置自动清理策略,可以在存储空间不足时自动删除最早的下载内容。- 下载支持后台继续,app 被杀掉或设备重启后下载任务会自动恢复。
- iOS 14 的
AVAssetDownloadSession支持下载字幕轨道(WebVTT),离线播放时可以显示字幕。