UNPKG

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
// ____________ // 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; }); }