jshbrntt
jshbrntt

Reputation: 5404

Why can't my Android device play HTML5 video loaded as Blob via XHR?

I've created a sample project that downloads an entire HTML5 video source before playing it. To accomplish this I had to load the video source file via a XMLHttpRequest using a responseType: 'arraybuffer' and converting it to a Blob.

This approach works fine on desktop browsers but when trying to play this on Android mobile devices the video won't play, and if I fullscreen the video on later versions of Android I'll get the following error dialog.

Error Screenshot

My sample project is available here.

Here's the code:

function getSupportedVideoFormats() {

    // Test from Modernizr.
    var elem = document.createElement('video');
    var bool = false;

    // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224
    try {
        if (bool = !!elem.canPlayType) {
            bool = new Boolean(bool);
            bool.ogg = elem.canPlayType('video/ogg; codecs="theora"').replace(/^no$/, '');

            // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546
            bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/, '');

            bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/, '');

            bool.vp9 = elem.canPlayType('video/webm; codecs="vp9"').replace(/^no$/, '');

            bool.hls = elem.canPlayType('application/x-mpegURL; codecs="avc1.42E01E"').replace(/^no$/, '');
        }
    } catch (e) {}

    return bool;

}

function getVideoFormat(mime) {

    return mime ? 'video/webm' : 'webm';

    var bool = getSupportedVideoFormats();
    // Prioritization of video format fallback.
    if (bool.h264 !== '') {
        return mime ? 'video/mp4' : 'mp4';
    }
    if (bool.webm !== '') {
        return mime ? 'video/webm' : 'webm';
    }
    if (bool.ogg !== '') {
        return mime ? 'video/ogg' : 'ogv';
    }
}

function updateProgressBar(value) {
    value = Math.round(value);
    progress.setAttribute('valuenow', value);
    progress.style.width = value + '%';
    progress.innerHTML = value + '%';
}

function loadVideoFully(event) {

    GET(url);

    function onProgress(event) {
        if (event.lengthComputable) {
            var completion = (event.loaded / event.total) * 100;
            updateProgressBar(completion);
        }
    }

    function onLoad(event) {
        var type = getVideoFormat(true);
        var blob = new Blob([event.target.response], {
            type: type
        });
        video.type = type;
        video.src = URL.createObjectURL(blob);
        video.play();
    }

    function GET(url) {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.setRequestHeader('Access-Control-Allow-Origin', '*');
        xhr.responseType = 'arraybuffer';
        xhr.onprogress = onProgress;
        xhr.onload = onLoad;
        xhr.send();
    }

}

function loadVideoNatively(event) {

    function logEvent(e) {
        var debug = [];
        switch (e.type) {
            case "progress":
                var completion = (this.buffered.end(0) / this.duration) * 100;
                updateProgressBar(completion);
                debug.push(completion);
            default:
                debug.push(e.type, e);
                break;
        }
        console.debug(debug);
    }

    video.addEventListener('loadstart', logEvent);
    video.addEventListener('durationchange', logEvent);
    video.addEventListener('loadedmetadata', logEvent);
    video.addEventListener('loadeddata', logEvent);
    video.addEventListener('progress', logEvent);
    video.addEventListener('canplay', logEvent);
    video.addEventListener('canplaythrough', logEvent);

    video.src = url;
    video.type = getVideoFormat(true);
    video.load();
}

// Video sample from videojs, without extension.
var url = 'videos/oceans-clip.' + getVideoFormat();
var video = document.getElementById('video');
var progress = document.getElementById('progress');
var button = document.getElementById('button');
var checkbox = document.getElementById('checkbox');

function loadVideo(event) {
    checkbox.checked ? loadVideoNatively(event) : loadVideoFully(event);
}

button.addEventListener('click', loadVideo);

console.debug('Video Formats Supported:', getSupportedVideoFormats());

Upvotes: 4

Views: 2968

Answers (1)

jrd261
jrd261

Reputation: 106

This looks to be a bug:

See Can't play a blob-url video using Chrome for Android and a possibly related bug report in Chromium https://code.google.com/p/chromium/issues/detail?id=253465

Upvotes: 4

Related Questions