@vikasietum_tecknology/record-rtc
Version:
record-rtc is a library based on recordrtc library. In this forked version of the original library we have optimized the memory management. The video recording is stored in IndexDB in chunks.
1,611 lines (1,408 loc) • 210 kB
JavaScript
"use strict";
// Last time updated: 2025-02-26 9:27:31 AM UTC
// ________________
// RecordRTC v5.6.11
// 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/blob/master/LICENSE|MIT}
* @author {@link https://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.}
* @example
* {
* type: "video",
* recorderType: MediaStreamRecorder,
* disableLogs: true,
* numberOfAudioChannels: 1,
* bufferSize: 0,
* sampleRate: 0,
* desiredSampRate: 16000,
* video: HTMLVideoElement,
* }
*/
function RecordRTC(mediaStream, config, db) {
if (!config.idb) {
throw new Error("Please provide the IDB config.");
} else if (!config.idb.version) {
throw new Error("Please provide IDB store version");
}
// var db = new Dexie(config.idb.storeName);
// db.version(config.idb.version).stores({
// recordings: config.idb.schema,
// });
db.version(config.idb.version).stores({
blobs: "++id,current",
});
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 (!config.disableLogs) {
console.debug("RecordRTC version: ", self.version);
}
clearExistingBlobs();
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.debug("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, db);
mediaRecorder.record();
setState("recording");
if (!config.disableLogs) {
console.debug(
"Initialized recorderType:",
mediaRecorder.constructor.name,
"for output-type:",
config.type
);
}
}
function stopRecording(recordId, callback, dbSaveCallback) {
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.debug("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.debug(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);
}
}
console.debug("[ABM]", "Moving content to recordings");
moveToRecordings(recordId, blob, dbSaveCallback);
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.debug("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.debug("Resumed recording.");
}
}
function readFile(_blob) {
postMessage(new FileReaderSync().readAsDataURL(_blob));
}
function getMetadata(activitySessionId) {
return db.recordings
.where("activitySessionId")
.equals(activitySessionId)
.toArray();
}
function moveToRecordings(recordId, blob, dbSaveCallback) {
db.recordings
.update(recordId, {
filename: config.filename,
content: blob,
})
.then(function(response) {
console.debug("[ABM]", "Moved to recordings");
db.blobs
.clear()
.then(function() {
console.debug("[ABM]", "Blobs table cleared");
blob = null;
})
.catch(function(err) {
console.error("[ABM]", "Could not clear blobs", err);
});
if (dbSaveCallback) {
console.debug("[ABM]", "Calling dbSaveCallback...");
dbSaveCallback();
}
})
.catch(function(err) {
console.error("[ABM]", "Could not move the file contents", err);
});
}
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);
}
}
function clearExistingBlobs() {
db.blobs.clear()
.then(function() {
console.debug("[ABM]", "Blobs table cleared");
}).catch(function(err) {
console.error("[ABM]", "Could not clear blobs", err);
});
}
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.debug("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 internalRecorder = recorder.getInternalRecorder();
* if(internalRecorder instanceof MultiStreamRecorder) {
* internalRecorder.addStreams([newAudioStream]);
* internalRecorder.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 (self.state === "recording" && !config.disableLogs) {
console.warn("Stop an active recorder.");
}
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.debug('Recorder state: ', state);
* };
*/
onStateChanged: function(state) {
if (!config.disableLogs) {
console.debug("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.debug("RecordRTC is destroyed.");
}
},
/**
* RecordRTC version number
* @property {String} version - Release version number.
* @memberof RecordRTC
* @static
* @readonly
* @example
* alert(recorder.version);
*/
version: "5.6.11",
/**
* The method returns the metadata about the recording using the activitySessionId.
* @property {String} activitySessionId - The activity session unique identifier
* @method
* @memberof RecordRTC
* @instance
*/
getMetadata: getMetadata,
};
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.6.11";
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/blob/master/LICENSE|MIT}
* @author {@link https://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/blob/master/LICENSE|MIT}
* @author {@link https://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/blob/master/LICENSE|MIT}
* @author {@link https://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,
});