Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML5 video screenshot via canvas using CORS

I have an issue getting screenshots of videos in Chrome, and I've exhausted all the internets and all Stackoverflow answers on it; no luck.

No matter what I try, when I try to use the canvas element to take a screenshot of a video that is on a different domain, or even just a different port, then I end up with a Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported. error.

Here is my setup:

Web app URL
http://client.myapp.com/home.html

CDN URLs (I've tried both)
http://client.myapp.com:8181/somevideo.mp4
http://cdn.myapp.com/somevideo.mp4

Headers sent back with MP4 from CDN:

Accept-Ranges:bytes
Access-Control-Allow-Origin:*
Access-Control-Expose-Headers:x-ms-request-id,Server,x-ms-version,Content-Type,Last-Modified,ETag,x-ms-lease-status,x-ms-lease-state,x-ms-blob-type,Accept-Ranges,Content-Length,Date,Transfer-Encoding
Content-Length:5253832
Content-Range:bytes 48-5253879/5253880
Content-Type:video/mp4
Date:Sat, 06 Feb 2016 17:24:05 GMT
ETag:"0x8D32E3EDB17EC00"
Last-Modified:Fri, 05 Feb 2016 15:13:08 GMT
Server:Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-blob-type:BlockBlob
x-ms-lease-state:available
x-ms-lease-status:unlocked
x-ms-request-id:88d3aaef-0629-4316-995f-021aa0153c32
x-ms-version:2015-04-05

I have:

  • Added crossOrigin="anonymous" to the video element, but this just makes the video fail to load altogether
  • Tried even the same domain on a different port (as above)
  • Ensured that Access-Control-Allow-Origin is coming back with * (as above)
  • I don't believe it is DRM as the screenshot works fine if I copy the exact same video file to the web app and load it locally
  • Run through all answers to this question, but this is for images not videos anyway and the answers only describe all the previous points

Yet, still the blasted error.

Edit
Added code:

var getScreenshotDataUrl = function(video, canvas, type) {
    type = type || "image/jpeg";
    var context = canvas.getContext("2d");
    var w = video.videoWidth;
    var h = video.videoHeight;
    canvas.width = w;
    canvas.height = h;
    context.fillRect(0, 0, w, h);
    context.drawImage(video, 0, 0, w, h);
    video.crossorigin = "anonymous";// makes no difference
    return canvas.toDataURL(type);
}

Please help.

like image 758
joshcomley Avatar asked Feb 06 '16 17:02

joshcomley


1 Answers

I have answered my own question.

What a horrible headache I now have.

The problem lies somewhere in the nuanced specification of the HTML5 video crossorigin/CORS specification.

I only tested in Chrome and Edge, but here is what you need to know at the time of writing:

Chrome

Loading your HTML5 video will fail if you have crossOrigin set, but your video is being served from any port other than 80 and is not using https:

THIS WILL FAIL
Client at http://www.myapp.com/player.html:

<video crossOrigin="anonymous" src="http://cdn.myapp.com:81/video.mp4"></video>

THIS WILL SUCCEED
Client at http://www.myapp.com/player.html:

<video crossOrigin="anonymous" src="https://cdn.myapp.com:81/video.mp4"></video>

Chrome and Edge

getImageData() and toDataURL() will be security blocked unless:

  • crossorigin is set to anonymous or use-credentials (as defined here) before the video is loaded. If you do this too late, it will still fail.

All

Finally, if you are going to set crossOrigin in javascript, be sure to use the correct casing for the javascript property: crossOrigin (NOT crossorigin)

I have written this up in a little more detail in my blog.

like image 113
joshcomley Avatar answered Oct 05 '22 09:10

joshcomley