大多数浏览器和
Developer App 均支持流媒体播放。
-
使用 HLS 插播内容提升广告体验
探索 HLS 插播内容如何帮助你将广告无缝插入到 HLS 内容中。我们还将展示如何利用整合的时间线调整 UI 体验,并为插播内容打造同播共享体验。
章节
- 0:00 - Introduction
- 0:45 - Interstitals recap
- 1:56 - Integrated timeline
- 10:40 - SharePlay with interstitials
资源
- Forum: Media Technologies
- Providing an integrated view of your timeline when playing HLS interstitials
相关视频
WWDC21
-
下载
大家好 我叫 Julian 是一名 AVFoundation 工程师 近年来 我们推出了 HLS 插播 作为在主要内容中 插入广告的一种方法 今年 我们将提供多种新的方法 来显示插播 并通过它来导航 因此 如果你有兴趣插入广告 或只想了解如何通过新的方法 来添加 HLS 插播 本次讲座就非常适合你 在本次讲座中 我们将首先 回顾 HLS 插播当前的工作原理
接着 我们会讨论 新的 Integrated Timeline API 这个 API 提供了一个数据模型 用于打造 App 的 UI 体验
最后 我们将讨论插播式同播共享 的新功能
现在 我们来快速回顾一下 HLS 插播
对于 HLS 插播来说 广告或插播是两种独立的素材 可以在主要内容时间线上 为它们设定时间 这是一个典型的媒体播放列表 如果我们想在 5 秒处 在内容中插入一个插播 只需将开始日期指定为从内容的 “Program-Date-Time”算起的 5 秒后 这样 就会先播放 5 秒钟的主要内容 然后开始播放 插播式 URL 的内容 最后 我们将在 这个插播结束后 继续播放主要内容
与直接将广告插入主要内容相比 HLS 插播 具备众多优点 插播支持 后期绑定广告决策 而不要求广告 与主要内容存在关联 虽然 HLS 插播 已主要用于广告插入 但我们鼓励用户使用它来 显示其他辅助内容 比如节目宣传片、 工作室横幅和节目回顾环节 以替代那些以不连续方式 提供的内容 对于将 HLS 插播插入你的内容 这还只是 冰山的一角 如果你想了解更多信息 请观看 “探索 HLS 中的动态前贴片广告 和中贴片广告” 今年 我们将提供一个 全新的 AVFoundation API 叫做 Integrated Timeline 这个 API 引入了一个数据模型 用于控制那些附带插播的 内容播放的时间和顺序 你可以利用这个数据模型 打造全新的 UI 体验 并帮助用户在插播内外 流畅地进行导航
首先 我们将简要介绍 在 Integrated Timeline 上 显示插播的不同方法
之后 继续了解这个 API 及其使用方法 然后 我们会查看一个示例 App 它使用了 Integrated Timeline 来构建 UI 最后 我们会讨论新的 HLS 语法 它用于描述插播 在 Integrated Timeline 中的运行方式
现在 我们来立即了解一下 在时间线上显示插播 的几种方法
对于插入附带 VOD 内容的广告 一个常见用例是 将插播标记为 传输栏上的一个点
当播放到这一点时 播放头便会 在播放插播时停止 插播结束后 主要内容会继续播放 而播放头也会继续运行 插播方面的另一个用例是 将插播表示为 传输栏上的一个范围 如此一来 这个插播的时长 便会计入 传输栏总时长
对于广播式直播中的广告 或将嵌入内容替换为插播 的任意场景 通常都会采用这种方法
最后一个示例则是 对前一示例的延伸 在前一示例中 我们在 UI 中以黄色来 突出显示插播 在本例中 待插入的插播 与主要内容 难以相互区分 将 HLS 插播与评级提示插播、 节目回顾等前贴片广告 或是配音信息卡等后贴片广告 搭配使用时 这个功能非常实用 尤其是针对插播内容 与主要内容密切相关的情况
介绍了 Integrated Timeline 将会建模的不同用例后 我们来了解一下 AVPlayerInterstitalEvent API 的新增功能 它们用于标记上述每个用例
要标记时间线上的某个点 我们可以创建一个插播事件 并将名为 timelineOccupancy 的新属性 的值设为 singlePoint
此时 要标记时间线上的 一个范围 我们可将这个事件的 timelineOccupancy 设为 fill 但是 由于插播事件 会在播放头靠近时 动态载入 因此事件的实际时长 可能会出现推迟 我们可提供 plannedDuration 作为备选时间线 直到获悉实际时长为止 总之 最好是为范围插播 设置这一属性
对于最后一个用例 其中的范围插播 在 UI 中难以区分 我们可将 supplementsPrimaryContent 设为 true
了解了如何标记时间线上的 插播行为后 我们来看看 Integrated Timeline API
为观察时间线进展 IntegratedTimeline 提供 一个 API 来获取 currentTime
当播放头到达 PointEvent 位置时 currentTime 会在事件播放的同时 停止前进
事件播放结束后 currentTime 会继续前进 在本例中 当我们到达 下一个插播事件时 currentTime 会在播放这个事件的同时 继续前进
此外 Integrated Timeline 还可 在插播内进行搜寻 假设 客户端想 向前搜寻 10 秒 于是便会落在 这个 FillEvent 的中间位置 如果没有这个 API 便难以完成搜寻 尤其是 此时 这一插播事件既未载入 也未在 AVPlayer 上排队 但通过这个 API 可轻松 随意搜寻到 时间线上的任意位置 而无论它是主要内容 还是插播 作为 Integrated Timeline 的一部分 它会提供一个名为 AVPlayerItemSegment 的新对象 时间线上的每个片段都是一个分段 每个分段均会提供 有关这部分内容的关键详细信息 以及它在时间线上所占用的时间 即使是主要内容的范围 也会被视为分段
整合时间线的 最大优点之一在于 它可以简化 播放 UI 的绘制 但要做到这一点并不容易 因为时间线会不断变化 为了提供一个 自洽状态 我们引入了时间线 可提供的一个快照对象 这个快照代表 整个自洽属性集合 绘制 UI 需使用这些属性
现在 随着播放的前进 以及时间线的变化 底部的快照会保持静态 且不会变化
借助 AVPlayerItemIntegratedSnapshot 我们可获取快照中所有 AVPlayerItemSegment 的列表 在本例中 我提取了几个分段 于是我们可查看它们的值 例如 快照中的第一个主分段 会将自身标记为 primary 并占用 0 到 5 的时间线范围 下一个分段为 插播 PointEvent 分段在这里表明它会占用 5 到 5 的范围 即时长为 0 此外 它还可以提供对 这个插播事件的访问权限 这个事件中还会包含作为 singlePoint 的 timelineOccupancy
最后 所选最后一个分段为 FillEvent 这个分段会占据 20 到 30 的时间线范围 且 timelineOccupancy 为 fill
了解了所有关键对象后 我们来看看如何利用 Integrated Timeline 构建一个简单的 UI 首先 我们需要创建 主要的 AVPlayerItem 然后 从 AVPlayerItem 获取 integratedTimeline 现在 我们可从时间线中抓取快照 来绘制当前状态 假设我们有一个用于 绘制 UISlider 的例程 它只需要 start、 end 和 currentPosition 我们可从快照中 获取所有这些值 我们将 start 设为 0 duration 为快照的时长 currentPosition 则是 快照的 currentTime 此时 我们有一个 简单的传输栏 它可以将填充插播 作为整个滑块的一部分包括在内 现在 我们要开始在时间线上 绘制单个点 为了获取点事件的位置 我们可对快照分段进行过滤 只包含类型为插播 且 timelineOccupancy 为 singlePoint 的分段
此时 对于每个分段 我们都可以使用分段的 timeMapping 目标 start 来绘制一个点 用它作为分段在整体时间线上的位置
最后 我们来突出显示 这些填充插播 在此 我们会过滤 快照的分段 并包含用 occupancy 为 fill 的 插播来进行表示的 那些分段 由于补充主要内容 表明该事件 不应在 UI 中加以区分 因此我们会忽略 进行了这项设置的所有插播 获取这些分段后 我们只需使用这些分段的 timeMapping 目标 并在 UI 滑块上 突出显示该片段即可
时间线可能会频繁变化 而且可能会在解析插播后 时常变化 或是在直播时 频繁更新 因此 需要监听时间线中的 snapshotsOutOfSyncNotification 以便在发生这些情况时 更新 UI 调用通知闭包时 我们可通过 userInfo 来查看原因 例如 如果原因为 segmentsChanged 我们可能就需要用新的快照 来重绘传输栏 但是 当原因为 currentSegmentChanged 时 我们可能需要更新 playerControls 或其他 UI 元素 以便转换为插播 或从插播转出
了解这个 API 和 部分示例代码后 我们来看看本讲座附带的 示例 App
在这个示例 App 中 我们提供了不同的示例 首先 我们来看看名为 “Fill Interstitial”的示例 这个示例包含一个将要在 内容 10 秒处插入的范围插播
底部的传输栏 从 10 秒开始为黄色范围 用来显示插播 播放时 播放会在预期时间 过渡到插播
现在 我们可尝试进行一些搜寻 并搜寻回主要内容中 最后 我们会 搜寻回这个插播
太棒了!
我刚才展示了 如何在 App 中构建插播 你也可以在 主要播放列表中指定插播 你可按以下方式 在服务器上描述插播 在时间线上的外观
首先是点示例 这是 HLS 插播的 DateRange 标签 为了让时间线 把它视为 POINT 我们引入了一个新属性 X-TIMELINE-OCCUPIES 其中的值已设为 POINT 此外 我们还在 DateRange 标签中 添加了一个新属性 X-TIMELINE-STYLE 它的值是 HIGHLIGHT 这表示客户端 应在 UI 表示中 标记这个点
对于范围用例 新的 X-TIMELINE-OCCUPIES 属性的值设为 RANGE X-TIMELINE-STYLE 保持与前一个示例相同
最后 对于插播在 UI 中 难以区分 的用例 X-TIMELINE-OCCUPIES 仍为 RANGE X-TIMELINE-SYTLE 则设为 PRIMARY 用来表示 App 的 UI 不应对其进行唯一标记
由于 Integrated Timeline 允许将插播作为 UI 中主要内容的 其中一部分来显示 这些插播 在同播共享期间 也可进行协调 便很有意义 因此 今年我们为协调插播 添加了支持
现在 我们来看看 插播式同播共享的运作方式 及其启用方法 此时 在一个协调后的同播共享 会话中存在两个播放器 我们的想法是 Player1 和 Player2 可在 Event1 播放期间 同时继续同步播放
对于搜寻 假设 Player1 想搜寻到 Event1 并将其发送回主要内容中 这个搜寻任务将提议给 Player2 两个播放器都会 从这个新的点开始 继续同步播放 要启用对插播的同播共享支持 其中一项关键要求是 要求插播事件 对所有参与者来说 都是共享的 换言之 本例中的 Event1 对两个播放器都应该是共享的 而且必须为同一内容 如果插播不是共享的 播放器就不会在 插播期间协调播放 让我们看看如何 告知这项要求
AVPlayerInterstitialEvent 新增的功能之一是 一个布尔值 可用于指定 内容是否会在 不同参与者或播放会话中改变 在本例中 通过将 contentMayVary 设为 false 可以表明这个插播 为静态或共享插播 而且允许对事件 进行协调
最后 我们还可以在 DateRange 标签中标记同一行为 具体方法是使用新的属性 X-CONTENT-MAY-VARY 并将它设为 NO
好了 现在我们来看看 支持同播共享的示例 App 我们在一个 FaceTime 通话中 设置了两部手机 在这个演示中 我们选择 Fill (Supplements Primary) 示例 其中包含一个在内容 第 10 秒处的插播
请注意 这两部手机一开始会保持同步 当过渡到插播时 它们仍会保持同步 接着 我们发出暂停信号 然后向后搜寻 最后则是向前搜寻
请注意 所有这些命令是如何 与这两台设备无缝协作的 总结一下 现在 你可使用 Integrated Timeline 通过插播 来打造 UI 体验 你可以自定 插播体验 具体方法是在这个 API 中 或使用新的 DateRange 属性 指定插播行为 现在 你还可以将插播标记为 在这个 API 中不可变 从而为插播素材 启用同播共享支持 如需了解其他相关讲座 请观看“探索 HLS 中的 动态前贴片广告和中贴片广告” 详细了解 HLS 插播 如需进一步了解同播共享 请观看“使用群组活动 协调媒体体验” 感谢观看
-
-
3:55 - Create a point interstitial event
// Creating a single point interstitial event let pointEvent = AVPlayerInterstitialEvent(primaryItem: playerItem, time: ten) pointEvent.timelineOccupancy = .singlePoint
-
4:07 - Create a fill interstitial event
// Creating a fill interstitial event let fillEvent = AVPlayerInterstitialEvent(primaryItem: playerItem, time: ten) fillEvent.timelineOccupancy = .fill fillEvent.plannedDuration = CMTime(value: 15, timescale: 1)
-
4:32 - Create fill interstitial supplementing primary
// Creating a fill interstitial event supplementing primary let fillEvent2 = AVPlayerInterstitialEvent(primaryItem: playerItem, time: ten) fillEvent2.supplementsPrimaryContent = true fillEvent2.timelineOccupancy = .fill fillEvent2.plannedDuration = CMTime(value: 15, timescale: 1)
-
7:14 - Draw simple transport bar with integrated timeline
// Create AVPlayerItem and obtain its integrated timeline let item = AVPlayerItem(url: ...) let integratedTimeline = item.integratedTimeline // Any time we need a new representation of the timeline state, we can request for a snapshot let snapshot = integratedTimeline.currentSnapshot // Using our snapshot, we can build a simple transport bar drawUISlider(start: .zero, duration: snapshot.duration, currentPosition: snapshot.currentTime) // Draw single-point interstitials on the transport bar let pointSegments = snapshot.segments.filter { segment in segment.segmentType == .interstitial && segment.interstitialEvent?.timelineOccupancy == .singlePoint } for segment in pointSegments { drawPoint(position: segment.timeMapping.target.start) } // Draw range interstitials on the transport bar let highlightFillSegments = snapshot.segments.filter { segment in if (segment.segmentType == .interstitial) { if let interstitialEvent = segment.interstitialEvent { return interstitialEvent.timelineOccupancy == .fill && !interstitialEvent.supplementsPrimaryContent } } return false } for segment in highlightFillSegments { let range = segment.timeMapping.target highlightRegion(start: range.start, end: range.end) }
-
8:26 - Listen to snapshotsOutOfSyncNotification to update our UI
// Listen to integrated timeline notifications to update our logic for await _ in NotificationCenter.default.notifications(named: AVPlayerItemIntegratedTimeline.snapshotsOutOfSyncNotification, object: integratedTimeline) { let reason = _.userInfo![AVPlayerItemIntegratedTimeline.snapshotsOutOfSyncReasonKey] as! AVPlayerIntegratedTimelineSnapshotsOutOfSyncReason switch(reason) { case .segmentsChanged: redrawTransportBar(snapshot: integratedTimeline.currentSnapshot) case .currentSegmentChanged: updatePlayerControls(snapshot: integratedTimeline.currentSnapshot) } }
-
11:42 - Set ContentMayVary to false for Interstitial SharePlay support
// Set contentMayVary to false for SharePlay support let event = AVPlayerInterstitialEvent(primaryItem: playerItem, time: ten) event.contentMayVary = false event.timelineOccupancy = .fill event.plannedDuration = CMTime(value: 15, timescale: 1) event.supplementsPrimaryContent = truensert code snippet.
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。