Safari/iOS problems when drawing videos on WebGL canvas

tl;dr | jump to the code

Lately, I've been working on a project for Adidas at Resn where we need to use video as a texture in a WebGL canvas. Everything was working well on every platform in scope, until we deployed our code onto the staging environment and placed our videos assets on a CDN.

At this moment, came the pain - it didn't work at all on iOS. When opening the devtools to inspect my device, the only information I got was that:
Error: SecurityError: DOM Exception 18

After doing some research I found the problem.
I reproduced it on a Android tablet with an older version of Google Chrome, and this time, the error was more explicit.

Chrome Error

The problem was an old bug of Webkit which ignored the CORS header on a video and disallowed our drawing of it on Canvas. The bug is old and is already logged on the Webkit tracker.
This comment in the three.js tracker confirmed my problem was indeed that bug.

I read the bug report to see if they found a workaround. I found one but it required you to use your server as a reverse proxy to avoid loading the video from a different origin.

This solution worked but I cannot use it server-side work and I didn't have the permission to change the server configuration..

So I had to think about a client-side solution to avoid that bug. I wanted to see if it was possible to use the video as Blob to know if that would avoid the problem.

I just needed to check if the Blob constructor was supported by iOS.

Can I Use blobbuilder? Data on support for the blobbuilder feature across the major browsers from caniuse.com.

Luckily, iOS did support the blob, so let's cook 🍽 !

Example code

// Check if you are on iOS or Safari < 10
if (/iP(hone|od|ad)/.test(navigator.platform)) {

    var video = document.createElement('video');
    var src = options.src;

    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        // When the content is ready
        if (xhr.readyState === 4 && xhr.status === 200){
            //xhr.response is what you're looking for
            var blob = xhr.response;

            video.addEventListener('canplay', onCanPlay);
            // Create a URL for the blob we just receive
            video.src = window.URL.createObjectURL(blob);

            if (video.readyState > 3) {
                onCanPlay();
            }
        }
    };
    xhr.open('GET', src);
    // Don't forget to get the content as a Blob
    xhr.responseType = 'blob';
    xhr.send();
}

function onCanPlay() {  
  // you can now bind your video texture with the webgl context
}

// do your stuffs
...

// (Optional) Call this method when you're done with you're file to revoke the reference to the file. 
window.URL.revokeObjectURL(video.src);  

This video might not be a universal solution (especially if you deal with big videos), but at least it is a client-side workaround that works pretty well with small video.

If you have any suggestions or improvement, please leave a comment or send me an email.

Thanks to Mark for correcting my rookie mistakes !

Thanks you for reading ! 😘

Show Comments