canvas.toDataURL() 安全错误 操作不安全

44

在上传视频到服务器之前,我正尝试获取屏幕截图并将其保存为PNG格式,但遇到了以下问题:

在此输入图片描述

希望您能解决我的问题...

/*Output image show view*/
$('#file_browse').change(function(e){
    getVideo(this);
});

var capbtn = document.querySelector('#video_capture');
var video = document.querySelector('video');
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
var w, h, ratio;

video.addEventListener('loadedmetadata', function() {
    ratio = video.videoWidth / video.videoHeight;
    w = video.videoWidth - 100;
    h = parseInt(w / ratio, 10);
    canvas.width = w;
    canvas.height = h;           
}, false);

capbtn.addEventListener("click", function(){
    context.fillRect(0, 0, w, h);
    context.drawImage(video, 0, 0, w, h);
    var objImageData = canvas.toDataURL("data:image/png;");  
});

function getVideo(input) {
    if (input.files && input.files[0]) {
        var reader = new FileReader();
        reader.onload = function (e) {
            var video = document.getElementsByTagName('video')[0];
            var sources = video.getElementsByTagName('source');
            sources[0].src = e.target.result;
            video.load();
            video.style.display="block";
        }
        reader.readAsDataURL(input.files[0]);
    }
}
<input id="video_capture" type="submit" value="Capture" />
<video id="video_view" controls>
    <source src="movie.mp4" type="video/mp4">
</video>
<canvas width="300" height="300"></canvas>

使用 windowURL.createObjectURL(input.files[0]) 可能会更加流畅/快速,并且在权限方面可能更加宽松。 - dandavis
Chromium 给了我比 Firefox 更详细的错误提示:"未捕获 DOM 异常:无法在 'HTMLCanvasElement' 上执行 'toDataURL':污染画布可能无法导出。" https://dev59.com/pWEh5IYBdhLWcg3wMA0t - baptx
6个回答

51
听起来像是CORS问题。
视频和Web服务器位于不同的源上。
如果您可以让视频在响应中包含“Access-Control-Allow-Origin:*”头,并且您可以设置video.crossorigin =“Anonymous”,那么您可能可以做到这一点。
我使用Charles Web Proxy将标头添加到我想要使用的任何图像或视频中。
请参见https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image 另请参见https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes 这是一个使用图像的Fiddle示例:http://jsfiddle.net/mcepc44p/2/
var canvas = document.getElementById("canvas").getContext("2d");

var button = document.getElementById("button");

var image = new Image();
image.crossOrigin = "anonymous";  // This enables CORS
image.onload = function (event) {
    try {
        canvas.drawImage(image, 0, 0, 200, 200);
        button.download = "cat.png";
        button.href = canvas.canvas.toDataURL();        
    } catch (e) {
        alert(e);
    }
};
image.src = "https://i.chzbgr.com/maxW500/1691290368/h07F7F378/"

这是你要找的吗?


13
这应该是被接受的答案。片段中以下代码行起了关键作用。image.crossOrigin = "anonymous"; // This enables CORS(注:CORS表示跨来源资源共享,"anonymous"意为匿名) - Sudi
7
这不应该被接受为答案,因为它不能处理视频,而这正是提问者所要求的。 - joshcomley
上帝保佑你,你刚刚救了我的命 ;) - Mehdi Sabouri
这个解决方法对于像 i.chzbgr.com 这样设置了完全允许的CORS的网站是不必要的。你可以直接使用 <img> 标签。然而,对于 i.imgur.com,似乎需要这个解决方法,因为服务器将CORS方法限制为 GET, OPTIONS - Brock Adams
1
对我来说,它不适用于视频。我已经设置了CORS标头,并使用curl -I进行确认,确保标头确实设置。我想从视频中提取一帧并在图像中显示它。当调用video帧的toDataURL()时,我遇到了错误。 - Simon Hessner
显示剩余3条评论

16
这是因为涉及到同源策略。简单来说,如果从另一个不同源/站点加载的内容使用画布绘制视频数据,则不允许访问该视频数据。
在画布上绘制视频数据会将origin-clean标志设置为false,这将防止您以任何方式获取图像数据。
请参阅toDataURL获取更多信息。

2
那么怎么做呢..? - MartianMartian
所以基本上这不能用于不同来源的视频? - MartianMartian

12

此外,在进行一些实验后得出结论,对于iOS来说,似乎应该将.position .crossOrigin参数放在.src之前。


// Webkit will throw security error when image used on canvas and canvas.toDataUrl()

return new Promise((resolve, reject) => {
  let image = new Image();
  img.onload = () => {resolve(image)}
  img.src = `${url}?${Date.now()}`;
  img.crossOrigin = ""
})

// Webkit will not throw security error when image used on canvas and canvas.toDataUrl()

return new Promise((resolve, reject) => {
  let img = new Image()
  img.onload = () => {resolve(img)}
  img.crossOrigin = ''
  img.src = `${url}?${Date.now()}`
})

1

您好,我有同样的问题,在一个展示视频的系统中,我的用户需要在选择视频时创建缩略图,但是出现了安全错误。

这个解决方案只适用于您可以修改服务器的情况,然后您可以使用CORS发送视频、字体等内容。因此,您需要编辑httpd.conf(从您拉取视频的服务器的Apache配置)。

<IfModule mod_setenvif.c>
    <IfModule mod_headers.c>
        <FilesMatch "\.(mp4)$">
            SetEnvIf Origin ":" IS_CORS
            Header set Access-Control-Allow-Origin "*" env=IS_CORS
        </FilesMatch>
    </IfModule>
</IfModule>

在您的网页或应用程序中,向视频标记添加:crossOrigin="Anonymous"

这是我的代码片段,之后一切都能正常工作。

document.getElementById('video_thumb').innerHTML =
             '<img src="'+data_uri+'"  width="250"
                        height="250px" crossOrigin="Anonymous">';       

使用此方法,canvas.toDataURL()不会抛出错误。

谢谢!这也适用于视频标签:<video src=... crossOrigin="anonymous">。这将允许在视频帧上调用 toDataURL - Simon Hessner

1
在我们的绘图应用中,将 src 之前设置 attribute 解决了问题:https://www.trumpgraffitimemes.com/
useEffect(() => {
    const img = new Image();
    if (picdatanew.length !== 0) {
      img.setAttribute("crossorigin", "anonymous");
      img.src = picdatanew[picID].webformatURL;
    
      setCanvasSize({
        width: picdatanew[picID].webformatWidth,
        height: picdatanew[picID].webformatHeight,
      });
      setPicturedata(img);
      setWholedata([]);


      setTimeout(() => {
        contextRef.current.drawImage(
          img,
          0,
          0,
          picdatanew[picID].webformatWidth,
          picdatanew[picID].webformatHeight
        );
        var image = canvasRef.current.toDataURL("image/jpg");
        setMyImage(image);
      }, 2000);
    }
  }, [picID, picdatanew]);

0
在我的情况下,问题与来自我的网站之外的图标以及选择输入的存在有关。您的视频容器可能包含相同的项目。为了解决这个问题,我必须在截屏之前隐藏选择输入和图标,然后在截屏之后再显示它们。然后,canvas.toDataURL 就可以完美地工作,不会抛出安全错误。
截屏之前:
$("select[name='example']").add(".external-icon").hide();

截图后:

$("select[name='example']").add(".external-icon").show();

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