由于开发人员充分发掘了 HTML5 的潜力,对于消费者来说,Web 不仅变得更具吸引力,而且提高了工作效率。在本篇客座博文中,Plain Concepts 的 Anton Molleda 将介绍他在开发 Vcylone 时的切身感受和经验教训,Vcylone 是一种基于 HTML5 和下一代浏览器(如 Internet Explorer 10)中的许多新功能而构建的社交视频编辑体验。Vcyclone 依托于指针事件、多点触控手势、硬件加速的画布和 CSS3 等技术而构建,这使该网站感觉更像是一个应用。
— Internet Explorer 小组项目经理 Rob Mauceri
大家好,
我叫 Anton Molleda,在 Plain Concepts 工作。在最近的几个月中,Internet Explorer 团队一直在不断与社交视频新创平台 Vyclone 的杰出的工作人员共同协作。作为 Web 开发人员,能够有机会推动 Web 的发展是一件令人非常激动的事情。幸运的是,我有幸能够为这一项目伸出援助之手。今天,我希望与您分享一下在我们使用 HTML5 和 JavaScript 为 Vyclone 创建 Web 视频编辑器时所积累的一些重要经验!
Vyclone 是一个社交视频平台,它允许您在共享时共同创建、同步和编辑多个视图,而毫不费力。
在开始开发 Vyclone 时,它仅专注于移动设备。但是,我们很快就意识到在能够使用手机获得出色的录制体验的同时,编辑视频会受到屏幕尺寸和设备功能的限制。在最近几年里,由于现代浏览器取得了长足的进步,HTML5 已成为打造这一新工具的可行途径。
Vyclone 的 Web 编辑器的核心由以下三部分组成:
视频预览:允许观看用户正创建的低质量剪切视频(在左侧)
视频网格:向用户显示所有可用视频源,同时显示特定时间点的内容(在右侧)
时间线:表明正在播放的视频源的过程的线性视图。在某段时间内播放的视频源称为剪切视频(显示于播放器控件上方)
当用户播放视频并开始向时间线中添加新的剪切视频时,视频预览将进行切换以反映新的源,而视频网格将使用三角角标突出显示源文件,以便用户可以识别选定的视频。
在构建该工具的过程中,我们遇到了巨量的视频操作、性能恢复和用户体验等方面的非常有趣的挑战。让我们来深入了解一下我们是如何在 Web 上实现这一功能的。因此,我们使用了视频、画布和 requestAnimationFrame (RAF)。我们在后台播放一个视频,并在每个 RAF 中将活动的源提取到画布(在视频预览中),或者计算视频的新尺寸并定位到视频网格中。
到目前为止一切顺利,但当我们让用户与其交互时情况又如何呢?例如,当用户前后移动时间线,或者添加/删除视频源(剪切视频)时的情况如何?当我们首次制作原型时,我们认为标准方法是一旦触发事件,即开始对该事件进行关注 - 因为这是我们所了解到的方法,对吗?
但当这些事件每秒钟触发几十次,甚至几百次时,将会发生什么情况呢?另外,如果处理程序需要更新 UI 怎么办?我们真要在有时增量变化小于 1 个像素时强制布局每秒钟刷新 130 次吗?这将大幅降低性能!
如果您的计算机拥有 i7 和 8GB 的 RAM,那么或许可以承担这样的计算能力。但是,使用较旧设备的用户怎么办?或者使用 ARM 设备的用户怎么办?这些用户将无法获得相同的体验,并且会看到网站的响应时间会逐渐慢下来。
我们的首要方法是在 RAF 中将操作加入队列,但此方法存在一些问题,例如,您可能会在 RAF 中加入同一 "tick" 的相同函数,这会让事情变得更加糟糕。为了解决这一问题,我们需要在首要方法中引入一个变量,以告诉我们是否操作已经加入队列。具体如下:
var queued = false; function myAction(){ //your awesome code here queued = false; } function onEvent(evt){ if(!queued){ queued = true; requestAnimationFrame(myAction); } }
该代码非常不错,但仍然有缺陷。如果您正在进行与事件位置(鼠标或指针)和增量有关的操作,那么该方法可能会使您感到非常纠结。我们在时间线中使用的解决方案是累积事件值并在 myAction 上进行处理:
var deltaX = 0, queued = false; function myAction(){ //your awesome code here uses deltaX deltaX = 0; // we reset the deltaX so it can be incremented // next time onEvent is executed queued = false; } function onEvent(evt){ if(!queued){ queued = true; deltaX = evt.translationX; // in the case of a pointer, if you are // using a mouse you will have to do some // magic with pageX or similar :) requestAnimationFrame(myAction); }else{ deltaX += evt.translationX; } }
借助该方法,您应该几乎已经准备就绪了。我们不断添加新的功能,随后注意到一些新的问题又出现了。
通过在适当的时候处理每个 requestAnimationFrame 中的这些事件,我们可以获得更高的响应速度,而不会影响计算能力。但是,由于 requestAnimationFrame 按顺序执行函数,它们会被加入队列,因此有时我们要在清除之前进行提取,或者在不必要的时候移动时间线,而我们不得不创建大量的复杂代码来确保函数按照我们需要的顺利来执行。
我们看到该代码并不是非常友好,并且我们要丢失一些周期,等待执行其他操作,因此我们决定再次改变我们处理输入的方式。此时,我们认为这是一种游戏循环。如果您不熟悉(简单)游戏体系结构,游戏循环基本上是一个连续的循环,可在不考虑用户交互的情况下执行,并在发生不同的事件和操作时进行拆分。从 Wikipedia 文章“游戏编程”,使用伪代码的简化游戏循环类似于如下所示:
while( user doesn't exit ) check for user input run AI move enemies resolve collisions draw graphics play sounds end while
这正是我们所需要的。通过利用 RAF,我们创建了连续执行的 tick 函数,并且在此 tick 函数中,我们根据先前的用户输入或其他因素决定我们必须做些什么。
视频网格的简化 tick 具体如下:
function tick(){ //we clean if we've changed the size of the quadrant if(needsClean){ cleanCanvas(); } // if we have to change the quadrant's frame because we are // the active one (or the opposite) if(newFrame){ drawFrame(); // we draw just the frame in a separate canvas so it // doesn't need to be calculated all the time, and it // is still faster than copying from an image } //we draw the new frame if we are playing or seeking if(dirty){ draw(); drawFrameInQuadrant(); } requestAnimationFrame(tick); }
needsClean、newFrame 和 dirty 等值将在事件处理程序中进行更新(如用户搜寻内容或播放视频等情况)。
正是这一点改变了我们关于用户交互的思维方式,继而转向了游戏循环,使我们改进了性能并简化了我们编辑器中的代码。
如果您正在构建一些需要高交互性,并会收到大量用户输入的内容,请考虑一下使用游戏循环如何可以简化您的生活,并使您大受裨益!对于我们,情况的确如此。如果您还没有机会了解 Vyclone 的全新 Web 编辑器(如果我不这样说自己),请立即开始行动!在 Vyclone.com 中,单击任意视频上的“Remix”,您即会看到 Web 编辑器。使用鼠标或触控输入也可以获得同样好的效果。我强烈建议您立即使用 Surface Pro 进行体验!
希望您喜欢!如有疑问,可通过在下方评论的方式与我联系!
— Anton Molleda, Plain Concepts