add firefogg.jsm, this should move back to firefogg later
This commit is contained in:
parent
edc69304e0
commit
166268a30c
1 changed files with 299 additions and 0 deletions
299
OxFF/modules/firefogg.jsm
Normal file
299
OxFF/modules/firefogg.jsm
Normal file
|
@ -0,0 +1,299 @@
|
|||
// -*- coding: utf-8 -*-
|
||||
// vi:si:et:sw=2:sts=2:ts=2
|
||||
|
||||
let EXPORTED_SYMBOLS = [ "FirefoggUploader" ];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
function FirefoggUploader(parent, path, url, data) {
|
||||
this._parent = parent;
|
||||
this._path = path;
|
||||
this._url = url;
|
||||
this._chunkUrl = false;
|
||||
this._data = data;
|
||||
this._status = 'initiated';
|
||||
this.encodingStatus = 'encoding';
|
||||
this.progress = 0.0;
|
||||
this.chunkSize = 1024*1024; //1MB, is there a need to make this variable?
|
||||
this.chunksUploaded = 0;
|
||||
this.chunkStatus = {};
|
||||
this._done_cb = function(upload) {};
|
||||
this._onProgress = function(progress) {};
|
||||
this.filename = 'video.ogv';
|
||||
this.canceled = false;
|
||||
this._retries = 0;
|
||||
this._maxRetry = -1;
|
||||
this.ready = false;
|
||||
}
|
||||
|
||||
/*
|
||||
Internal helper object dealing with chunked upload as defined at
|
||||
http://firefogg.org/dev/chunk_post.html
|
||||
*/
|
||||
FirefoggUploader.prototype = {
|
||||
start: function() {
|
||||
var boundary = "--------XX" + Math.random();
|
||||
|
||||
this._status = 'requesting chunk upload';
|
||||
|
||||
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
|
||||
|
||||
//XMLHttpRequest status callbacks
|
||||
var upload = this;
|
||||
function transferComplete(evt) {
|
||||
var response = {};
|
||||
upload.responseText = evt.target.responseText;
|
||||
try {
|
||||
response = JSON.parse(evt.target.responseText);
|
||||
//dump(evt.target.responseText);
|
||||
} catch(e) {
|
||||
dump('FAILED to parse response:\n\n');
|
||||
dump(evt.target.responseText);
|
||||
response = {};
|
||||
}
|
||||
if (response.maxRetry) {
|
||||
upload._maxRetry = response.maxRetry;
|
||||
}
|
||||
upload._chunkUrl = response.uploadUrl;
|
||||
if (upload._chunkUrl) {
|
||||
//dump('now tracking chunk uploads to ' + upload._chunkUrl + '\n');
|
||||
upload._status = 'uploading';
|
||||
upload._progress = 0.0;
|
||||
upload._uploadChunkId = 0;
|
||||
upload.uploadChunk(0, false);
|
||||
} else {
|
||||
upload._status = 'upload failed, no upload url provided';
|
||||
upload._progress = -1;
|
||||
upload._parent.cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
function transferFailed(evt) {
|
||||
upload._status = 'uplaod failed';
|
||||
upload._progress = -1;
|
||||
upload.responseText = evt.target.responseText;
|
||||
//dump(evt.target.responseText);
|
||||
}
|
||||
req.addEventListener("load", transferComplete, false);
|
||||
req.addEventListener("error", transferFailed, false);
|
||||
|
||||
req.open("POST", this._url);
|
||||
|
||||
var formData = "";
|
||||
for (key in this._data) {
|
||||
formData += "--" + boundary + "\r\n" +
|
||||
"Content-Disposition: form-data; name=\""+key+"\"\r\n\r\n" + this._data[key] + "\r\n";
|
||||
}
|
||||
formData += "--" + boundary + "--\r\n";
|
||||
|
||||
var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
var formStream = converter.convertToInputStream(formData);
|
||||
|
||||
req.setRequestHeader("Content-type", "multipart/form-data; boundary=" + boundary);
|
||||
req.setRequestHeader("Content-length", formStream.available());
|
||||
req.send(formStream);
|
||||
},
|
||||
onProgress: function(cb) {
|
||||
this._onProgress = cb;
|
||||
},
|
||||
done: function(done_cb) {
|
||||
//used by Firefogg to indicate that encoding is done
|
||||
//provies callback that is called once upload is done too
|
||||
this.encodingStatus = 'done';
|
||||
this._done_cb = done_cb;
|
||||
},
|
||||
uploadChunk: function(chunkId, last) {
|
||||
/*
|
||||
upload chunk with given chunkId, last indicated if this is the last chunk.
|
||||
once upload is done next chunk is queued
|
||||
*/
|
||||
var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||
file.initWithPath(this._path);
|
||||
this.filename = file.leafName;
|
||||
this.filename = this.filename.replace(/\(\.\d+\).ogv/, '.ogv');
|
||||
this.filename = this.filename.replace(/\(\.\d+\).webm/, '.webm');
|
||||
|
||||
var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
|
||||
fileStream.init(file, -1, -1, false);
|
||||
fileStream.QueryInterface(Ci.nsISeekableStream);
|
||||
var f = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
|
||||
f.setInputStream(fileStream);
|
||||
|
||||
var bytesAvailable = fileStream.available();
|
||||
var chunk = false;
|
||||
var chunkOffset = chunkId * this.chunkSize;
|
||||
this.progress = parseFloat(chunkOffset)/bytesAvailable;
|
||||
|
||||
if (last) {
|
||||
//last chunk, read add remaining data
|
||||
fileStream.seek(fileStream.NS_SEEK_SET, chunkOffset);
|
||||
chunk = f.readBytes(bytesAvailable-chunkOffset);
|
||||
}
|
||||
else if (this.ready && bytesAvailable >= chunkOffset + this.chunkSize) {
|
||||
//read next chunk
|
||||
fileStream.seek(fileStream.NS_SEEK_SET, chunkOffset);
|
||||
chunk = f.readBytes(this.chunkSize);
|
||||
} else {
|
||||
if (this.encodingStatus == 'done' && this._status == 'uploading') {
|
||||
//uploading is done and last chunk is < chunkSize, upload remaining data
|
||||
this._status = 'success';
|
||||
this.uploadChunk(chunkId, true);
|
||||
} else {
|
||||
//encoding not ready. wait for 2 seconds and try again
|
||||
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
timer.initWithCallback(
|
||||
function(obj, chunkId, last) {
|
||||
return function() { obj.uploadChunk(chunkId, last) }
|
||||
}(this, chunkId, last),
|
||||
2000, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
f.close();
|
||||
fileStream.close();
|
||||
return;
|
||||
}
|
||||
f.close();
|
||||
fileStream.close();
|
||||
|
||||
//FIXME: should this be checked so it does not get called again?
|
||||
this.chunkStatus[chunkId] = {};
|
||||
|
||||
//dump('POST ' + this._chunkUrl + ' uploading chunk ' + chunkId +' ('+chunk.length+' bytes)\n');
|
||||
|
||||
var boundary = "--------XX" + Math.random();
|
||||
|
||||
var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
|
||||
|
||||
var upload = this;
|
||||
function transferComplete(evt) {
|
||||
//if server resonds with valid {result=1} and status was is still 'uploading' go on to next chunk
|
||||
upload.responseText = evt.target.responseText;
|
||||
try {
|
||||
var response = JSON.parse(evt.target.responseText);
|
||||
} catch(e) {
|
||||
dump('FAILED to parse response:\n\n');
|
||||
dump(evt.target.responseText);
|
||||
var response = {};
|
||||
}
|
||||
/*
|
||||
if (upload.canceled) {
|
||||
//FIXME: should we let the server know that upload was canceled?
|
||||
}
|
||||
else */
|
||||
|
||||
/*
|
||||
dump('\n\n');
|
||||
dump(evt.target.responseText);
|
||||
dump('\n************\n\n');
|
||||
*/
|
||||
if (response.done == 1) {
|
||||
//upload finished, update state and expose result
|
||||
upload.resultUrl = response.resultUrl;
|
||||
upload.progress = 1;
|
||||
if (upload._done_cb)
|
||||
upload._done_cb(upload);
|
||||
//reset retry counter
|
||||
upload._retries = 0;
|
||||
}
|
||||
else if (response.result == 1) {
|
||||
//start uploading next chunk
|
||||
upload._uploadChunkId = chunkId + 1;
|
||||
upload.uploadChunk(upload._uploadChunkId, false);
|
||||
//upload status
|
||||
upload.chunkStatus[chunkId].progress = 1;
|
||||
upload.chunkStatus[chunkId].loaded = evt.loaded;
|
||||
upload.chunksUploaded++;
|
||||
//reset retry counter
|
||||
upload._retries = 0;
|
||||
} else {
|
||||
//failed to upload, try again in 3 second
|
||||
//dump('could not parse response, failed to upload, will try again in 3 second\n');
|
||||
//dump(evt.target.responseText);
|
||||
if (upload.max_retry > 0 && upload._retries > upload.max_retry) {
|
||||
upload.cancel();
|
||||
upload._status = 'uplaod failed';
|
||||
upload._progress = -1;
|
||||
} else {
|
||||
upload._retries++;
|
||||
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
timer.initWithCallback(
|
||||
function(obj, chunkId, last) {
|
||||
return function() { obj.uploadChunk(chunkId, last); };
|
||||
}(upload, chunkId, last), 3000, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
}
|
||||
}
|
||||
function transferFailed(evt) {
|
||||
//failed to upload, try again in 3 second
|
||||
//dump('transferFailed, will try again in 3 second\n');
|
||||
if (upload.max_retry > 0 && upload._retries > upload.max_retry) {
|
||||
upload.cancel();
|
||||
upload._status = 'uplaod failed';
|
||||
upload._progress = -1;
|
||||
} else {
|
||||
upload._retries++;
|
||||
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
timer.initWithCallback(
|
||||
function(obj, chunkId, last) {
|
||||
return function() { obj.uploadChunk(chunkId, last); };
|
||||
}(upload, chunkId, last), 3000, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
}
|
||||
function updateProgress(evt) {
|
||||
if (evt.lengthComputable) {
|
||||
upload.progress = parseFloat(chunkOffset + evt.loaded)/bytesAvailable;
|
||||
upload.chunkStatus[chunkId].loaded = evt.loaded;
|
||||
upload.chunkStatus[chunkId].loaded = evt.total;
|
||||
//dump('progress: chunk ' + chunkId + ' uploaded ' + evt.loaded + '\n');
|
||||
upload._onProgress(upload.progress);
|
||||
}
|
||||
}
|
||||
req.upload.addEventListener("progress", updateProgress, false);
|
||||
|
||||
req.addEventListener("load", transferComplete, false);
|
||||
req.addEventListener("error", transferFailed, false);
|
||||
|
||||
req.open("POST", this._chunkUrl);
|
||||
|
||||
var chunk = "--" + boundary + "\r\n" +
|
||||
"Content-Disposition: form-data; name=\"chunk\"; filename=\"" + this.filename + "\"\r\n" +
|
||||
"Content-type: video/ogg\r\n\r\n" + chunk;
|
||||
var chunkStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
|
||||
chunkStream.setData(chunk, chunk.length);
|
||||
|
||||
// write done flag into stream
|
||||
var formData = "\r\n";
|
||||
var _data = {
|
||||
chunkId: chunkId
|
||||
};
|
||||
if (last) {
|
||||
_data['done'] = 1;
|
||||
}
|
||||
for (key in _data) {
|
||||
formData += "--" + boundary + "\r\n" +
|
||||
"Content-Disposition: form-data; name=\""+key+"\"\r\n\r\n" + _data[key] + "\r\n";
|
||||
}
|
||||
formData += "--" + boundary + "--\r\n";
|
||||
var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
var formStream = converter.convertToInputStream(formData);
|
||||
|
||||
var multiStream = Cc["@mozilla.org/io/multiplex-input-stream;1"].createInstance(Ci.nsIMultiplexInputStream);
|
||||
multiStream.appendStream(chunkStream);
|
||||
multiStream.appendStream(formStream);
|
||||
|
||||
//send mupltiplrex stream
|
||||
req.setRequestHeader("Content-type", "multipart/form-data; boundary=" + boundary);
|
||||
req.setRequestHeader("Content-length", multiStream.available());
|
||||
//this._current_req = req;
|
||||
req.send(multiStream);
|
||||
},
|
||||
cancel: function() {
|
||||
this.canceled = true;
|
||||
if (this._current_req) {
|
||||
this._current_req.abort();
|
||||
this._current_req = null;
|
||||
}
|
||||
},
|
||||
}
|
Loading…
Reference in a new issue