构建更流畅的媒体应用
Create a more responsive media app
2022年6月6日
一句话判断
如果你的媒体应用在加载缩略图或编辑视频时偶尔会卡顿,这场 Session 提供了 AVFoundation 新增的一整套 async API 来彻底解决这个问题。
这场 Session 讲了什么
AVFoundation 团队的 Jeremy 介绍了 2022 年新增的异步 API,目标是让媒体应用在加载图片、创建合成、检查资源属性等操作时不再阻塞主线程。
内容分为三部分:AVAssetImageGenerator 的新 async 方法、AVMutableComposition 和相关类的 async 版本、以及 async asset inspection 的更新(正式废弃了旧的 async key-value loading)。
Session 还介绍了 AVAssetResourceLoader 的自定义数据加载优化,允许开发者为本地和缓存媒体提供自定义加载逻辑。
值得深挖的点
图片生成的 async 化。 传统的 copyCGImage 是同步方法,在主线程调用会导致 UI 冻结。新增的 image(at:) async 方法利用 async/await 释放调用线程。对于批量生成图片,新的 images(for:times) 方法接受 CMTime 数组并返回 AsyncSequence,不再需要先映射到 NSValue。
宽容差策略。 压缩视频中,I 帧可以独立解码,其他帧依赖邻近帧。默认情况下 image generator 使用最近的 I 帧生成图片。将容差设为零获取精确帧虽然听起来好,但意味着需要加载更多依赖帧。宽容差策略让 image generator 有更多帧可选,减少数据加载量。
Composition 的 async 方法。 AVMutableComposition 的 insertTimeRange 原本是同步的——如果 track 信息未加载,会同步加载。新增的 async 版本会自动异步加载所需的 track 属性。AVVideoComposition 和 AVMutableVideoComposition 的构造器和验证方法也新增了 async 对应版本。
正式废弃 async key-value loading。 旧的基于字符串 key 的异步加载方式(loadValuesAsynchronously)被正式废弃。拼写错误的 key 不会被编译器捕获,会导致属性同步加载而引入性能问题。新的 async load(_:) 使用类型安全的 key,编译期就能发现错误。
代码片段
import AVFoundation
// 新的 async 图片生成方法
let generator = AVAssetImageGenerator(asset: asset)
generator.requestedTimeToleranceBefore = .positiveInfinity // 宽容差策略
generator.requestedTimeToleranceAfter = .positiveInfinity
// 单张图片
let (image, actualTime) = try await generator.image(at: CMTime(seconds: 5, preferredTimescale: 600))
// 批量图片 - 使用 AsyncSequence
let times = [0.0, 5.0, 10.0, 15.0].map { CMTime(seconds: $0, preferredTimescale: 600) }
for await result in generator.images(for: times) {
switch result {
case .success(let imageResult):
let image = imageResult.image // CGImage
let requestedTime = imageResult.requestedTime
case .failure(let error):
print("生成失败: \(error)")
}
}
// 新的 async composition 插入方法
let composition = AVMutableComposition()
guard let compositionTrack = composition.addMutableTrack(
withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid
) else { return }
// 自动异步加载 track 属性
try await compositionTrack.insertTimeRange(
timeRange,
of: videoTrack,
at: .zero
)
// 新的 async 属性检查(推荐方式)
let (duration, tracks) = try await asset.load(.duration, .tracks)
最佳实践
- 使用新的 async API 替代所有同步的媒体数据加载操作,特别是在主线程上
- 图片生成时使用宽容差策略,减少不必要的帧数据加载
- 将代码从旧的
loadValuesAsynchronously迁移到新的async load(_:),获得编译期类型安全 - 只有当属性数据已在内存中(如 AVMutableComposition 的属性)时才使用同步访问
- 对于自定义数据加载场景,考虑使用 AVAssetResourceLoader 提供自定义加载逻辑
- 参考 WWDC 2021 的 Swift 并发相关 Session 了解 async/await 的最佳实践
还有什么值得关注
- AVAssetResourceLoader 的自定义数据加载允许你为本地和缓存媒体提供更优化的加载策略
- 旧的同步属性在 Swift 中被标记为废弃,但 Objective-C 中仍可用,迁移可以分阶段进行
AVMetadataItem等子类也支持新的 async load API- AsyncSequence 的错误处理需要特别注意,每个元素都可能独立失败