大多数浏览器和
Developer App 均支持流媒体播放。
-
构建更快速响应的媒体 App
了解如何利用 AVFoundation 让用户专注于您的媒体 App 的内容,而不用盯着加载动画长时间等待。我们将介绍如何在您的 App 中提供响应快速且运行流畅的界面,帮助您创建丰富的音视频复合内容、加载音视频素材以及制作媒体缩略图。学习如何在您的 App 的主线程上执行这些操作并同时并行运行 I/O 处理,探索如何在加载自定义存储等数据时实现一流的播放性能。为能更好地理解此讲座,我们建议您先观看 WWDC21 的“AVFoundation 的新功能”。
资源
相关视频
WWDC21
-
下载
♪ ♪ Jeremy:嗨 我是 Jeremy 我将向您展示如何 使用 AVFoundation 创建 响应速度更快的媒体 App 您在 App 中使用媒体资产时 想做的可能不仅仅是播放它们 您可能想显示缩略图 将媒体合成新的作品 或获取有关您资产的信息 这些任务需要加载数据 对于像视频这样的大文件 可能需要一些时间才能完成 但非常遗憾 如果在主线程上同步工作 很容易让您的 App 出现延迟问题 一个保持 App 快速响应的 好方法是异步加载数据 并在完成后更新您的 UI AVFoundation 就能提供工具 让这一切变得简单 所以这就是我们今天要讨论的内容 首先 我将向您介绍 AVFoundation 中的一些新的异步 API 接下来 我会介绍一项关于使用去年 介绍的 async load(_:) 方法 进行资产检查的更新 我还将向您展示如何使用 AVAssetResourceLoader 优化本地和缓存媒体的 自定义数据加载 但首先 让我们了解一下 新的异步 API 使用 AVAssetImageGenerator 从视频中抓取静止图像 是创建缩略图的好方法 但图像的生成不是即时的
要生成图像 图像生成器 需要从您的视频文件中 加载帧数据 对于存储在远程服务器 或互联网上的媒体来说 加载会慢得多 这就是为什么生成 图像的方式非常重要 使用在主线程上同步 加载数据的方法 比如 copyCGImage 可能会导致您的 UI 在等待视频加载时定格 今年 我们添加了 image(at: time) 异步方法 它在图像生成器加载数据时使用 异步/等待来释放调用线程 图像生成器返回一个包含图像的元组 及其在资产中的实际时间 有几个原因会导致实际时间 可能与您请求的时间不同 但如果您只想要图像 您可以使用 .image 属性直接访问它 压缩视频中的某些帧比 其他帧更容易加载 i 帧可以独立解码 而其他帧则依赖于附近的帧进行解码 对于您请求的时间 默认情况下图像生成器 将使用最近的 i 帧来生成图像 将容差设置为 0 以获得您请求时间 对应的确切帧可能看起来很诱人 但请记住 这一帧可能会依赖 附近的其他帧 它们也需要图像生成器进行加载 相反地 您可以考虑将容差设置放宽 这样您依然会得到寻求的结果 放宽的容差通过给 图像生成器更多帧进行选择 有助于最大限度地减少数据加载 它需要加载的帧越少 返回图像的速度就越快
要在资产中多次获取系列图像 图像生成器提供 generateCGImagesAsynchronously(forTimes:) 然而 在 Swift 中使用时 需要注意一些细微差别 今年我们新增了 images(for: times) 方法 它现在要求一个 CMTimes 数组 所以不需要先将 它们映射到 NSValues 它还使用异步序列提供结果 在 Swift 中 序列允许您使用 for in 循环遍历它们的项目 对于没有一次性准备好的一系列项目 异步序列可让您在 每次迭代后等待下一个元素 对于每个成功生成的图像 结果包括最初请求的时间 以及跟随图像的实际时间 如果失败的话 结果中会包含错误原因说明
如果您只对图像感兴趣 结果可以通过属性直接访问它的值 如果生成失败 这也可能引发错误 如需了解有关异步序列的更多信息 我推荐您查看“认识异步序列”讲座 对于图像生成这样的任务 可以相对简单地 看到它是如何涉及加载数据的 但 AVFoundation 还有 一些其他的同步领域 更难发现问题点 AVMutableComposition 就是这些领域中的一个 插入资产的时间范围 需要有关资产轨道的信息 以便在合成中添加对它们的引用 它会同步检查轨道 因此 如果轨道尚未加载 它们将被同步加载 以创建新的合成轨道
从前 解决方案是 先等待资产轨道加载 再将它们插入合成中 但是今年我们引入了 insertTimeRange 的全新异步版本 它将根据需要为您异步加载轨道
视频合成和可变视频合成中 还有其他方法也需要加载资产的属性 今年新增了 “propertiesOf asset”构造函数 isValid(for:timeRange:) 方法 现在也有了对应的异步版本 这些新方法将异步 加载资产的轨道和持续时间 所以也无需预先加载它们 这些新的异步方法通过 异步加载资产需要的属性 使得与资产交互变得更容易 但您可能需要自己加载资产属性 所以我们来回顾一下异步资产检查 您可能已经注意到 有两种方法可以检查资产属性 引入 AVFoundation 时 检查属性的最佳方法是异步键值加载 去年 我们引入了 async load(_:) 它使用类型安全键 来识别要加载的属性 过去的异步键值加载技术 将硬编码字符串用作键 这些字符串键中的 拼写错误很难被发现 拼写错误的键会使它无法被异步加载 当稍后使用该属性时 它会在加载时受阻
也很容易发生 忘记向要加载的键添加新属性 或者完全忘记异步加载它们的情况 出于这些原因 今年我们将弃用异步键值加载 以及 Swift 中的同步属性 转而支持异步加载 异步加载使用 类型安全标识符来防止拼写错误 它直接按要求返回属性值 以避免访问未加载的属性 由于这些都是在编译时检查的 您也可以预防一切 新的 IO 绑定性能问题产生 异步加载现在是在 AVAsset、AVAssetTrack AVMetadataItem 及其子类上 进行属性异步检查的唯一推荐方法 不过 这些类中有一小部分 仍提供同步属性检查 这是因为它们属性的 数据已经在内存中可用 让我们再看一下可变组合来寻找原因
我们将使用可变合成将两个现有 视频轨道的片段拼合在一起 我们首先创建一个空的合成 并添加一个空的视频轨道 然后 我们可以同步将 第一个视频轨道的一部分 插入合成轨道 在后台 这一步不加载任何数据 相反地 它会添加一个 指向所需轨道的新轨道段
然后我们可以用相同的方式 附加第二个轨道的一部分
由于组合本身由内存结构 而不是文件支持 我们可以安心地同步检查其属性 而无需先加载它们 同样 出于这个原因 同步属性检查将在这些类上保持可用 而且所有类都可以 使用异步加载进行异步检查
AVFoundation 中 所有的新异步方法 将让加载媒体数据时防止 阻塞变得更容易 不过 第一次在您的 App 中 引入并发单元可能会很棘手 请查看 WWDC 21 中的 这些讲座来了解如何开始使用 Swift 并发单元并在您的 App 中 迁移到 AVFoundation 的异步加载 在今天的最后一个话题中 我们来谈谈如何为您的资产 优化自定义数据加载 首先 让我们看看 AVAsset 默认是如何加载数据的 当您使用 URL 创建 AVAsset 时 媒体可能在网络上 也可能存储在本地设备上 如果在网络上 AVAsset 会动态缓存 一定量的数据 保证流畅播放 如果媒体在本地 AVAsset 可以绕过缓存 并根据需要加载数据进行播放 在某些情况下 您可能无法让 AVAsset 直接指向您的媒体 也许您将 mp4 的原始字节 存储在自定义项目文件中 对于这种情况 AVAsset 可以使用 AVAssetResourceLoader 资源加载器为资产提供了 一种从使用特殊 加载方式的媒体中 请求任意字节的方法 但是由于资产不再处理读取数据 它无法预测加载 每个数据块需要多长时间 所以它假设访问媒体涉及网络通信 并一直等到它缓存数据 才准备好进行播放 今年 如果您的媒体在本地可用 您可以为资源加载器启用 entireLengthAvailableOnDemand 设置此标志就告诉了资产 它可以期望按要求接收数据 因此可以跳过缓存
对于本地媒体 entireLengthAvailableOnDemand 可以帮助减少 App 在播放期间的内存使用量 因为它不需要缓存额外的数据 它还可以减少启动播放所需的时间 因为资产不必等待缓存预先填满 但是 必须谨慎启用此标志 如果加载需要任何网络操作 包括网络文件存储 播放很有可能会不可靠
这就是资源加载器的新增强功能 现在让我们讨论一些用于 您 App 的后续步骤作为总结
使用媒体时 可以用 异步/等待 让您的 App 在后台加载时 保持快速响应 在使用图像生成器获取更快的结果时 请考虑增加容差 如果您使用资源加载器来 获取本地可用的媒体 请按需启用完整块长以帮助提高性能 这就是我今天要讲的全部内容 感谢观看 希望您享受 WWDC 22
-
-
1:41 - Generate a thumbnail
func thumbnail() async throws -> UIImage { let generator = AVAssetImageGenerator(asset: asset) generator.requestedTimeToleranceBefore = .zero generator.requestedTimeToleranceAfter = CMTime(seconds: 3, preferredTimescale: 600) let thumbnail = try await generator.image(at: time).image return UIImage(cgImage: thumbnail) }
-
2:56 - Generate a series of thumbnails
func timelineThumbnails(for times: [CMTime]) async { for await result in generator.images(for: times) { switch result { case .success(requestedTime: let requestedTime, image: let image, actualTime: _): updateThumbnail(for: requestedTime, with: image) case .failed(requestedTime: let requestedTime, error: _): updateThumbnail(for: requestedTime, with: placeholder) } } }
-
3:49 - Generate a series of thumbnails
func timelineThumbnails(for times: [CMTime]) async { for await result in generator.images(for: times) { updateThumbnail(for: result.requestedTime, with: (try? result.image) ?? placeholder) } }
-
4:40 - AVMutableComposition
let composition = AVMutableComposition() try await composition.insertTimeRange(timeRange, of: asset, at: startTime)
-
4:57 - AVVideoComposition
let videoComposition = try await AVVideoComposition .videoComposition(withPropertiesOf: asset) try await videoComposition.isValid(for: asset, timeRange: range, validationDelegate: delegate)
-
5:33 - Asset inspection
asset.loadValuesAsynchronously(forKeys: ["duration", "tracks"]) { guard asset.statusOfValue(forKey: "duration", error: &error) == .loaded else { ... } guard asset.statusOfValue(forKey: "tracks", error: &error) == .loaded else { ... } myFunction(thatUses: asset.duration, and: asset.tracks) } let (duration, tracks) = try await asset.load(.duration, .tracks) myFunction(thatUses: duration, and: tracks)
-
7:06 - Synchronously insert track segments into a composition
// videoTrack1: AVAssetTrack, videoTrack2: AVAssetTrack // Create a composition and add an empty track let composition = AVMutableComposition() guard let compositionTrack = composition .addMutableTrack(withMediaType: .video, preferredTrackID: 1) else { return } // Append the first 5 seconds of track 1 try compositionTrack .insertTimeRange(firstFiveSeconds, of: videoTrack1, at: .zero) // Append the first 5 seconds of track 2 try compositionTrack .insertTimeRange(firstFiveSeconds, of: videoTrack2, at: fiveSeconds) myFunction(thatUses: composition.duration, and: composition.tracks)
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。