HTML5画布从视频中绘制的drawImage在第一次绘制时未显示

6
我有一个视频元素,上面覆盖着一个画布。我设置了一个绘图工具,在视频上进行绘制,并有一个保存按钮,该按钮会从视频和画布分别获取图像并保存为一个合成帧。但是,第一次按下保存按钮时,我只得到了画布drawImage的结果,没有看到视频。在随后的保存中,我才能正确地显示两个图像层叠。我认为这可能是视频图像加载的问题,但在我点击保存之前,视频已经完全加载了,我甚至可以提前加载帧,并在第二次保存时正常工作。
以下是代码...
<div style="width:960px; height:540px; display:inline-block;">
    <video id="video" src="media/_tmp/AA_017_COMP_v37.mov" width="960" height="540" ></video>
</div>
<canvas id="canvas" width="960" height="540" style="position:absolute; top:40px; left:9px; z-index:100;"></canvas>
<input type="button" value="save" id="btn" size="30" onclick="save()" style="float:left; padding:4px; margin-right:4px;" >
<div id="saved" style="border:1px solid #000; position:absolute; top:626px; left:10px; bottom:40px; width:958px; overflow:auto;">SAVED:</div>


function save() {

    //COMP CANVAS OVER VIDEOFRAME
    var video = document.getElementById("video");
    var currentFrame = Math.floor((<?php echo $mov_frames ?> / video.duration) * video.currentTime);

    var compCanvas = document.createElement('canvas');
    compCanvas.width = video.width;
    compCanvas.height = video.height;
    compContext = compCanvas.getContext('2d');
    compContext.drawImage(video, 0, 0);
    compContext.drawImage(canvas, 0, 0);
    var dataURL = compCanvas.toDataURL();

    $("#saved").append('<div style="width:954px; border-bottom:1px solid #000; padding:2px 2px 0 2px;"><img id="compFrame_'+currentFrame+'" width="180" height="90" src="'+dataURL+'" />Frame: '+currentFrame+'</div>');
}

你能提供一个 jsfiddle 来展示问题吗? - Oriol
这里有一个jsFiddle链接。唯一的问题是我不知道为什么跨浏览器视频链接无法绘制到画布上...但是代码已经在那里了。 - Andrew Sinagra
保存函数位于$(document).ready之外,但该函数仅在页面完全加载后才被调用。 - Andrew Sinagra
  1. 可能是由于某个缓冲区交换问题造成的一个偏移错误。您能确定所打印的图像是当前的,而不是先前的吗?还有,您已经想到了一个想法。
  2. 另外,将视频播放在画布上会怎样?这样您就可以100%确定手头有哪些图像。
- GameAlchemist
另外,您能否查看此代码片段是否按预期生成帧(对我来说似乎可以工作):http://jsfiddle.net/AbdiasSoftware/zL8KC/1/ - user1693593
显示剩余5条评论
4个回答

3

我遇到了同样的问题,并尝试了上一条评论中提供的解决方案。但是它对我来说还没有完全起作用。

后来我想到了一个解决方法:

在我的代码中,除了从视频绘制的drawImage之外,还有一些其他的绘图内容。

context.rect(0, 0, 50, 50);
context.fillStyle = 'black';
context.fill();

即使在“canplaythrough”事件后重新绘制第二次,使用这些代码仍然不起作用。

因此,我删除了上面的三行代码,并在“canplaythrough”事件后进行了第二次绘制。 然后它就可以工作了。

简单地说,对我有效的代码如下:

context.drawImage(video, 0, 0);
video.addEventListener('canplaythrough', function () {
    context.drawImage(video, 0, 0);
});

3
我最近也遇到了同样的问题,并通过在“虚拟”画布上绘制视频来解决它,然后进行“真正”的处理。这样做很有效,因为我代码中所有尝试解析视频的方法都会等待直到视频触发了“canplaythrough”事件。我将“canplaythrough”事件处理程序包装在一个Promise中,只有当收到“canplaythrough”事件并尝试将视频绘制到虚拟画布上时,才会解决Promise。这似乎是Safari错误的一个有效解决方法

例如:
var readyPromise = new Promise(function(resolve) {
    video.addEventListener("canplaythrough", function() {
        var canvas = document.createElement("canvas"),
            context = canvas.getContext("2d");

        context.drawImage(video, 0, 0);

        resolve(video);
    });
});

readyPromise.then(function() {
    // NOW manipulate the video, draw it onto a canvas, etc
});

它在Chrome中不再工作。我看到canplaythrough被触发,但是drawImage没有画出任何东西。在canplaythrough内设置100毫秒的超时时间有所帮助,但也许有更少hacky的方法。 - deathangel908

2
由于这是在OSX上使用Safari,有一些坏消息:
引用: 注意:iOS目前不支持将视频作为canvas drawImage()方法的源。
如果这是过时的信息,我不知道,但无论哪种情况,当前实现都存在问题。如评论所述,问题不太可能与编解码器相关,因为它也发生在其他格式上。 fiddle消除了toDataURL()方法作为源,因此剩下的就是drawImage()方法。
由于在其他平台上提供的代码可以工作,并且从外观上看,这也不是问题的来源,因此您正在查看OSX平台Safari浏览器可能存在的问题(错误)。
上面引用的官方文档也指出不支持此功能,因此这可能是一个早期实现,仍存在一些问题。
在这方面你能做的不多,只能等待并报告它作为一个问题(我查找了已报告的问题,但没有找到任何问题,所以建议你这样做)。

我相信你是正确的。在OSX上尝试使用Firefox以及在Win7上使用Chrome和Firefox似乎都能产生正确的行为。 - Andrew Sinagra
实际上,回顾您提供的源链接后,我意识到iOS特别不受支持。桌面系统上的Mavericks并非iOS。但是,它似乎与Safari直接相关,因此可能仍然存在一个错误。 - Andrew Sinagra

0

这些方法都没用。看起来谷歌浏览器(目前版本为97)在后台进行了一些不好的优化。

  • canplaythrough 看起来很懒,不能保证第一帧被加载
  • 如果 video 元素有样式 display: none,那么它也不会被加载,因此绘制也无法工作。
  • 视频元素应该附加到 DOM 上
  • 画布元素不应该有任何与其宽度或大小相关的 CSS。

唯一有效的方法是使用 setTimeout,这是一个不好的 hack,但它就像这样。

fileinput.addEventListener('change', function (e) {
    var ctx = canvas.getContext("2d");
    video.addEventListener('loadedmetadata', function () {
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        setTimeout(() => {
            ctx.drawImage(video, 0, 0);
        }, 100)
    });
    video.querySelector("source").setAttribute('src', URL.createObjectURL(fileinput.files[0]));
    video.load();
});
video {
    opacity: 0;
    width: 0;
    height: 0;
} 
<input type="file" id="fileinput" name="file">
<video id="video" controls>
  <source type="video/mp4">
</video>
<canvas id="canvas"></canvas>


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