recordrtc
Version:
RecordRTC is a server-less (entire client-side) JavaScript library that can be used to record WebRTC audio/video media streams. It supports cross-browser audio/video recording.
1,514 lines (1,315 loc) • 193 kB
JavaScript
'use strict';
// Last time updated: 2019-06-18 3:20:07 AM UTC
// ________________
// RecordRTC v5.5.7
// 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 WebRTC JavaScript library for audio/video as well as screen activity recording. It supports Chrome, Firefox, Opera, Android, and Microsoft Edge. Platforms: Linux, Mac and Windows.
* @summary Record audio, video or screen inside the browser.
* @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
* @author {@link http://www.MuazKhan.com|Muaz Khan}
* @typedef RecordRTC
* @class
* @example
* var recorder = RecordRTC(mediaStream or [arrayOfMediaStream], {
* type: 'video', // audio or video or gif or canvas
* recorderType: MediaStreamRecorder || CanvasRecorder || StereoAudioRecorder || Etc
* });
* recorder.startRecording();
* @see For further information:
* @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
* @param {MediaStream} mediaStream - Single media-stream object, array of media-streams, html-canvas-element, etc.
* @param {object} config - {type:"video", recorderType: MediaStreamRecorder, disableLogs: true, numberOfAudioChannels: 1, bufferSize: 0, sampleRate: 0, desiredSampRate: 16000, video: HTMLVideoElement, etc.}
*/
function RecordRTC(mediaStream, config) {
if (!mediaStream) {
throw 'First parameter is required.';
}
config = config || {
type: 'video'
};
config = new RecordRTCConfiguration(mediaStream, config);
// a reference to user's recordRTC object
var self = this;
function startRecording(config2) {
if (!!config2) {
// allow users to set options using startRecording method
// config2 is similar to main "config" object (second parameter over RecordRTC constructor)
config = new RecordRTCConfiguration(mediaStream, config2);
}
if (!config.disableLogs) {
console.log('started recording ' + config.type + ' stream.');
}
if (mediaRecorder) {
mediaRecorder.clearRecordedData();
mediaRecorder.record();
setState('recording');
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; // recorder.initRecorder should be call-backed once.
};
}
var Recorder = new GetRecorderType(mediaStream, config);
mediaRecorder = new Recorder(mediaStream, config);
mediaRecorder.record();
setState('recording');
if (!config.disableLogs) {
console.log('Initialized recorderType:', mediaRecorder.constructor.name, 'for output-type:', config.type);
}
}
function stopRecording(callback) {
callback = callback || function() {};
if (!mediaRecorder) {
warningLog();
return;
}
if (self.state === 'paused') {
self.resumeRecording();
setTimeout(function() {
stopRecording(callback);
}, 1);
return;
}
if (self.state !== 'recording' && !config.disableLogs) {
console.warn('Recording state should be: "recording", however current state is: ', self.state);
}
if (!config.disableLogs) {
console.log('Stopped recording ' + config.type + ' stream.');
}
if (config.type !== 'gif') {
mediaRecorder.stop(_callback);
} else {
mediaRecorder.stop();
_callback();
}
setState('stopped');
function _callback(__blob) {
if (!mediaRecorder) {
if (typeof callback.call === 'function') {
callback.call(self, '');
} else {
callback('');
}
return;
}
Object.keys(mediaRecorder).forEach(function(key) {
if (typeof mediaRecorder[key] === 'function') {
return;
}
self[key] = mediaRecorder[key];
});
var blob = mediaRecorder.blob;
if (!blob) {
if (__blob) {
mediaRecorder.blob = blob = __blob;
} else {
throw 'Recording failed.';
}
}
if (blob && !config.disableLogs) {
console.log(blob.type, '->', bytesToSize(blob.size));
}
if (callback) {
var url;
try {
url = URL.createObjectURL(blob);
} catch (e) {}
if (typeof callback.call === 'function') {
callback.call(self, url);
} else {
callback(url);
}
}
if (!config.autoWriteToDisk) {
return;
}
getDataURL(function(dataURL) {
var parameter = {};
parameter[config.type + 'Blob'] = dataURL;
DiskStorage.Store(parameter);
});
}
}
function pauseRecording() {
if (!mediaRecorder) {
warningLog();
return;
}
if (self.state !== 'recording') {
if (!config.disableLogs) {
console.warn('Unable to pause the recording. Recording state: ', self.state);
}
return;
}
setState('paused');
mediaRecorder.pause();
if (!config.disableLogs) {
console.log('Paused recording.');
}
}
function resumeRecording() {
if (!mediaRecorder) {
warningLog();
return;
}
if (self.state !== 'paused') {
if (!config.disableLogs) {
console.warn('Unable to resume the recording. Recording state: ', self.state);
}
return;
}
setState('recording');
// not all libs have this method yet
mediaRecorder.resume();
if (!config.disableLogs) {
console.log('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 finish its job yet.');
}
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) {
try {
var blob = URL.createObjectURL(new Blob([_function.toString(),
'this.onmessage = function (eee) {' + _function.name + '(eee.data);}'
], {
type: 'application/javascript'
}));
var worker = new Worker(blob);
URL.revokeObjectURL(blob);
return worker;
} catch (e) {}
}
}
function handleRecordingDuration(counter) {
counter = counter || 0;
if (self.state === 'paused') {
setTimeout(function() {
handleRecordingDuration(counter);
}, 1000);
return;
}
if (self.state === 'stopped') {
return;
}
if (counter >= self.recordingDuration) {
stopRecording(self.onRecordingStopped);
return;
}
counter += 1000; // 1-second
setTimeout(function() {
handleRecordingDuration(counter);
}, 1000);
}
function setState(state) {
if (!self) {
return;
}
self.state = state;
if (typeof self.onStateChanged.call === 'function') {
self.onStateChanged.call(self, state);
} else {
self.onStateChanged(state);
}
}
var WARNING = 'It seems that recorder is destroyed or "startRecording" is not invoked for ' + config.type + ' recorder.';
function warningLog() {
if (config.disableLogs === true) {
return;
}
console.warn(WARNING);
}
var mediaRecorder;
var returnObject = {
/**
* This method starts the recording.
* @method
* @memberof RecordRTC
* @instance
* @example
* var recorder = RecordRTC(mediaStream, {
* type: 'video'
* });
* recorder.startRecording();
*/
startRecording: startRecording,
/**
* This method stops the recording. It is strongly recommended to get "blob" or "URI" inside the callback to make sure all recorders finished their job.
* @param {function} callback - Callback to get the recorded blob.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.stopRecording(function() {
* // use either "this" or "recorder" object; both are identical
* video.src = this.toURL();
* var blob = this.getBlob();
* });
*/
stopRecording: stopRecording,
/**
* This method pauses the recording. You can resume recording using "resumeRecording" method.
* @method
* @memberof RecordRTC
* @instance
* @todo Firefox is unable to pause the recording. Fix it.
* @example
* recorder.pauseRecording(); // pause the recording
* recorder.resumeRecording(); // resume again
*/
pauseRecording: pauseRecording,
/**
* This method resumes the recording.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.pauseRecording(); // first of all, pause the recording
* recorder.resumeRecording(); // now resume it
*/
resumeRecording: resumeRecording,
/**
* This method initializes the recording.
* @method
* @memberof RecordRTC
* @instance
* @todo This method should be deprecated.
* @example
* recorder.initRecorder();
*/
initRecorder: initRecorder,
/**
* Ask RecordRTC to auto-stop the recording after 5 minutes.
* @method
* @memberof RecordRTC
* @instance
* @example
* var fiveMinutes = 5 * 1000 * 60;
* recorder.setRecordingDuration(fiveMinutes, function() {
* var blob = this.getBlob();
* video.src = this.toURL();
* });
*
* // or otherwise
* recorder.setRecordingDuration(fiveMinutes).onRecordingStopped(function() {
* var blob = this.getBlob();
* video.src = this.toURL();
* });
*/
setRecordingDuration: function(recordingDuration, callback) {
if (typeof recordingDuration === 'undefined') {
throw 'recordingDuration is required.';
}
if (typeof recordingDuration !== 'number') {
throw 'recordingDuration must be a number.';
}
self.recordingDuration = recordingDuration;
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
* @todo Figure out the difference between "reset" and "clearRecordedData" methods.
* @example
* recorder.clearRecordedData();
*/
clearRecordedData: function() {
if (!mediaRecorder) {
warningLog();
return;
}
mediaRecorder.clearRecordedData();
if (!config.disableLogs) {
console.log('Cleared old recorded data.');
}
},
/**
* Get the recorded blob. Use this method inside the "stopRecording" callback.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.stopRecording(function() {
* var blob = this.getBlob();
*
* var file = new File([blob], 'filename.webm', {
* type: 'video/webm'
* });
*
* var formData = new FormData();
* formData.append('file', file); // upload "File" object rather than a "Blob"
* uploadToServer(formData);
* });
* @returns {Blob} Returns recorded data as "Blob" object.
*/
getBlob: function() {
if (!mediaRecorder) {
warningLog();
return;
}
return mediaRecorder.blob;
},
/**
* Get data-URI instead of Blob.
* @param {function} callback - Callback to get the Data-URI.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.stopRecording(function() {
* recorder.getDataURL(function(dataURI) {
* video.src = dataURI;
* });
* });
*/
getDataURL: getDataURL,
/**
* Get virtual/temporary URL. Usage of this URL is limited to current tab.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.stopRecording(function() {
* video.src = this.toURL();
* });
* @returns {String} Returns a virtual/temporary URL for the recorded "Blob".
*/
toURL: function() {
if (!mediaRecorder) {
warningLog();
return;
}
return URL.createObjectURL(mediaRecorder.blob);
},
/**
* Get internal recording object (i.e. internal module) e.g. MutliStreamRecorder, MediaStreamRecorder, StereoAudioRecorder or WhammyRecorder etc.
* @method
* @memberof RecordRTC
* @instance
* @example
* var internal = recorder.getInternalRecorder();
* if(internal instanceof MultiStreamRecorder) {
* internal.addStreams([newAudioStream]);
* internal.resetVideoStreams([screenStream]);
* }
* @returns {Object} Returns internal recording object.
*/
getInternalRecorder: function() {
return mediaRecorder;
},
/**
* Invoke save-as dialog to save the recorded blob into your disk.
* @param {string} fileName - Set your own file name.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.stopRecording(function() {
* this.save('file-name');
*
* // or manually:
* invokeSaveAsDialog(this.getBlob(), 'filename.webm');
* });
*/
save: function(fileName) {
if (!mediaRecorder) {
warningLog();
return;
}
invokeSaveAsDialog(mediaRecorder.blob, fileName);
},
/**
* This method gets a blob from indexed-DB storage.
* @param {function} callback - Callback to get the recorded blob.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.getFromDisk(function(dataURL) {
* video.src = dataURL;
* });
*/
getFromDisk: function(callback) {
if (!mediaRecorder) {
warningLog();
return;
}
RecordRTC.getFromDisk(config.type, callback);
},
/**
* This method appends an 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
* @todo This method should be deprecated.
* @example
* var arrayOfWebPImages = [];
* arrayOfWebPImages.push({
* duration: index,
* image: 'data:image/webp;base64,...'
* });
* recorder.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">"recorder.getBlob()"</code> method. Usage of "getBlob" is recommended, though.
* @property {Blob} blob - Recorded Blob can be accessed using this property.
* @memberof RecordRTC
* @instance
* @readonly
* @example
* recorder.stopRecording(function() {
* var blob = this.blob;
*
* // below one is recommended
* var blob = this.getBlob();
* });
*/
blob: null,
/**
* This works only with {recorderType:StereoAudioRecorder}. Use this property on "stopRecording" to verify the encoder's sample-rates.
* @property {number} bufferSize - Buffer-size used to encode the WAV container
* @memberof RecordRTC
* @instance
* @readonly
* @example
* recorder.stopRecording(function() {
* alert('Recorder used this buffer-size: ' + this.bufferSize);
* });
*/
bufferSize: 0,
/**
* This works only with {recorderType:StereoAudioRecorder}. Use this property on "stopRecording" to verify the encoder's sample-rates.
* @property {number} sampleRate - Sample-rates used to encode the WAV container
* @memberof RecordRTC
* @instance
* @readonly
* @example
* recorder.stopRecording(function() {
* alert('Recorder used these sample-rates: ' + this.sampleRate);
* });
*/
sampleRate: 0,
/**
* {recorderType:StereoAudioRecorder} returns ArrayBuffer object.
* @property {ArrayBuffer} buffer - Audio ArrayBuffer, supported only in Chrome.
* @memberof RecordRTC
* @instance
* @readonly
* @example
* recorder.stopRecording(function() {
* var arrayBuffer = this.buffer;
* alert(arrayBuffer.byteLength);
* });
*/
buffer: null,
/**
* This method resets the recorder. So that you can reuse single recorder instance many times.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.reset();
* recorder.startRecording();
*/
reset: function() {
if (mediaRecorder && typeof mediaRecorder.clearRecordedData === 'function') {
mediaRecorder.clearRecordedData();
}
mediaRecorder = null;
setState('inactive');
self.blob = null;
},
/**
* This method is called whenever recorder's state changes. Use this as an "event".
* @property {String} state - A recorder's state can be: recording, paused, stopped or inactive.
* @method
* @memberof RecordRTC
* @instance
* @example
* recorder.onStateChanged = function(state) {
* console.log('Recorder state: ', state);
* };
*/
onStateChanged: function(state) {
if (!config.disableLogs) {
console.log('Recorder state changed:', state);
}
},
/**
* A recorder can have inactive, recording, paused or stopped states.
* @property {String} state - A recorder's state can be: recording, paused, stopped or inactive.
* @memberof RecordRTC
* @static
* @readonly
* @example
* // this looper function will keep you updated about the recorder's states.
* (function looper() {
* document.querySelector('h1').innerHTML = 'Recorder\'s state is: ' + recorder.state;
* if(recorder.state === 'stopped') return; // ignore+stop
* setTimeout(looper, 1000); // update after every 3-seconds
* })();
* recorder.startRecording();
*/
state: 'inactive',
/**
* Get recorder's readonly state.
* @method
* @memberof RecordRTC
* @example
* var state = recorder.getState();
* @returns {String} Returns recording state.
*/
getState: function() {
return self.state;
},
/**
* Destroy RecordRTC instance. Clear all recorders and objects.
* @method
* @memberof RecordRTC
* @example
* recorder.destroy();
*/
destroy: function() {
var disableLogsCache = config.disableLogs;
config = {
disableLogs: true
};
self.reset();
setState('destroyed');
returnObject = self = null;
if (Storage.AudioContextConstructor) {
Storage.AudioContextConstructor.close();
Storage.AudioContextConstructor = null;
}
config.disableLogs = disableLogsCache;
if (!config.disableLogs) {
console.warn('RecordRTC is destroyed.');
}
},
/**
* RecordRTC version number
* @property {String} version - Release version number.
* @memberof RecordRTC
* @static
* @readonly
* @example
* alert(recorder.version);
*/
version: '5.5.7'
};
if (!this) {
self = returnObject;
return returnObject;
}
// if someone wants to use RecordRTC with the "new" keyword.
for (var prop in returnObject) {
this[prop] = returnObject[prop];
}
self = this;
return returnObject;
}
RecordRTC.version = '5.5.7';
if (typeof module !== 'undefined' /* && !!module.exports*/ ) {
module.exports = RecordRTC;
}
if (typeof define === 'function' && define.amd) {
define('RecordRTC', [], function() {
return RecordRTC;
});
}
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
});
});
}
};
// __________________________
// 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.audio && !!config.video) {
config.type = 'video';
} else if (!!config.audio && !config.video) {
config.type = 'audio';
}
}
if (config.recorderType && !config.type) {
if (config.recorderType === WhammyRecorder || config.recorderType === CanvasRecorder || (typeof WebAssemblyRecorder !== 'undefined' && config.recorderType === WebAssemblyRecorder)) {
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 (getTracks(mediaStream, 'audio').length && getTracks(mediaStream, 'video').length) {
config.type = 'video';
} else if (!getTracks(mediaStream, 'audio').length && getTracks(mediaStream, 'video').length) {
config.type = 'video';
} else if (getTracks(mediaStream, 'audio').length && !getTracks(mediaStream, 'video').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;
if (typeof WebAssemblyRecorder !== 'undefined' && typeof ReadableStream !== 'undefined') {
recorder = WebAssemblyRecorder;
}
}
// 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 (getTracks(mediaStream, 'video').length || getTracks(mediaStream, 'audio').length) {
// audio-only recording
if (config.type === 'audio') {
if (typeof MediaRecorder.isTypeSupported === 'function' && MediaRecorder.isTypeSupported('audio/webm')) {
recorder = MediaStreamRecorder;
}
// else recorder = StereoAudioRecorder;
} else {
// video or screen tracks
if (typeof MediaRecorder.isTypeSupported === 'function' && MediaRecorder.isTypeSupported('video/webm')) {
recorder = MediaStreamRecorder;
}
}
}
}
if (mediaStream instanceof Array && mediaStream.length) {
recorder = MultiStreamRecorder;
}
if (config.recorderType) {
recorder = config.recorderType;
}
if (!config.disableLogs && !!recorder && !!recorder.name) {
console.log('Using recorderType:', recorder.name || recorder.constructor.name);
}
if (!recorder && isSafari) {
recorder = MediaStreamRecorder;
}
return recorder;
}
// _____________
// MRecordRTC.js
/**
* MRecordRTC runs on top of {@link RecordRTC} to bring multiple recordings in a 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 or WebAssemblyRecorder or CanvasRecorder
* 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.
* @requires {@link RecordRTC}
*/
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 the 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 or WebAssemblyRecorder or CanvasRecorder
* 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() && !getTracks(mediaStream, 'audio').length) {
mediaType.audio = false;
}
if (typeof mediaType.video !== 'function' && isMediaRecorderCompatible() && !getTracks(mediaStream, 'video').length) {
mediaType.video = false;
}
if (typeof mediaType.gif !== 'function' && isMediaRecorderCompatible() && !getTracks(mediaStream, 'video').length) {
mediaType.gif = false;
}
if (!mediaType.audio && !mediaType.video && !mediaType.gif) {
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,
timeSlice: this.timeSlice,
onTimeStamp: this.onTimeStamp
});
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 = getTracks(mediaStream, 'video')[0];
if (isFirefox) {
newStream = new MediaStream();
newStream.addTrack(videoTrack);
if (recorderType && recorderType === WhammyRecorder) {
// Firefox does NOT supports webp-encoding yet
// But Firefox do supports WebAssemblyRecorder
recorderType = MediaStreamRecorder;
}
} else {
newStream = new MediaStream();
newStream.addTrack(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,
timeSlice: this.timeSlice,
onTimeStamp: this.onTimeStamp,
workerPath: this.workerPath,
webAssemblyPath: this.webAssemblyPath,
frameRate: this.frameRate, // used by WebAssemblyRecorder; values: usually 30; accepts any.
bitrate: this.bitrate // used by WebAssemblyRecorder; values: 0 to 1000+
});
if (!mediaType.audio) {
this.videoRecorder.startRecording();
}
}
if (!!mediaType.audio && !!mediaType.video) {
var self = this;
var isSingleRecorder = isMediaRecorderCompatible() === true;
if (mediaType.audio instanceof StereoAudioRecorder && !!mediaType.video) {
isSingleRecorder = false;
} else if (mediaType.audio !== true && mediaType.video !== true && mediaType.audio !== mediaType.video) {
isSingleRecorder = false;
}
if (isSingleRecorder === true) {
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 stops recording.
* @param {function} callback - Callback function is invoked when all encoders finished 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 pauses recording.
* @method
* @memberof MRecordRTC
* @example
* recorder.pauseRecording();
*/
this.pauseRecording = function() {
if (this.audioRecorder) {
this.audioRecorder.pauseRecording();
}
if (this.videoRecorder) {
this.videoRecorder.pauseRecording();
}
if (this.gifRecorder) {
this.gifRecorder.pauseRecording();
}
};
/**
* This method resumes recording.
* @method
* @memberof MRecordRTC
* @example
* recorder.resumeRecording();
*/
this.resumeRecording = function() {
if (this.audioRecorder) {
this.audioRecorder.resumeRecording();
}
if (this.videoRecorder) {
this.videoRecorder.resumeRecording();
}
if (this.gifRecorder) {
this.gifRecorder.resumeRecording();
}
};
/**
* This method can be used to manually get all recorded blobs.
* @param {function} callback - All recorded blobs are passed back to the "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;
};
/**
* Destroy all recorder instances.
* @method
* @memberof MRecordRTC
* @example
* recorder.destroy();
*/
this.destroy = function() {
if (this.audioRecorder) {
this.audioRecorder.destroy();
this.audioRecorder = null;
}
if (this.videoRecorder) {
this.videoRecorder.destroy();
this.videoRecorder = null;
}
if (this.gifRecorder) {
this.gifRecorder.destroy();
this.gifRecorder = null;
}
};
/**
* This method can be used to manually get all recorded blobs' DataURLs.
* @param {function} callback - All recorded blobs' DataURLs are passed back to the "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) {
if (blob.audio && blob.video) {
getDataURL(blob.audio, function(_audioDataURL) {
getDataURL(blob.video, function(_videoDataURL) {
callback({
audio: _audioDataURL,
video: _videoDataURL
});
});
});
} else if (blob.audio) {
getDataURL(blob.audio, function(_audioDataURL) {
callback({
audio: _audioDataURL
});
});
} else if (blob.video) {
getDataURL(blob.video, function(_videoDataURL) {
callback({
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 (eee) {' + _function.name + '(eee.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 a 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,
gi