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.

1,574 lines (1,357 loc) 125 kB
'use strict'; // Last time updated: 2016-05-06 5:21:33 PM UTC // Open-Sourced: https://github.com/muaz-khan/RecordRTC //-------------------------------------------------- // Muaz Khan - www.MuazKhan.com // MIT License - www.WebRTC-Experiment.com/licence //-------------------------------------------------- // ____________ // 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; }); } // __________________________ // RecordRTC-Configuration.js /** * {@link RecordRTCConfiguration} is an inner/private helper for {@link RecordRTC}. * @summary It configures the 2nd parameter passed over {@link RecordRTC} and returns a valid "config" object. * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} * @author {@link http://www.MuazKhan.com|Muaz Khan} * @typedef RecordRTCConfiguration * @class * @example * var options = RecordRTCConfiguration(mediaStream, options); * @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, getNativeBlob:true, etc.} */ function RecordRTCConfiguration(mediaStream, config) { if (config.recorderType && !config.type) { if (config.recorderType === WhammyRecorder || config.recorderType === CanvasRecorder) { config.type = 'video'; } else if (config.recorderType === GifRecorder) { config.type = 'gif'; } else if (config.recorderType === StereoAudioRecorder) { config.type = 'audio'; } else if (config.recorderType === MediaStreamRecorder) { if (mediaStream.getAudioTracks().length && mediaStream.getVideoTracks().length) { config.type = 'video'; } else if (mediaStream.getAudioTracks().length && !mediaStream.getVideoTracks().length) { config.type = 'audio'; } else if (!mediaStream.getAudioTracks().length && mediaStream.getVideoTracks().length) { config.type = 'audio'; } else { // config.type = 'UnKnown'; } } } if (typeof MediaStreamRecorder !== 'undefined' && typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype) { if (!config.mimeType) { config.mimeType = 'video/webm'; } if (!config.type) { config.type = config.mimeType.split('/')[0]; } if (!config.bitsPerSecond) { // config.bitsPerSecond = 128000; } } // consider default type=audio if (!config.type) { if (config.mimeType) { config.type = config.mimeType.split('/')[0]; } if (!config.type) { config.type = 'audio'; } } return config; } // __________________ // GetRecorderType.js /** * {@link GetRecorderType} is an inner/private helper for {@link RecordRTC}. * @summary It returns best recorder-type available for your browser. * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} * @author {@link http://www.MuazKhan.com|Muaz Khan} * @typedef GetRecorderType * @class * @example * var RecorderType = GetRecorderType(options); * var recorder = new RecorderType(options); * @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 GetRecorderType(mediaStream, config) { var recorder; // StereoAudioRecorder can work with all three: Edge, Firefox and Chrome // todo: detect if it is Edge, then auto use: StereoAudioRecorder if (isChrome || isEdge || isOpera) { // Media Stream Recording API has not been implemented in chrome yet; // That's why using WebAudio API to record stereo audio in WAV format recorder = StereoAudioRecorder; } if (typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype && !isChrome) { recorder = MediaStreamRecorder; } // video recorder (in WebM format) if (config.type === 'video' && (isChrome || isOpera)) { recorder = WhammyRecorder; } // video recorder (in Gif format) if (config.type === 'gif') { recorder = GifRecorder; } // html2canvas recording! if (config.type === 'canvas') { recorder = CanvasRecorder; } if (isMediaRecorderCompatible() && recorder !== CanvasRecorder && recorder !== GifRecorder && typeof MediaRecorder !== 'undefined' && 'requestData' in MediaRecorder.prototype) { if (mediaStream.getVideoTracks().length) { recorder = MediaStreamRecorder; } } if (config.recorderType) { recorder = config.recorderType; } if (!config.disableLogs && !!recorder && !!recorder.name) { console.debug('Using recorderType:', recorder.name || recorder.constructor.name); } return recorder; } // _____________ // MRecordRTC.js /** * MRecordRTC runs top over {@link RecordRTC} to bring multiple recordings in single place, by providing simple API. * @summary MRecordRTC stands for "Multiple-RecordRTC". * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} * @author {@link http://www.MuazKhan.com|Muaz Khan} * @typedef MRecordRTC * @class * @example * var recorder = new MRecordRTC(); * recorder.addStream(MediaStream); * recorder.mediaType = { * audio: true, // or StereoAudioRecorder or MediaStreamRecorder * video: true, // or WhammyRecorder or MediaStreamRecorder * gif: true // or GifRecorder * }; * // mimeType is optional and should be set only in advance cases. * recorder.mimeType = { * audio: 'audio/wav', * video: 'video/webm', * gif: 'image/gif' * }; * recorder.startRecording(); * @see For further information: * @see {@link https://github.com/muaz-khan/RecordRTC/tree/master/MRecordRTC|MRecordRTC Source Code} * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. */ function MRecordRTC(mediaStream) { /** * This method attaches MediaStream object to {@link MRecordRTC}. * @param {MediaStream} mediaStream - A MediaStream object, either fetched using getUserMedia API, or generated using captureStreamUntilEnded or WebAudio API. * @method * @memberof MRecordRTC * @example * recorder.addStream(MediaStream); */ this.addStream = function(_mediaStream) { if (_mediaStream) { mediaStream = _mediaStream; } }; /** * This property can be used to set recording type e.g. audio, or video, or gif, or canvas. * @property {object} mediaType - {audio: true, video: true, gif: true} * @memberof MRecordRTC * @example * var recorder = new MRecordRTC(); * recorder.mediaType = { * audio: true, // TRUE or StereoAudioRecorder or MediaStreamRecorder * video: true, // TRUE or WhammyRecorder or MediaStreamRecorder * gif : true // TRUE or GifRecorder * }; */ this.mediaType = { audio: true, video: true }; /** * This method starts recording. * @method * @memberof MRecordRTC * @example * recorder.startRecording(); */ this.startRecording = function() { var mediaType = this.mediaType; var recorderType; var mimeType = this.mimeType || { audio: null, video: null, gif: null }; if (typeof mediaType.audio !== 'function' && isMediaRecorderCompatible() && mediaStream.getAudioTracks && !mediaStream.getAudioTracks().length) { // Firefox is supporting both audio/video in single blob mediaType.audio = false; } if (typeof mediaType.video !== 'function' && isMediaRecorderCompatible() && mediaStream.getVideoTracks && !mediaStream.getVideoTracks().length) { // Firefox is supporting both audio/video in single blob mediaType.video = false; } if (!mediaType.audio && !mediaType.video) { throw 'MediaStream must have either audio or video tracks.'; } if (!!mediaType.audio) { recorderType = null; if (typeof mediaType.audio === 'function') { recorderType = mediaType.audio; } this.audioRecorder = new RecordRTC(mediaStream, { type: 'audio', bufferSize: this.bufferSize, sampleRate: this.sampleRate, numberOfAudioChannels: this.numberOfAudioChannels || 2, disableLogs: this.disableLogs, recorderType: recorderType, mimeType: mimeType.audio }); if (!mediaType.video) { this.audioRecorder.startRecording(); } } if (!!mediaType.video) { recorderType = null; if (typeof mediaType.video === 'function') { recorderType = mediaType.video; } var newStream = mediaStream; if (isMediaRecorderCompatible() && !!mediaType.audio && typeof mediaType.audio === 'function') { var videoTrack = mediaStream.getVideoTracks()[0]; if (!!navigator.mozGetUserMedia) { newStream = new MediaStream(); newStream.addTrack(videoTrack); if (recorderType && recorderType === WhammyRecorder) { // Firefox is NOT supporting webp-encoding yet recorderType = MediaStreamRecorder; } } else { newStream = new MediaStream([videoTrack]); } } this.videoRecorder = new RecordRTC(newStream, { type: 'video', video: this.video, canvas: this.canvas, frameInterval: this.frameInterval || 10, disableLogs: this.disableLogs, recorderType: recorderType, mimeType: mimeType.video }); if (!mediaType.audio) { this.videoRecorder.startRecording(); } } if (!!mediaType.audio && !!mediaType.video) { var self = this; if (isMediaRecorderCompatible()) { self.audioRecorder = null; self.videoRecorder.startRecording(); } else { self.videoRecorder.initRecorder(function() { self.audioRecorder.initRecorder(function() { // Both recorders are ready to record things accurately self.videoRecorder.startRecording(); self.audioRecorder.startRecording(); }); }); } } if (!!mediaType.gif) { recorderType = null; if (typeof mediaType.gif === 'function') { recorderType = mediaType.gif; } this.gifRecorder = new RecordRTC(mediaStream, { type: 'gif', frameRate: this.frameRate || 200, quality: this.quality || 10, disableLogs: this.disableLogs, recorderType: recorderType, mimeType: mimeType.gif }); this.gifRecorder.startRecording(); } }; /** * This method stop recording. * @param {function} callback - Callback function is invoked when all encoders finish their jobs. * @method * @memberof MRecordRTC * @example * recorder.stopRecording(function(recording){ * var audioBlob = recording.audio; * var videoBlob = recording.video; * var gifBlob = recording.gif; * }); */ this.stopRecording = function(callback) { callback = callback || function() {}; if (this.audioRecorder) { this.audioRecorder.stopRecording(function(blobURL) { callback(blobURL, 'audio'); }); } if (this.videoRecorder) { this.videoRecorder.stopRecording(function(blobURL) { callback(blobURL, 'video'); }); } if (this.gifRecorder) { this.gifRecorder.stopRecording(function(blobURL) { callback(blobURL, 'gif'); }); } }; /** * This method can be used to manually get all recorded blobs. * @param {function} callback - All recorded blobs are passed back to "callback" function. * @method * @memberof MRecordRTC * @example * recorder.getBlob(function(recording){ * var audioBlob = recording.audio; * var videoBlob = recording.video; * var gifBlob = recording.gif; * }); * // or * var audioBlob = recorder.getBlob().audio; * var videoBlob = recorder.getBlob().video; */ this.getBlob = function(callback) { var output = {}; if (this.audioRecorder) { output.audio = this.audioRecorder.getBlob(); } if (this.videoRecorder) { output.video = this.videoRecorder.getBlob(); } if (this.gifRecorder) { output.gif = this.gifRecorder.getBlob(); } if (callback) { callback(output); } return output; }; /** * This method can be used to manually get all recorded blobs' DataURLs. * @param {function} callback - All recorded blobs' DataURLs are passed back to "callback" function. * @method * @memberof MRecordRTC * @example * recorder.getDataURL(function(recording){ * var audioDataURL = recording.audio; * var videoDataURL = recording.video; * var gifDataURL = recording.gif; * }); */ this.getDataURL = function(callback) { this.getBlob(function(blob) { getDataURL(blob.audio, function(_audioDataURL) { getDataURL(blob.video, function(_videoDataURL) { callback({ audio: _audioDataURL, video: _videoDataURL }); }); }); }); function getDataURL(blob, callback00) { if (typeof Worker !== 'undefined') { var webWorker = processInWebWorker(function readFile(_blob) { postMessage(new FileReaderSync().readAsDataURL(_blob)); }); webWorker.onmessage = function(event) { callback00(event.data); }; webWorker.postMessage(blob); } else { var reader = new FileReader(); reader.readAsDataURL(blob); reader.onload = function(event) { callback00(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); var url; if (typeof URL !== 'undefined') { url = URL; } else if (typeof webkitURL !== 'undefined') { url = webkitURL; } else { throw 'Neither URL nor webkitURL detected.'; } url.revokeObjectURL(blob); return worker; } }; /** * This method can be used to ask {@link MRecordRTC} to write all recorded blobs into IndexedDB storage. * @method * @memberof MRecordRTC * @example * recorder.writeToDisk(); */ this.writeToDisk = function() { RecordRTC.writeToDisk({ audio: this.audioRecorder, video: this.videoRecorder, gif: this.gifRecorder }); }; /** * This method can be used to invoke save-as dialog for all recorded blobs. * @param {object} args - {audio: 'audio-name', video: 'video-name', gif: 'gif-name'} * @method * @memberof MRecordRTC * @example * recorder.save({ * audio: 'audio-file-name', * video: 'video-file-name', * gif : 'gif-file-name' * }); */ this.save = function(args) { args = args || { audio: true, video: true, gif: true }; if (!!args.audio && this.audioRecorder) { this.audioRecorder.save(typeof args.audio === 'string' ? args.audio : ''); } if (!!args.video && this.videoRecorder) { this.videoRecorder.save(typeof args.video === 'string' ? args.video : ''); } if (!!args.gif && this.gifRecorder) { this.gifRecorder.save(typeof args.gif === 'string' ? args.gif : ''); } }; } /** * 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 MRecordRTC * @example * MRecordRTC.getFromDisk('all', function(dataURL, type){ * if(type === 'audio') { } * if(type === 'video') { } * if(type === 'gif') { } * }); */ MRecordRTC.getFromDisk = RecordRTC.getFromDisk; /** * This method can be used to store recorded blobs into IndexedDB storage. * @param {object} options - {audio: Blob, video: Blob, gif: Blob} * @method * @memberof MRecordRTC * @example * MRecordRTC.writeToDisk({ * audio: audioBlob, * video: videoBlob, * gif : gifBlob * }); */ MRecordRTC.writeToDisk = RecordRTC.writeToDisk; if (typeof RecordRTC !== 'undefined') { RecordRTC.MRecordRTC = MRecordRTC; } var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45'; (function(that) { if (!that) { return; } if (typeof window !== 'undefined') { return; } if (typeof global === 'undefined') { return; } global.navigator = { userAgent: browserFakeUserAgent, getUserMedia: function() {} }; if (!global.console) { global.console = {}; } if (typeof global.console.debug === 'undefined') { global.console.debug = global.console.info = global.console.error = global.console.log = global.console.log || function() { console.log(arguments); }; } if (typeof document === 'undefined') { /*global document:true */ that.document = {}; document.createElement = document.captureStream = document.mozCaptureStream = function() { var obj = { getContext: function() { return obj; }, play: function() {}, pause: function() {}, drawImage: function() {}, toDataURL: function() { return ''; } }; return obj; }; that.HTMLVideoElement = function() {}; } if (typeof location === 'undefined') { /*global location:true */ that.location = { protocol: 'file:', href: '', hash: '' }; } if (typeof screen === 'undefined') { /*global screen:true */ that.screen = { width: 0, height: 0 }; } if (typeof URL === 'undefined') { /*global screen:true */ that.URL = { createObjectURL: function() { return ''; }, revokeObjectURL: function() { return ''; } }; } /*global window:true */ that.window = global; })(typeof global !== 'undefined' ? global : null); // _____________________________ // Cross-Browser-Declarations.js // animation-frame used in WebM recording /*jshint -W079 */ var requestAnimationFrame = window.requestAnimationFrame; if (typeof requestAnimationFrame === 'undefined') { if (typeof webkitRequestAnimationFrame !== 'undefined') { /*global requestAnimationFrame:true */ requestAnimationFrame = webkitRequestAnimationFrame; } if (typeof mozRequestAnimationFrame !== 'undefined') { /*global requestAnimationFrame:true */ requestAnimationFrame = mozRequestAnimationFrame; } } /*jshint -W079 */ var cancelAnimationFrame = window.cancelAnimationFrame; if (typeof cancelAnimationFrame === 'undefined') { if (typeof webkitCancelAnimationFrame !== 'undefined') { /*global cancelAnimationFrame:true */ cancelAnimationFrame = webkitCancelAnimationFrame; } if (typeof mozCancelAnimationFrame !== 'undefined') { /*global cancelAnimationFrame:true */ cancelAnimationFrame = mozCancelAnimationFrame; } } // WebAudio API representer var AudioContext = window.AudioContext; if (typeof AudioContext === 'undefined') { if (typeof webkitAudioContext !== 'undefined') { /*global AudioContext:true */ AudioContext = webkitAudioContext; } if (typeof mozAudioContext !== 'undefined') { /*global AudioContext:true */ AudioContext = mozAudioContext; } } /*jshint -W079 */ var URL = window.URL; if (typeof URL === 'undefined' && typeof webkitURL !== 'undefined') { /*global URL:true */ URL = webkitURL; } if (typeof navigator !== 'undefined') { // maybe window.navigator? if (typeof navigator.webkitGetUserMedia !== 'undefined') { navigator.getUserMedia = navigator.webkitGetUserMedia; } if (typeof navigator.mozGetUserMedia !== 'undefined') { navigator.getUserMedia = navigator.mozGetUserMedia; } } var isEdge = navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveBlob || !!navigator.msSaveOrOpenBlob); var isOpera = !!window.opera || navigator.userAgent.indexOf('OPR/') !== -1; var isChrome = !isOpera && !isEdge && !!navigator.webkitGetUserMedia; var MediaStream = window.MediaStream; if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') { MediaStream = webkitMediaStream; } /*global MediaStream:true */ if (typeof MediaStream !== 'undefined') { if (!('getVideoTracks' in MediaStream.prototype)) { MediaStream.prototype.getVideoTracks = function() { if (!this.getTracks) { return []; } var tracks = []; this.getTracks.forEach(function(track) { if (track.kind.toString().indexOf('video') !== -1) { tracks.push(track); } }); return tracks; }; MediaStream.prototype.getAudioTracks = function() { if (!this.getTracks) { return []; } var tracks = []; this.getTracks.forEach(function(track) { if (track.kind.toString().indexOf('audio') !== -1) { tracks.push(track); } }); return tracks; }; } if (!('stop' in MediaStream.prototype)) { MediaStream.prototype.stop = function() { this.getAudioTracks().forEach(function(track) { if (!!track.stop) { track.stop(); } }); this.getVideoTracks().forEach(function(track) { if (!!track.stop) { track.stop(); } }); }; } } // below function via: http://goo.gl/B3ae8c /** * @param {number} bytes - Pass bytes and get formafted string. * @returns {string} - formafted string * @example * bytesToSize(1024*1024*5) === '5 GB' * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} */ function bytesToSize(bytes) { var k = 1000; var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; if (bytes === 0) { return '0 Bytes'; } var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10); return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]; } /** * @param {Blob} file - File or Blob object. This parameter is required. * @param {string} fileName - Optional file name e.g. "Recorded-Video.webm" * @example * invokeSaveAsDialog(blob or file, [optional] fileName); * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} */ function invokeSaveAsDialog(file, fileName) { if (!file) { throw 'Blob object is required.'; } if (!file.type) { try { file.type = 'video/webm'; } catch (e) {} } var fileExtension = (file.type || 'video/webm').split('/')[1]; if (fileName && fileName.indexOf('.') !== -1) { var splitted = fileName.split('.'); fileName = splitted[0]; fileExtension = splitted[1]; } var fileFullName = (fileName || (Math.round(Math.random() * 9999999999) + 888888888)) + '.' + fileExtension; if (typeof navigator.msSaveOrOpenBlob !== 'undefined') { return navigator.msSaveOrOpenBlob(file, fileFullName); } else if (typeof navigator.msSaveBlob !== 'undefined') { return navigator.msSaveBlob(file, fileFullName); } var hyperlink = document.createElement('a'); hyperlink.href = URL.createObjectURL(file); hyperlink.target = '_blank'; hyperlink.download = fileFullName; if (!!navigator.mozGetUserMedia) { hyperlink.onclick = function() { (document.body || document.documentElement).removeChild(hyperlink); }; (document.body || document.documentElement).appendChild(hyperlink); } var evt = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); hyperlink.dispatchEvent(evt); if (!navigator.mozGetUserMedia) { URL.revokeObjectURL(hyperlink.href); } } // __________ (used to handle stuff like http://goo.gl/xmE5eg) issue #129 // Storage.js /** * Storage is a standalone object used by {@link RecordRTC} to store reusable objects e.g. "new AudioContext". * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} * @author {@link http://www.MuazKhan.com|Muaz Khan} * @example * Storage.AudioContext === webkitAudioContext * @property {webkitAudioContext} AudioContext - Keeps a reference to AudioContext object. * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} */ var Storage = {}; if (typeof AudioContext !== 'undefined') { Storage.AudioContext = AudioContext; } else if (typeof webkitAudioContext !== 'undefined') { Storage.AudioContext = webkitAudioContext; } if (typeof RecordRTC !== 'undefined') { RecordRTC.Storage = Storage; } function isMediaRecorderCompatible() { var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; var isChrome = !!window.chrome && !isOpera; var isFirefox = typeof window.InstallTrigger !== 'undefined'; if (isFirefox) { return true; } var nVer = navigator.appVersion; var nAgt = navigator.userAgent; var fullVersion = '' + parseFloat(navigator.appVersion); var majorVersion = parseInt(navigator.appVersion, 10); var nameOffset, verOffset, ix; if (isChrome || isOpera) { verOffset = nAgt.indexOf('Chrome'); fullVersion = nAgt.substring(verOffset + 7); } // trim the fullVersion string at semicolon/space if present if ((ix = fullVersion.indexOf(';')) !== -1) { fullVersion = fullVersion.substring(0, ix); } if ((ix = fullVersion.indexOf(' ')) !== -1) { fullVersion = fullVersion.substring(0, ix); } majorVersion = parseInt('' + fullVersion, 10); if (isNaN(majorVersion)) { fullVersion = '' + parseFloat(navigator.appVersion); majorVersion = parseInt(navigator.appVersion, 10); } return majorVersion >= 49; } // ______________________ // MediaStreamRecorder.js // todo: need to show alert boxes for incompatible cases // encoder only supports 48k/16k mono audio channel /* * Implementation of https://dvcs.w3.org/hg/dap/raw-file/default/media-stream-capture/MediaRecorder.html * The MediaRecorder accepts a mediaStream as input source passed from UA. When recorder starts, * a MediaEncoder will be created and accept the mediaStream as input source. * Encoder will get the raw data by track data changes, encode it by selected MIME Type, then store the encoded in EncodedBufferCache object. * The encoded data will be extracted on every timeslice passed from Start function call or by RequestData function. * Thread model: * When the recorder starts, it creates a "Media Encoder" thread to read data from MediaEncoder object and store buffer in EncodedBufferCache object. * Also extract the encoded data and create blobs on every timeslice passed from start function or RequestData function called by UA. */ /** * MediaStreamRecorder is an abstraction layer for "MediaRecorder API". It is used by {@link RecordRTC} to record MediaStream(s) in Firefox. * @summary Runs top over MediaRecorder API. * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT} * @author {@link http://www.MuazKhan.com|Muaz Khan} * @typedef MediaStreamRecorder * @class * @example * var options = { * mimeType: 'video/mp4', // audio/ogg or video/webm * audioBitsPerSecond : 256 * 8 * 1024, * videoBitsPerSecond : 256 * 8 * 1024, * bitsPerSecond: 256 * 8 * 1024, // if this is provided, skip above two * getNativeBlob: true // by default it is false * } * var recorder = new MediaStreamRecorder(MediaStream, options); * recorder.record(); * recorder.stop(function(blob) { * video.src = URL.createObjectURL(blob); * * // or * var blob = recorder.blob; * }); * @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 - {disableLogs:true, initCallback: function, mimeType: "video/webm", onAudioProcessStarted: function} */ function MediaStreamRecorder(mediaStream, config) { var self = this; config = config || { // bitsPerSecond: 256 * 8 * 1024, mimeType: 'video/webm' }; if (config.type =