recordrtc
Version:
RecordRTC is a server-less (entire client-side) JavaScript library can be used to record WebRTC audio/video media streams. It supports cross-browser audio/video recording.
626 lines (545 loc) • 18.9 kB
JavaScript
// ____________
// RecordRTC.js
/**
* {@link https://github.com/muaz-khan/RecordRTC|RecordRTC} is a JavaScript-based media-recording library for modern web-browsers (supporting WebRTC getUserMedia API). It is optimized for different devices and browsers to bring all client-side (pluginfree) recording solutions in single place.
* @summary JavaScript audio/video recording library runs top over WebRTC getUserMedia API.
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @typedef RecordRTC
* @class
* @example
* var recordRTC = RecordRTC(mediaStream, {
* type: 'video' // audio or video or gif or canvas
* });
*
* // or, you can even use keyword "new"
* var recordRTC = new RecordRTC(mediaStream[, config]);
* @see For further information:
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
* @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API.
* @param {object} config - {type:"video", disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, video: HTMLVideoElement, etc.}
*/
function RecordRTC(mediaStream, config) {
if (!mediaStream) {
throw 'MediaStream is mandatory.';
}
config = new RecordRTCConfiguration(mediaStream, config);
// a reference to user's recordRTC object
var self = this;
function startRecording() {
if (!config.disableLogs) {
console.debug('started recording ' + config.type + ' stream.');
}
if (mediaRecorder) {
mediaRecorder.clearRecordedData();
mediaRecorder.resume();
if (self.recordingDuration) {
handleRecordingDuration();
}
return self;
}
initRecorder(function() {
if (self.recordingDuration) {
handleRecordingDuration();
}
});
return self;
}
function initRecorder(initCallback) {
if (initCallback) {
config.initCallback = function() {
initCallback();
initCallback = config.initCallback = null; // recordRTC.initRecorder should be call-backed once.
};
}
var Recorder = new GetRecorderType(mediaStream, config);
mediaRecorder = new Recorder(mediaStream, config);
mediaRecorder.record();
if (!config.disableLogs) {
console.debug('Initialized recorderType:', mediaRecorder.constructor.name, 'for output-type:', config.type);
}
}
function stopRecording(callback) {
if (!mediaRecorder) {
return console.warn(WARNING);
}
/*jshint validthis:true */
var recordRTC = this;
if (!config.disableLogs) {
console.warn('Stopped recording ' + config.type + ' stream.');
}
if (config.type !== 'gif') {
mediaRecorder.stop(_callback);
} else {
mediaRecorder.stop();
_callback();
}
function _callback() {
for (var item in mediaRecorder) {
if (self) {
self[item] = mediaRecorder[item];
}
if (recordRTC) {
recordRTC[item] = mediaRecorder[item];
}
}
var blob = mediaRecorder.blob;
if (callback) {
var url = URL.createObjectURL(blob);
callback(url);
}
if (blob && !config.disableLogs) {
console.debug(blob.type, '->', bytesToSize(blob.size));
}
if (!config.autoWriteToDisk) {
return;
}
getDataURL(function(dataURL) {
var parameter = {};
parameter[config.type + 'Blob'] = dataURL;
DiskStorage.Store(parameter);
});
}
}
function pauseRecording() {
if (!mediaRecorder) {
return console.warn(WARNING);
}
mediaRecorder.pause();
if (!config.disableLogs) {
console.debug('Paused recording.');
}
}
function resumeRecording() {
if (!mediaRecorder) {
return console.warn(WARNING);
}
// not all libs yet having this method
mediaRecorder.resume();
if (!config.disableLogs) {
console.debug('Resumed recording.');
}
}
function readFile(_blob) {
postMessage(new FileReaderSync().readAsDataURL(_blob));
}
function getDataURL(callback, _mediaRecorder) {
if (!callback) {
throw 'Pass a callback function over getDataURL.';
}
var blob = _mediaRecorder ? _mediaRecorder.blob : (mediaRecorder || {}).blob;
if (!blob) {
if (!config.disableLogs) {
console.warn('Blob encoder did not yet finished its job.');
}
setTimeout(function() {
getDataURL(callback, _mediaRecorder);
}, 1000);
return;
}
if (typeof Worker !== 'undefined' && !navigator.mozGetUserMedia) {
var webWorker = processInWebWorker(readFile);
webWorker.onmessage = function(event) {
callback(event.data);
};
webWorker.postMessage(blob);
} else {
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function(event) {
callback(event.target.result);
};
}
function processInWebWorker(_function) {
var blob = URL.createObjectURL(new Blob([_function.toString(),
'this.onmessage = function (e) {' + _function.name + '(e.data);}'
], {
type: 'application/javascript'
}));
var worker = new Worker(blob);
URL.revokeObjectURL(blob);
return worker;
}
}
function handleRecordingDuration() {
setTimeout(function() {
stopRecording(self.onRecordingStopped);
}, self.recordingDuration);
}
var WARNING = 'It seems that "startRecording" is not invoked for ' + config.type + ' recorder.';
var mediaRecorder;
var returnObject = {
/**
* This method starts recording. It doesn't take any argument.
* @method
* @memberof RecordRTC
* @instance
* @example
* recordRTC.startRecording();
*/
startRecording: startRecording,
/**
* This method stops recording. It takes single "callback" argument. It is suggested to get blob or URI in the callback to make sure all encoders finished their jobs.
* @param {function} callback - This callback function is invoked after completion of all encoding jobs.
* @method
* @memberof RecordRTC
* @instance
* @example
* recordRTC.stopRecording(function(videoURL) {
* video.src = videoURL;
* recordRTC.blob; recordRTC.buffer;
* });
*/
stopRecording: stopRecording,
/**
* This method pauses the recording process.
* @method
* @memberof RecordRTC
* @instance
* @example
* recordRTC.pauseRecording();
*/
pauseRecording: pauseRecording,
/**
* This method resumes the recording process.
* @method
* @memberof RecordRTC
* @instance
* @example
* recordRTC.resumeRecording();
*/
resumeRecording: resumeRecording,
/**
* This method initializes the recording process.
* @method
* @memberof RecordRTC
* @instance
* @example
* recordRTC.initRecorder();
*/
initRecorder: initRecorder,
/**
* This method initializes the recording process.
* @method
* @memberof RecordRTC
* @instance
* @example
* recordRTC.initRecorder();
*/
setRecordingDuration: function(milliseconds, callback) {
if (typeof milliseconds === 'undefined') {
throw 'milliseconds is required.';
}
if (typeof milliseconds !== 'number') {
throw 'milliseconds must be a number.';
}
self.recordingDuration = milliseconds;
self.onRecordingStopped = callback || function() {};
return {
onRecordingStopped: function(callback) {
self.onRecordingStopped = callback;
}
};
},
/**
* This method can be used to clear/reset all the recorded data.
* @method
* @memberof RecordRTC
* @instance
* @example
* recordRTC.clearRecordedData();
*/
clearRecordedData: function() {
if (!mediaRecorder) {
return console.warn(WARNING);
}
mediaRecorder.clearRecordedData();
if (!config.disableLogs) {
console.debug('Cleared old recorded data.');
}
},
/**
* It is equivalent to <code class="str">"recordRTC.blob"</code> property.
* @method
* @memberof RecordRTC
* @instance
* @example
* recordRTC.stopRecording(function() {
* var blob = recordRTC.getBlob();
*
* // equivalent to: recordRTC.blob property
* var blob = recordRTC.blob;
* });
*/
getBlob: function() {
if (!mediaRecorder) {
return console.warn(WARNING);
}
return mediaRecorder.blob;
},
/**
* This method returns DataURL. It takes single "callback" argument.
* @param {function} callback - DataURL is passed back over this callback.
* @method
* @memberof RecordRTC
* @instance
* @example
* recordRTC.stopRecording(function() {
* recordRTC.getDataURL(function(dataURL) {
* video.src = dataURL;
* });
* });
*/
getDataURL: getDataURL,
/**
* This method returns Virutal/Blob URL. It doesn't take any argument.
* @method
* @memberof RecordRTC
* @instance
* @example
* recordRTC.stopRecording(function() {
* video.src = recordRTC.toURL();
* });
*/
toURL: function() {
if (!mediaRecorder) {
return console.warn(WARNING);
}
return URL.createObjectURL(mediaRecorder.blob);
},
/**
* This method saves blob/file into disk (by inovking save-as dialog). It takes single (optional) argument i.e. FileName
* @method
* @memberof RecordRTC
* @instance
* @example
* recordRTC.stopRecording(function() {
* recordRTC.save('file-name');
* });
*/
save: function(fileName) {
if (!mediaRecorder) {
return console.warn(WARNING);
}
invokeSaveAsDialog(mediaRecorder.blob, fileName);
},
/**
* This method gets blob from indexed-DB storage. It takes single "callback" argument.
* @method
* @memberof RecordRTC
* @instance
* @example
* recordRTC.getFromDisk(function(dataURL) {
* video.src = dataURL;
* });
*/
getFromDisk: function(callback) {
if (!mediaRecorder) {
return console.warn(WARNING);
}
RecordRTC.getFromDisk(config.type, callback);
},
/**
* This method appends prepends array of webp images to the recorded video-blob. It takes an "array" object.
* @type {Array.<Array>}
* @param {Array} arrayOfWebPImages - Array of webp images.
* @method
* @memberof RecordRTC
* @instance
* @example
* var arrayOfWebPImages = [];
* arrayOfWebPImages.push({
* duration: index,
* image: 'data:image/webp;base64,...'
* });
* recordRTC.setAdvertisementArray(arrayOfWebPImages);
*/
setAdvertisementArray: function(arrayOfWebPImages) {
config.advertisement = [];
var length = arrayOfWebPImages.length;
for (var i = 0; i < length; i++) {
config.advertisement.push({
duration: i,
image: arrayOfWebPImages[i]
});
}
},
/**
* It is equivalent to <code class="str">"recordRTC.getBlob()"</code> method.
* @property {Blob} blob - Recorded Blob can be accessed using this property.
* @memberof RecordRTC
* @instance
* @example
* recordRTC.stopRecording(function() {
* var blob = recordRTC.blob;
*
* // equivalent to: recordRTC.getBlob() method
* var blob = recordRTC.getBlob();
* });
*/
blob: null,
/**
* @todo Add descriptions.
* @property {number} bufferSize - Either audio device's default buffer-size, or your custom value.
* @memberof RecordRTC
* @instance
* @example
* recordRTC.stopRecording(function() {
* var bufferSize = recordRTC.bufferSize;
* });
*/
bufferSize: 0,
/**
* @todo Add descriptions.
* @property {number} sampleRate - Audio device's default sample rates.
* @memberof RecordRTC
* @instance
* @example
* recordRTC.stopRecording(function() {
* var sampleRate = recordRTC.sampleRate;
* });
*/
sampleRate: 0,
/**
* @todo Add descriptions.
* @property {ArrayBuffer} buffer - Audio ArrayBuffer, supported only in Chrome.
* @memberof RecordRTC
* @instance
* @example
* recordRTC.stopRecording(function() {
* var buffer = recordRTC.buffer;
* });
*/
buffer: null,
/**
* @todo Add descriptions.
* @property {DataView} view - Audio DataView, supported only in Chrome.
* @memberof RecordRTC
* @instance
* @example
* recordRTC.stopRecording(function() {
* var dataView = recordRTC.view;
* });
*/
view: null
};
if (!this) {
self = returnObject;
return returnObject;
}
// if someone wanna use RecordRTC with "new" keyword.
for (var prop in returnObject) {
this[prop] = returnObject[prop];
}
self = this;
return returnObject;
}
/**
* This method can be used to get all recorded blobs from IndexedDB storage.
* @param {string} type - 'all' or 'audio' or 'video' or 'gif'
* @param {function} callback - Callback function to get all stored blobs.
* @method
* @memberof RecordRTC
* @example
* RecordRTC.getFromDisk('all', function(dataURL, type){
* if(type === 'audio') { }
* if(type === 'video') { }
* if(type === 'gif') { }
* });
*/
RecordRTC.getFromDisk = function(type, callback) {
if (!callback) {
throw 'callback is mandatory.';
}
console.log('Getting recorded ' + (type === 'all' ? 'blobs' : type + ' blob ') + ' from disk!');
DiskStorage.Fetch(function(dataURL, _type) {
if (type !== 'all' && _type === type + 'Blob' && callback) {
callback(dataURL);
}
if (type === 'all' && callback) {
callback(dataURL, _type.replace('Blob', ''));
}
});
};
/**
* This method can be used to store recorded blobs into IndexedDB storage.
* @param {object} options - {audio: Blob, video: Blob, gif: Blob}
* @method
* @memberof RecordRTC
* @example
* RecordRTC.writeToDisk({
* audio: audioBlob,
* video: videoBlob,
* gif : gifBlob
* });
*/
RecordRTC.writeToDisk = function(options) {
console.log('Writing recorded blob(s) to disk!');
options = options || {};
if (options.audio && options.video && options.gif) {
options.audio.getDataURL(function(audioDataURL) {
options.video.getDataURL(function(videoDataURL) {
options.gif.getDataURL(function(gifDataURL) {
DiskStorage.Store({
audioBlob: audioDataURL,
videoBlob: videoDataURL,
gifBlob: gifDataURL
});
});
});
});
} else if (options.audio && options.video) {
options.audio.getDataURL(function(audioDataURL) {
options.video.getDataURL(function(videoDataURL) {
DiskStorage.Store({
audioBlob: audioDataURL,
videoBlob: videoDataURL
});
});
});
} else if (options.audio && options.gif) {
options.audio.getDataURL(function(audioDataURL) {
options.gif.getDataURL(function(gifDataURL) {
DiskStorage.Store({
audioBlob: audioDataURL,
gifBlob: gifDataURL
});
});
});
} else if (options.video && options.gif) {
options.video.getDataURL(function(videoDataURL) {
options.gif.getDataURL(function(gifDataURL) {
DiskStorage.Store({
videoBlob: videoDataURL,
gifBlob: gifDataURL
});
});
});
} else if (options.audio) {
options.audio.getDataURL(function(audioDataURL) {
DiskStorage.Store({
audioBlob: audioDataURL
});
});
} else if (options.video) {
options.video.getDataURL(function(videoDataURL) {
DiskStorage.Store({
videoBlob: videoDataURL
});
});
} else if (options.gif) {
options.gif.getDataURL(function(gifDataURL) {
DiskStorage.Store({
gifBlob: gifDataURL
});
});
}
};
if (typeof module !== 'undefined' /* && !!module.exports*/ ) {
module.exports = RecordRTC;
}
if (typeof define === 'function' && define.amd) {
define('RecordRTC', [], function() {
return RecordRTC;
});
}