WWDC Quick Look 💓 By SwiftGGTeam
在 AVFoundation 中探索 HLS 变体

在 AVFoundation 中探索 HLS 变体

观看原视频

在 AVFoundation 中探索 HLS 变体

Highlight

iOS 15 的 AVAssetVariant API 让应用可以直接从 master playlist 中读取变体属性——码率、分辨率、HDR 类型、音频格式,无需再手动解析 playlist 文本。下载场景下,NSPredicate 驱动的 AVAssetVariantQualifier 让变体选择从”几个固定选项”进化为”任意条件组合”。

核心内容

HLS 的 master playlist 中包含多个变体流(variant),每个变体有不同的分辨率、码率、codec 和 HDR 设置。过去,应用想要展示”4K Dolby Vision”或”Dolby Atmos”这样的 badge,需要手动解析 playlist 文本,容易出错且难以维护。

iOS 15 引入的 AVAssetVariant 将变体信息暴露为结构化 API。AVAssetVariant.VideoAttributesAVAssetVariant.AudioAttributes 分别封装视频和音频属性,应用可以直接读取。下载场景下,AVAssetVariantQualifier 配合 NSPredicate 实现了灵活的变体筛选,支持码率上限、HDR 偏好、无损音频等任意条件组合。

详细内容

变体检查 API

以一个典型的 master playlist 为例(00:40):

#EXTM3U
#EXT-X-VERSION:7
#EXT-X-INDEPENDENT-SEGMENTS

#EXT-X-MEDIA:TYPE=AUDIO,NAME="English",GROUP-ID="stereo",LANGUAGE="en",DEFAULT=YES,AUTOSELECT=YES,CHANNELS="2",URI="en_stereo.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,NAME="French",GROUP-ID="stereo",LANGUAGE="fr",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="2",URI="fr_stereo.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,NAME="English",GROUP-ID="atmos",LANGUAGE="en",DEFAULT=YES,AUTOSELECT=YES,CHANNELS="16/JOC",URI="en_atmos.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,NAME="French",GROUP-ID="atmos",LANGUAGE="fr",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="16/JOC",URI="fr_atmos.m3u8"

#EXT-X-STREAM-INF:BANDWIDTH=14516883,VIDEO-RANGE=SDR,CODECS="avc1.64001f,mp4a.40.5",AUDIO="stereo",FRAME-RATE=23.976,RESOLUTION=1920x1080
sdr_variant.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=34516883,VIDEO-RANGE=PQ,CODECS="dvh1.05.06,ec-3",AUDIO="atmos",FRAME-RATE=23.976,RESOLUTION=3840x1920
dovi_variant.m3u8

这个 playlist 有两个变体:SDR + 立体声(1080p),以及 Dolby Vision + Dolby Atmos(4K)。

iOS 15 起可以直接用 API 读取这些属性:

let asset = AVURLAsset(url: masterPlaylistURL)

// 获取所有变体
for variant in asset.variants {
    let bitrate = variant.peakBitRate

    if let videoAttrs = variant.videoAttributes {
        let resolution = videoAttrs.presentationSize  // CGSize
        let frameRate = videoAttrs.frameRate
        let videoRange = videoAttrs.videoRange  // .sdr, .hdr, .pq, .hlg
    }

    if let audioAttrs = variant.audioAttributes {
        let channelCount = audioAttrs.channelCount
        let formatIDs = audioAttrs.formatIDs
    }
}

应用可以基于这些属性在 UI 上展示 badge(如”4K”、“Dolby Vision”、“Dolby Atmos”),无需手动解析 playlist。

下载场景下的变体选择

HLS 离线下载从 2016 年开始支持。iOS 15 之前,变体选择通过有限的 downloadTask options 实现——HDR 开关、无损音频开关等几个固定选项。

iOS 15 引入 AVAssetVariantQualifier,用 NSPredicate 表达变体偏好(03:38):

// 码率上限: prefer 5Mbps 以下的变体
let peakBitRateCap = NSPredicate(format: "peakBitRate < %d", 5_000_000)
let peakBitRateCapQualifier = AVAssetVariantQualifier(predicate: peakBitRateCap)

// HDR 偏好:只选择 PQ(Dolby Vision)变体
let hdrOnlyPredicate = NSPredicate(
    format: "videoAttributes.videoRange == %@",
    argumentArray: [AVVideoRange.pq]
)
let hdrOnlyQualifier = AVAssetVariantQualifier(predicate: hdrOnlyPredicate)

组合多个条件(04:46):

let compoundPredicate = NSPredicate(
    format: "videoAttributes.videoRange == %@ && peakBitRate < %d",
    argumentArray: [AVVideoRange.pq, 5_000_000]
)
let variantPref = AVAssetVariantQualifier(predicate: compoundPredicate)

Content Configuration

AVAssetDownloadContentConfiguration 将变体偏好和媒体选择绑定在一起(04:46):

let myMediaSelections: [AVMediaSelection] = [
    enAudioMS,   // 英语音频
    frAudioMS,   // 法语音频
    enLegibleMS  // 英文字幕
]

let contentConfig = AVAssetDownloadContentConfiguration()
contentConfig.variantQualifiers = [variantPref]
contentConfig.mediaSelections = myMediaSelections

Download Configuration

AVAssetDownloadConfiguration 是下载配置的根接口(06:29):

let asset = AVURLAsset(url: masterPlaylistURL)
let dwConfig = AVAssetDownloadConfiguration(asset: asset, title: "my-title")

// Primary content config:HDR + 5Mbps 以下 + 英法音频 + 英文字幕
dwConfig.primaryContentConfiguration.variantQualifiers = [varQf]
dwConfig.primaryContentConfiguration.mediaSelections = [enAudioMS, frAudioMS, enLegibleMS]

// Auxiliary content config:无损英语音频
let auxVarPref = NSPredicate(
    format: "%d IN audioAttributes.formatIDs",
    argumentArray: [kAudioFormatAppleLossless]
)
let auxVarQf = AVAssetVariantQualifier(predicate: auxVarPref)
let auxContentConfig = AVAssetDownloadContentConfiguration()
auxContentConfig.variantQualifiers = [auxVarQf]
auxContentConfig.mediaSelections = [enAudioMS]

dwConfig.auxiliaryContentConfigurations = [auxContentConfig]
dwConfig.optimizesAuxiliaryContentConfigurations = true

optimizesAuxiliaryContentConfigurations(默认 true)让 AVFoundation 选择无损变体时,确保其视频 rendition 与 primary 相同,避免重复下载视频。

创建下载任务

let session = AVAssetDownloadURLSession(
    configuration: .background(withIdentifier: "my-background-session"),
    assetDownloadDelegate: myDelegate,
    delegateQueue: .main
)

let downloadTask = session.makeAssetDownloadTask(downloadConfiguration: dwConfig)
downloadTask.resume()

// iOS 15 起可以观察下载进度
let progress = downloadTask.progress  // KVO-observable

直接指定变体

对于难以用 predicate 表达的业务逻辑,可以直接指定变体(08:10):

let myVariant: AVAssetVariant = ...  // 从 asset.variants 中筛选
let variantQf = AVAssetVariantQualifier(variant: myVariant)
dwConfig.primaryContentConfiguration.variantQualifiers = [variantQf]

注意:直接指定的变体必须确保在下载设备上可播放。

核心启发

  1. 用 AVAssetVariant 替代手动解析 playlist:结构化 API 比文本解析更可靠,且自动适配 Apple 新增的属性字段。

  2. 用 NSPredicate 组合任意筛选条件:码率上限、HDR 类型、分辨率、音频格式、声道数等属性都可以作为 predicate 条件,组合方式灵活。

  3. Primary + Auxiliary 配置优化下载体积:Primary 配置包含完整的视频+音频+字幕,Auxiliary 只补充额外的音频或字幕轨道。开启 optimizesAuxiliaryContentConfigurations 避免视频重复下载。

  4. 利用 NSProgress 展示下载进度:iOS 15 起 AVAssetDownloadTask.progress 是 KVO-observable 的,可以直接绑定到 UIProgressView。

  5. 下载前验证设备兼容性:直接指定变体时,确认目标变体的 codec 和分辨率在下载设备上受支持。HDR 内容在 SDR 设备上无法播放。

关联 Session

评论

GitHub Issues · utterances