HTML5画布在更改精灵表(逐帧视频播放)时出现卡顿。

5
我需要在移动设备上使用HTML5播放具有透明背景的视频。在Windows Phone上,我可以捕获视频标签的当前帧并将其显示在画布中。但是在Android和iOS设备上无法使用(我想是出于安全原因?)
我的解决方案是使用FFMPEG拆分.flv,然后将这些帧拼接成大型图像,就像精灵表一样。 问题是当我切换到新的帧表时动画会“卡顿”。我只是通过视觉和控制台(通过记录何时更改当前精灵表行来确定)进行了检查。我测试了更改精灵表时它如何卡顿,以及仅循环同一表时它不会卡顿。
我预先加载了所有图像:
var frameImages = [];

for(var i = 0; i < 35; i++)
{
  frameImages.push(new Image());
  frameImages[i].src = 'frame' + i + '.png';

  console.log(frameImages[i].src);

  frameImages[i].onload = function()
  {
    // Notify us that it's been loaded.
    console.log("Image loaded");
  }
}

然后像这样播放:

processFrame = function()
{
    outputCanvas.width = outputCanvas.width;
    output.drawImage(frameImages[currentFrame], (curCol*153), (curRow*448), 153, 448, 0, 0, 153, 448);
    curCol += 1;

    if(curCol >= maxCol)
    {
      curCol = 0;
      curRow += 1;

      if(curRow >= maxRow)
      {
        curRow = 0;
        currentFrame++;
      }
    }
  }
}

var mozstart = window.mozAnimationStartTime;

step = function(timestamp) {

  var diff = (new Date().getTime() - start) - time;
  if(diff >= frameDelay)
  {
    processFrame();
    time += frameDelay;
  }
}

我在使用Chrome v 23.0.1271.97 m浏览器和Nexus 7上的Chrome尝试过此操作。
您可以在这里查看效果:
http://savedbythecode.com/spokes/mozanimation.php - 这是使用mozAnimationStartTime
以及http://savedbythecode.com/spokes/newplayer.php - 这是使用从http://www.sitepoint.com/creating-accurate-timers-in-javascript/调整每一步的常规JS计时器。
有什么想法吗?问题清晰吗?
谢谢, Kevin

1
我的意思是,如果这让业务受到损害,那么付给他费用的人是不会继续付费的。他的业务在增长,而不是缩小。最重要的是,你必须牢记观众的需求。我们对技术非常熟悉,我们这一代人也是如此。我的客户所涉及的企业通常面向年龄较大、不太熟悉技术的受众群体。有时候,甚至会让他们感到不知所措。网站上有人与他们交流会让他们更加舒适,并且更容易消化信息。再次强调,这不是我的事业。 - Banath
1
在Chrome中,它对我来说运行得很好,但只有在加载所有帧之后,因此您可能需要延迟动画,直到所有精灵完全加载。不确定您是否在其他浏览器中进行了任何调试,但是在Firefox中我会出现动画延迟(声音不同步),而在Opera中我既看不到动画也听不到声音。它在IE8中不起作用,但这是可以预料的LOL。也许为不符合HTML5标准的IE浏览器使用背景声音,并确保不调用不支持的函数?虽然我可以预见到视频+音频同步问题。到目前为止做得很好!;) - TildalWave
这实际上只需要在Android和iOS设备上运行,客户已经有一个类似于我的Windows Phone解决方案的现有Web解决方案。所以我不必担心支持IE :)。 - Banath
我冒昧使用TweakPNG工具调查了您动画中使用的一个精灵图像,它看起来有点奇怪,显示出许多块,表明它们没有渐进编码(它们似乎是PNG所谓的“交错”)。您能否尝试将它们重新编码为渐进式(非交错),并查看是否有助于在整个图像加载而不是第一个可见帧加载时触发onload函数?这也应该节省一些带宽,使文件稍微小一些。您还可以使用optipng工具进行进一步的文件大小优化(约10%)。干杯! - TildalWave
请忽略我的上一条建议。我刚刚使用你的精灵之一进行了测试,结果如预期一样工作正常,这意味着即使它们是交错的,也没有任何问题。通过进一步调查你的代码,我注意到你并没有真正等待所有图像加载完成,仅在它们加载完成时向控制台添加一行。我还遇到了一些错误提示代码中的其他问题,但我需要进一步调查,我只想确保你不会在图像格式上浪费时间,因为那不是问题。待续 - TildalWave
显示剩余4条评论
3个回答

2
这里有另一种不太正统的解决方案 - 使用视频作为画布输入,并使用getImageData对每个帧进行绿幕处理。
基本上,你需要准备好你的视频标签和画布。
<video id='source' style='display: none;'>
   <source>whatever.mp4</source>
   <source>whatever.ogg</source>
</video>
<canvas id='screen' width='videoWidth' height='videoHeight></canvas>

然后您设置绘制功能。
var t = null;
document.getElementById('source').addEventListener('playing', function(){
   var vid = this;   
   t = setInterval(function(){
       if (vid.paused || vid.ended){
          clearInterval(t);
          return
       } else {
          drawFrame();
       }
   }, 20)
}, false)

你的绘制框架函数是:
function drawFrame(){
   var ctx = document.getElementById('screen').getContext('2d'),
       vid = document.getElementById('source');

   ctx.drawImage(vid, 0, 0);
   var tolerance = 0.2,
       bgColor = [0, 255, 0], //or whatever 
       imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height),
       len = imageData.data.length;

   for (var i=0; i<len; i+=4){
      var r = imageData.data[i],
          g = imageData.data[i+1],
          b = imageData.data[i+2];

      //There's lots of different color comparing methods, just check google
      var dif = colorCompare([r, g, b], bgColor);
      if (dif < tolerance){
         imageData.data[i+3] = 255;
      }
   }

   ctx.putImageData(imageData, 0, 0);
}

您可以尝试不同的方法来达到所需的性能水平。对于您相对较小(在尺寸方面)的视频,性能不应该是一个问题。您可能需要使用缓冲画布来避免屏幕闪烁,但我觉得不太可能。您可以调整公差变量以避免出现任何光晕效果。
并不是说这是最好的解决方案,但它绝对是可行的解决方案,并且如果您想通过仅更改输入视频而无需进行任何额外的工作来更改视频,则可以使用此解决方案。

2
在Android和iOS设备上不允许这样做。这基本上是我为Windows Phone解决方案所做的。 - Banath
@hobberwickey -- 是啊,这是个很酷的想法,但安全限制真的需要更多的辨别力。比如说,只有从用户设备(如FaceTime)传来的视频被拒绝,否则,它会打破什么安全性呢? - Cris Stringfellow
@hobberwickey -- 之前没时间找文章,但你现在做视频和画布透明度的方式是困难且更糟糕的。假设你已经设置好了透明度的视频 -> http://jakearchibald.com/scratch/alphavid/ (今天在Chrome中无法工作;不确定为什么)。当视频看起来像这样 -> http://savedbythecode.com/transparentvideo.png 时,就已经设置好了透明度的视频,其中上半部分是用户应该看到的视频,下半部分是要以白色显示的视频部分。如果视频还没有像这样设置好,那么你的方法将是最可行的。 - Banath
@Banath我不知道,它们其实并没有太大的区别。唯一的区别是颜色比较,如果透明背景以相当一致的方式从视频中获取,则可能非常简单。它们都循环遍历图像数据,我的看起来像是几个drawImage调用。我之前没有意识到安全限制。这很愚蠢,为什么他们不实现与图像相同的跨域策略呢? - hobberwickey

2
很酷的代码。为什么你不直接使用视频标签(参见选项6)?无论如何,我会列出我能想到的问题,并希望能帮助你:
  1. ++ 改为 += 1:为了与您之前的代码保持一致,并且因为 channeling Doug Crockford's benevolent ghost 可能有助于消除 JavaScript 代码中的故障。

  2. 您将一堆图像拼接成一个单独的“精灵表”。然后,当您想要进入这些表的下一个表时,代码会挂起。因此,作为测试,删除所有循环遍历每个帧中的子图像的循环,并在每次渲染时直接将 frameImages 数组中的每个帧输出到画布上,以查看它是否仍然挂起。这样做的结果将让您知道您的代码是否可以进入 frameImages 的下一个帧,或者是否无法进入。这可能会让您更接近错误的源头。

  3. currentFrame 是否使用 var 声明并在 processFrame 范围内?如果没有,请将 var currentFrame = 0; 放在定义 function processFrame(... 的位置之前。

  4. 当您说“然后我这样播放”时,您是使用 promise 还是在所有 onload 都触发后调用您的动画开始函数?一种方法是:每次 onload 触发时递增一个计数器,并且每次检查计数器是否等于所需的总加载数量,只有在这种情况下才调用您的动画开始。 我猜测这是缓存问题,因为这些问题似乎在不同的浏览器之间最容易变化,这是由于不同的内部机制造成的。

  5. 尝试在 drawImage 调用之间清除画布,并在更新 currentFrame 时清除画布,因为当您更改图像时,它可能有助于重置画布的内部状态。

  6. 您可以使用 HTML5 视频标签进行 alpha 和透明度。只需使用一些 svg 变换即可完成,如 this SO answer 中所述。


@Banath -- 好的,我在第二个单词上遇到了困难,我会再试一次。为什么要清除画布?嗯,我记得过去可能通过简单地清除画布来消除一些JavaScript图形故障。因为,正如您所知,画布是一个状态机,并且呈现是作为状态步进的一部分调用的。这当然适用于绘制顶点和形状,我似乎还记得它也适用于drawImage。在内部,画布渲染操作被合并在一起,然后为了效率而执行复合操作,但是这方面的确切细节是经过调整的。 - Cris Stringfellow
@Banath -- 关于缓存:你怎么确定呢?如果你只是通过输出“图像已加载”来测试,那是不够的。我知道这一点,因为我也做过。如果你想消除缓存的可能性,你必须执行所有的渲染,然后开始你的动画。唯一严格的方法是(即使onload也不可靠,但仍然)保持一个检查有多少图像已经触发了onload事件,然后在所有图像都加载完成后调用你的启动动画函数。从onload处理程序内部调用。你明白我的意思吗? - Cris Stringfellow
@Banath -- 由于其同步性,它可能会在该图像完全加载之前达到currentFrame == 1。如果我没记错的话,您需要将这些图像加载到DOM中。 即使这不是严格正确的,也可以尝试使用visibility hidden类将HTMLImageELements添加到DOM中。然后,您就可以更加信任onload方法了。这很可能是缓存问题,因为这些问题是最棘手的调试问题。 - Cris Stringfellow
@Banath -- 我希望如此。但你的解决方案很棒,你做得很好。感谢加分,但你尝试了我提到的其他修复方法吗?有什么起作用的吗? - Cris Stringfellow
谢谢Chris。不,我还没有尝试过它们,我周末有其他工作要忙。我授予你赏金是因为你的回答可能是唯一可行的,你花了时间去做它,并向我展示了SVG剪辑,希望能奏效。 - Banath
显示剩余2条评论

0

不知道你是否看过这个......但是你有没有考虑过使用 jQuery 队列?当我在浏览器中加载它时,我发现一些框架没有显示出来,或者变成空白,然后又回来了,等等。我以前使用过队列来进行同步处理,例如我想要按特定顺序“链接”起来的 ajax 调用...所以这可能是一个你可以尝试以同步方式加载每个“图片框架”的区域,确保每个框架按正确的顺序加载的领域。

http://api.jquery.com/queue/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接