前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用于浏览器中视频渲染的时间管理 API

用于浏览器中视频渲染的时间管理 API

作者头像
用户1324186
发布2022-04-11 18:21:14
2.3K0
发布2022-04-11 18:21:14
举报
文章被收录于专栏:媒矿工厂媒矿工厂

来源:Demuxed 2021 主讲人:Jacques Blom 内容整理:张雨虹 本次演讲主要介绍了浏览器中视频渲染的时间管理,如何在 React 中实现时间状态跟踪,包括:1)采用“派生状态”的概念以实现可靠、确定性的渲染;2)通过各种技术优化性能;3)如何测试基于时间的状态(或者,如何在测试中进行时间的移动);4)如何将各种类型的媒体(视频、字幕等)与单一事实来源同步。这将帮助任何想要在浏览器中构建视频编辑器或渲染系统的人,为在其 UI 中处理时间奠定坚实的基础。

目录

  • 实现方案
    • 方案1
    • 方案2
  • 测试
    • 播放和暂停的有效性
    • 同步问题
  • 应用和总结
    • 应用
    • 总结

对于用户可以在浏览器中进行视频剪辑的软件来说,为了实现这个功能需要在项目渲染成 MP4 文件时,以一种一致的方式来同步画布上的所有不同元素。

实现方案

实现方案

总共需要两个关键状态,一个是一个布尔值,表示项目是否在播放,另一个是时间状态,表示项目是何时开始播放。其他一些组件(比如时间码、字幕)会根据播放与否以及播放的开始时间运行一个循环。当在循环中时,会利用当前的时间计算一些其他状态参数,比如哪些单词是活动的并作出相应的反应。对于视频元素,仅依靠布尔值的真假来播放或者停顿。对于像导出按钮、项目总时间的显示这类元素,将利用存储在项目状态中的持续时间属性来计算。当用户插入和删除元素时,这个属性都会进行更新。每当插入一个元素时,会重新计算当前画布上持续时间最长的元素,然后将项目的持续时间设定为该值,删除项目时也同理。因此,会有一些从核心播放状态的派生状态,比如字幕和时间码;也有一些基于状态更改的命令式调用,比如视频元素;在项目持续时间的情况下,有同步状态,比如添加元素时,需要一个主要更新函数,但还需要一个函数来以一种命令式的、直接的方式来更新描述其从属状态。

方案1

使用同步状态路由来进行实现。画布上的不同元素都代表一个不同的场景,按照场景的时间的长度对场景进行排序。这意味着每当我们从场景中添加或者删除一个项目时,就需要重新计算更新它的持续时间。这在简单情况下是可行的,但是当进行粘贴剪辑这样的动作时,虽然这个动作也改变了场景的持续时间,但是在实现上,该方案并没有重新计算这一过程,因此并不会更新场景的持续时间,导致状态不一致的问题。因此我们不仅需要将场景的持续时间存储在状态中,还要将活动的场景存在其中。当用户按下播放时,我们需要计算活动场景是什么,哪些元素应该出现在画布上。同理当播放暂停,有人删除场景时,也需要重新计算活动场景;当删除场景中的特定元素时,仍需要重新计算持续时间,但删除元素会影响场景以及更多的其他同步状态值,使得更新不能及时。这个弊端是无法控制的。

方案2

方案 2 的目标是为时间和由时间派生出的状态的改变来建立单一的事实来源。它必须在回放以及任何时间变化时工作,包括用户搜索或者擦除时。

我们的 API

我们的方案设计了一个上下文提供者(Time Context Provider),这个组件包括了任何需要访问时间的组件,并且有两个核心状态。一个是播放开始的时间戳,当没有播放时,为空值;另一个是播放偏移量,这表示项目被寻求的最后时间代码,在此基础上,可以推导出项目的当前时间,据此我们可以创建一个链接,无论项目是否处于播放状态,都可以让任意组件与当前时间相联系。这就创建了一个可靠的接口来响应当前时间。因此利用 React 来进行状态跟踪。React 擅长在依赖状态发生变化时重新运行函数。这样处理效果很好,但是也面临着性能问题。

由于 API 的设置问题,任何使用此链接的组件都会接受当前时间值。但是当前时间值每帧都会更改,这样导致几乎画布上的所有组件每一帧都会被重新渲染。在 React 中,重新渲染很慢,必须重新运行整个渲染函数,而不仅仅是依赖于时间的一小部分 UI,还会导致组件中的子组件也需要重新渲染。React 需要执行 DF 来确认是否需要在 DOM 中实际更改任何内容,因此不建议以 60fps 的速度来重新渲染。实际上,并不是每帧都需要渲染的,即使当前时间可能会改变每一帧,比如在字幕示例中,当前单词的索引并不是每一帧都发生变化的。这在方案一中并不是问题,因为我们只需要在每个需要时间的组件中运行一个循环,并且是在 React 渲染循环之外运行它,当任何类型的派生状态发生变化时,都会调用一个设置状态来重新渲染组件,所以效率很高。因此,为了解决这一问题,我们设想与其让所有这些不同的循环分散在代码库中,不如设计一个计算当前时间的中心循环,使得组件能够有效地响应,而不是每一帧都重新渲染。

useTimeSelector

由此就产生了两个 hook,第一个为 useTimeSelector,我们有一个 requestAnimationFrame,在项目播放时运行每一帧,计算当前时间,并调度时间更改事件,任何想要调用当前时间更改的组件都可以通过调用 useTimeSelector 来完成。然后该组件将在每一帧或每当时间更改时运行一个函数以确定新的结果值,如果该值发生更改,将重新渲染。整个流程中唯一真正涉及 React 的是最后一部分,因此计算成本不高。useTimeSelector 背后的想法是把昂贵运算改为廉价运算,当廉价运算返回相应结果时再触发其他运算,在这种情况下计算的代价是重新渲染。无论是何原因,一旦当前时间发生变化,就调用 useTimeSelector,以确保方案的可靠性。

另一方面需要保证的是能够依据项目全局时间正确的播放和暂停。由此创建了第二个 hook useTimeEffect

useTimeSelector

这个函数非常简单,它用于当前时间改变时来触发副作用。其工作原理类似于 useTimeSelector,可以向其传递一个函数,该函数在当前时间更改时调用,唯一的区别是 useTimeEffect 没有返回值。

测试

播放和暂停的有效性

理想情况下,按照现实生活中的使用方式来进行测试:开始播放,等待一秒钟,然后检查当前时间以确保它已设置到一秒钟;然后暂停,再等待一秒,确保暂停状态正确、当前时间正确。但是实际上我们并不希望真的花费一秒来暂停或者播放进行测试。因此我们采用了一种方法来模拟日期,利用 MockDate 库,它的工作原理是渲染 hook,将时间设置为零,开始播放,然后我们可以将日期设置为 1000,将时间向前移动一秒,进行检查测试;然后暂停,时间再移动一秒,再次检查时间和状态。

但是我们忽略了 useTimeEffect 和 useTimeSelector 在项目播放时依赖于 requestAnimationFrame,因此不能在 Jest 中运行,Jest 不支持 requestAnimationFrame。为了解决这一问题,需要用设置的超时替换 requestAnimationFrame 并使用 Jest 的 useFakeTimers 功能,在 Jest 的超时中关闭实时。

实现方案

每次测试之前,启用FakeTimer,用一个自定义的通过设置超时达 50ms 实现的 requestAnimationFrame 替换实际的 requestAnimationFrame,在测试中,可以看到有测试挂载了 useTimeEffect 并且有某种测试函数。我们需要确保无论时间何时发生改变,测试函数都会被调用。所以首先需要将时间设置为 0。然后用 usePlayback 启用播放,将时间提前 50ms ,并通过 Jest 移动 50ms 来触发一帧,这将触发之前设置的超时调用,这就提供了一种逐帧推进时间的方法,以便我们可以更加精细地进行测试。使用这种“时间移动”的方案,可以对任何依赖于时间系统的东西进行测试,包括确保视频被搜索到正确的时间、正确的标题词被突出显,所有的测试都可以比实际时间运行得更快。

同步问题

在工程中,需要保证视频元素与其他元素的同步性,因为一旦按下 Play,系统会立即记录播放开始值,时间就开始推进,但是页面上的 HTML5 视频元素是由于正在缓冲、浏览器正忙、蓝牙延时等问题会推迟几秒开始播放,因此实际开始播放的时间比内部存储的时间要晚得多。

解决方案

开始播放时,时间开始推进,页面上的视频元素都开始周期性地回调时间系统来告知时间系统它们的内部状态。因此,如果两者之间有任何偏差,视频元素将告知时间系统按照实际寻找正确的时间。所以,基本上,视频元素由于时间系统而开始播放,并且时不时地回调按照实际来更新实时时间,并保持时间系统与视频元素的同步。Web Timing 将是一个保持同步非常有用的 API。

应用和总结

应用

逐帧渲染:现在的工作方式是在浏览器中打开画布,播放它,并且屏幕录制页面。但是会面临速度和帧率问题。但是利用我们的时间 API 可以逐帧推进时间,实现逐帧渲染。动画:可以利用构建的时间系统来创建基于插值的动画,对于给定的时间戳或者给定的帧,输出特定的 CSS 值。比如从时间 0 到 200ms,可将不透明度的值从 0 插入到 1,实现 200ms 内的淡入淡出动画。

总结

在浏览器中处理时间的最佳方式是以声明的方式直接从时间派生 UI 元素的属性,构建时间系统的最佳方式是创建一个时间的单一来源,采用一种标准和集中的方式来处理时间变化引起的其他效应。充分利用用于构建 UI 的库,但不能过度使用,并且把经常运行的计算留在昂贵的渲染周期之外。

最后附上演讲视频:

http://mpvideo.qpic.cn/0b2eu4aacaaayqapytyvf5rfbj6dagtqaaia.f10002.mp4?dis_k=508c63ba22ae747bfe28468b92cd6883&dis_t=1649672442&vid=wxv_2281970160456646662&format_id=10002&support_redirect=0&mmversion=false

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-02-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 媒矿工厂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实现方案
    • 方案1
      • 方案2
      • 测试
        • 播放和暂停的有效性
          • 实现方案
        • 同步问题
          • 解决方案
      • 应用和总结
        • 应用
          • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档