dean.humphreys
dean.humphreys

Reputation: 11

Vimeo upload using chunks

I am having the same problem as here: Upload a video file by chunks with the "Content-Range" header but none of the solutions given work for me. I am using javascript to PUT videos to the Vimeo endpoint. I get an upload ticket and deal with the initialising and completion of the upload via php on another server. All the javascript does is PUT the video data to an endpoint.

My client has an unreliable internet connection and I am therefore building a resumable uploader that tries to auto resume.

What I have noticed is that if I remove the "Content-Range" header and set my chunk size to bigger than the uploading video, it uploads correctly. As soon as I add the header things go wrong. I understand that the header should be in the form "Content-Range": "bytes 1001-4999/5000" but this causes issues for some reason. When I use this format, chrome doesn't send the "Content-Length" header and the request fails. FF sends the header, but fails as well.

I have tried different combinations for the "Content-Range" header including: "1001-4999/5000", "range=1001-4999-5000". Which don't give errors but are not recognised by vimeo as when I ask for the uploaded bytes, I always get the length of the last uploaded chunk. I have also tried sending the first chunk without the header and the rest with it but but it's the header itself that is not being sent correctly.

Javascript:

/*
 * Uploads raw data to the server
 *
 */
var uploadRawData = function(starting_byte, ending_byte, data_to_send){
    $('.auto_uploader #play_pause_button').addClass('pause').removeClass('disabled');
    $('.auto_uploader #stop_button').removeClass('disabled');
    $('.auto_uploader #status_display').attr('class', '').fadeIn(); //Remove all other classes
    _offset_starting_from = parseInt(starting_byte) || 0;

    _uploader_process = $.ajax({
        url: _endpoint,
        type: 'PUT',
        beforeSend: function (request)
        {
            //if (starting_byte != 0){ // Send the first chunk with without content-range header
                // If this function is being called to resume a file, add the starting offset
                var content_range_header = "bytes " + starting_byte + "-" + ending_byte + "/" + _file.size;
                request.setRequestHeader("Content-Range", content_range_header);
            //}
            request.setRequestHeader("Content-Type", _file.type);
            //request.setRequestHeader("Content-Length", data.byteLength);
        },
        processData: false,
        xhr: function() {  // custom xhr
            myXhr = $.ajaxSettings.xhr();
            if(myXhr.upload){ // check if upload property exists
                myXhr.upload.addEventListener('progress', progressHandlingFunction, false); // for handling the progress of the upload
            }
            return myXhr;
        },
        //Ajax events
        success: function() {
            if (ending_byte < _file.size - 1){ // Not complete (chunk mode)
                var next_upload_byte = ending_byte + 1;
                retryUpload(); // Check for uploaded bytes before next chunk upload (this creates an infinite loop since vimeo only reports the last byte uploaded, since the Content-Range header above is not being recognised) - Or
                //uploadFilePart(next_upload_byte, next_upload_byte + _chunk_size - 1); // - doesn't check uploaded bytes before send of next chunk (Uploads all parts, but vimeo only registers the last chunk)
            } else { // Complete!
                //retryUpload(); // Check to make sure the entire file was uploaded
                $('.auto_uploader #status_display').attr('class', 'tick');
                resetButtons();
                _completeFunction(); //Call user defined callback
            }
        },
        error: function(data) {
            console.log(data.responseText);
            if (!_paused && _file != null){ //Aborting (pausing) causes an error
                // Retry the upload if we fail
                if (_retry_count < _retry_limit){
                    _retry_count = _retry_count + 1;
                    console.log('Error occurred while uploading file "' + _file.name + '",  retry #' + _retry_count + ' in ' + _retry_count * 10 + ' sec');
                    setTimeout(function() {   //calls click event after a certain time
                        retryUpload(); // Call the retry upload method in 10 sec
                    }, 10000);
                } else if (_retry_count == _retry_limit){
                    //We've tried enough!});
                    resetButtons();
                    $('.auto_uploader #status_display').attr('class', 'error');
                    alert('Maximum number of retries exceeded!');
                }
            }
        },
        //error: errorHandler,
        // Form data
        data: data_to_send,
        //Options to tell JQuery not to process data or worry about content-type
        cache: false,
        contentType: false
    });
}

I have uploaded a test version here: http://test.paperjet.info/auto_uploader/ and the full source here: http://test.paperjet.info/auto_uploader.zip The headers are being added on line 121 of autoUploader.js. Note that if you use this it has an endpoint hardcoded into index.html and may have expired.

If anyone has solved this problem or has successively achieved this functionality using javascript please let me know.

Upvotes: 1

Views: 2869

Answers (2)

Adnan
Adnan

Reputation: 211

I had the same problem and managed to build an implementation of Upload a video file by chunks to Vimeo on Github : https://github.com/websemantics/vimeo-upload

It uses resumable PUT and shows a progress bar and returns the url of the uploaded video.

Upvotes: 2

restlessdesign
restlessdesign

Reputation: 1489

The proper way to get the bytes on the server is to do a PUT with Content-Range: bytes */* in the request header:

var byte_request = new XMLHttpRequest();
byte_request.open('PUT', upload_url, true);
byte_request.overrideMimeType('application/octet-stream');
byte_request.setRequestHeader('Content-Range', 'bytes */*');
byte_request.send();

This should return a 308 response. In your success handler, scape the Range header:

function onByteRequestSuccess(e) {
    var range_header,
        bytes_received;

    if (byte_request.status === 308) {
        range_header = xhr.getResponseHeader('Range');

        // The Range header will return something like "bytes=0-215235".
        // This grabs the group of numbers AFTER the "-" character,
        // which is the total bytes on the server.
        bytes_received = parseInt(range_header.split('-')[1], 10);
    }
}

As for your Content-Type header not going out, I've found most libraries (jQuery and MooTools) to be unreliable for these sorts of requests. Might have to dig deeper into jQuery's code base if you still run into issues.

Upvotes: 0

Related Questions