UNPKG

@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.

614 lines (493 loc) 16.9 kB
// Last time updated: 2019-06-21 4:09:42 AM UTC // ________________________ // MultiStreamsMixer v1.2.2 // Open-Sourced: https://github.com/muaz-khan/MultiStreamsMixer // -------------------------------------------------- // Muaz Khan - www.MuazKhan.com // MIT License - www.WebRTC-Experiment.com/licence // -------------------------------------------------- function MultiStreamsMixer(arrayOfMediaStreams, elementClass) { var browserFakeUserAgent = "Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45"; (function(that) { if (typeof RecordRTC !== "undefined") { return; } 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.log === "undefined" || typeof global.console.error === "undefined" ) { global.console.error = global.console.log = global.console.log || function() { console.log(arguments); }; } if (typeof document === "undefined") { /*global document:true */ that.document = { documentElement: { appendChild: function() { return ""; }, }, }; document.createElement = document.captureStream = document.mozCaptureStream = function() { var obj = { getContext: function() { return obj; }, play: function() {}, pause: function() {}, drawImage: function() {}, toDataURL: function() { return ""; }, style: {}, }; 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); // requires: chrome://flags/#enable-experimental-web-platform-features elementClass = elementClass || "multi-streams-mixer"; var videos = []; var isStopDrawingFrames = false; var canvas = document.createElement("canvas"); var context = canvas.getContext("2d"); canvas.style.opacity = 0; canvas.style.position = "absolute"; canvas.style.zIndex = -1; canvas.style.top = "-1000em"; canvas.style.left = "-1000em"; canvas.className = elementClass; (document.body || document.documentElement).appendChild(canvas); this.disableLogs = false; this.frameInterval = 10; this.width = 360; this.height = 240; // use gain node to prevent echo this.useGainNode = true; var self = this; // _____________________________ // Cross-Browser-Declarations.js // 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" && typeof navigator.getUserMedia === "undefined" ) { // maybe window.navigator? if (typeof navigator.webkitGetUserMedia !== "undefined") { navigator.getUserMedia = navigator.webkitGetUserMedia; } if (typeof navigator.mozGetUserMedia !== "undefined") { navigator.getUserMedia = navigator.mozGetUserMedia; } } var MediaStream = window.MediaStream; if ( typeof MediaStream === "undefined" && typeof webkitMediaStream !== "undefined" ) { MediaStream = webkitMediaStream; } /*global MediaStream:true */ if (typeof MediaStream !== "undefined") { // override "stop" method for all browsers if (typeof MediaStream.prototype.stop === "undefined") { MediaStream.prototype.stop = function() { this.getTracks().forEach(function(track) { track.stop(); }); }; } } var Storage = {}; if (typeof AudioContext !== "undefined") { Storage.AudioContext = AudioContext; } else if (typeof webkitAudioContext !== "undefined") { Storage.AudioContext = webkitAudioContext; } function setSrcObject(stream, element) { if ("srcObject" in element) { element.srcObject = stream; } else if ("mozSrcObject" in element) { element.mozSrcObject = stream; } else { element.srcObject = stream; } } this.startDrawingFrames = function() { drawVideosToCanvas(); }; function drawVideosToCanvas() { if (isStopDrawingFrames) { return; } var videosLength = videos.length; var fullcanvas = false; var remaining = []; videos.forEach(function(video) { if (!video.stream) { video.stream = {}; } if (video.stream.fullcanvas) { fullcanvas = video; } else { // todo: video.stream.active or video.stream.live to fix blank frames issues? remaining.push(video); } }); if (fullcanvas) { canvas.width = fullcanvas.stream.width; canvas.height = fullcanvas.stream.height; } else if (remaining.length) { canvas.width = videosLength > 1 ? remaining[0].width * 2 : remaining[0].width; var height = 1; if (videosLength === 3 || videosLength === 4) { height = 2; } if (videosLength === 5 || videosLength === 6) { height = 3; } if (videosLength === 7 || videosLength === 8) { height = 4; } if (videosLength === 9 || videosLength === 10) { height = 5; } canvas.height = remaining[0].height * height; } else { canvas.width = self.width || 360; canvas.height = self.height || 240; } if (fullcanvas && fullcanvas instanceof HTMLVideoElement) { drawImage(fullcanvas); } remaining.forEach(function(video, idx) { drawImage(video, idx); }); setTimeout(drawVideosToCanvas, self.frameInterval); } function drawImage(video, idx) { if (isStopDrawingFrames) { return; } var x = 0; var y = 0; var width = video.width; var height = video.height; if (idx === 1) { x = video.width; } if (idx === 2) { y = video.height; } if (idx === 3) { x = video.width; y = video.height; } if (idx === 4) { y = video.height * 2; } if (idx === 5) { x = video.width; y = video.height * 2; } if (idx === 6) { y = video.height * 3; } if (idx === 7) { x = video.width; y = video.height * 3; } if (typeof video.stream.left !== "undefined") { x = video.stream.left; } if (typeof video.stream.top !== "undefined") { y = video.stream.top; } if (typeof video.stream.width !== "undefined") { width = video.stream.width; } if (typeof video.stream.height !== "undefined") { height = video.stream.height; } context.drawImage(video, x, y, width, height); if (typeof video.stream.onRender === "function") { video.stream.onRender(context, x, y, width, height, idx); } } function getMixedStream() { isStopDrawingFrames = false; var mixedVideoStream = getMixedVideoStream(); var mixedAudioStream = getMixedAudioStream(); if (mixedAudioStream) { mixedAudioStream .getTracks() .filter(function(t) { return t.kind === "audio"; }) .forEach(function(track) { mixedVideoStream.addTrack(track); }); } var fullcanvas; arrayOfMediaStreams.forEach(function(stream) { if (stream.fullcanvas) { fullcanvas = true; } }); // mixedVideoStream.prototype.appendStreams = appendStreams; // mixedVideoStream.prototype.resetVideoStreams = resetVideoStreams; // mixedVideoStream.prototype.clearRecordedData = clearRecordedData; return mixedVideoStream; } function getMixedVideoStream() { resetVideoStreams(); var capturedStream; if ("captureStream" in canvas) { capturedStream = canvas.captureStream(); } else if ("mozCaptureStream" in canvas) { capturedStream = canvas.mozCaptureStream(); } else if (!self.disableLogs) { console.error( "Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features" ); } var videoStream = new MediaStream(); capturedStream .getTracks() .filter(function(t) { return t.kind === "video"; }) .forEach(function(track) { videoStream.addTrack(track); }); canvas.stream = videoStream; return videoStream; } function getMixedAudioStream() { // via: @pehrsons if (!Storage.AudioContextConstructor) { Storage.AudioContextConstructor = new Storage.AudioContext(); } self.audioContext = Storage.AudioContextConstructor; self.audioSources = []; if (self.useGainNode === true) { self.gainNode = self.audioContext.createGain(); self.gainNode.connect(self.audioContext.destination); self.gainNode.gain.value = 0; // don't hear self } var audioTracksLength = 0; arrayOfMediaStreams.forEach(function(stream) { if ( !stream.getTracks().filter(function(t) { return t.kind === "audio"; }).length ) { return; } audioTracksLength++; var audioSource = self.audioContext.createMediaStreamSource(stream); if (self.useGainNode === true) { audioSource.connect(self.gainNode); } self.audioSources.push(audioSource); }); if (!audioTracksLength) { // because "self.audioContext" is not initialized // that's why we've to ignore rest of the code return; } self.audioDestination = self.audioContext.createMediaStreamDestination(); self.audioSources.forEach(function(audioSource) { audioSource.connect(self.audioDestination); }); return self.audioDestination.stream; } function getVideo(stream) { var video = document.createElement("video"); setSrcObject(stream, video); video.className = elementClass; video.muted = true; video.volume = 0; video.width = stream.width || self.width || 360; video.height = stream.height || self.height || 240; video.play(); return video; } this.appendStreams = function(streams) { if (!streams) { throw "First parameter is required."; } if (!(streams instanceof Array)) { streams = [streams]; } streams.forEach(function(stream) { var newStream = new MediaStream(); if ( stream.getTracks().filter(function(t) { return t.kind === "video"; }).length ) { var video = getVideo(stream); video.stream = stream; videos.push(video); newStream.addTrack( stream.getTracks().filter(function(t) { return t.kind === "video"; })[0] ); } if ( stream.getTracks().filter(function(t) { return t.kind === "audio"; }).length ) { var audioSource = self.audioContext.createMediaStreamSource(stream); self.audioDestination = self.audioContext.createMediaStreamDestination(); audioSource.connect(self.audioDestination); newStream.addTrack( self.audioDestination.stream.getTracks().filter(function(t) { return t.kind === "audio"; })[0] ); } arrayOfMediaStreams.push(newStream); }); }; this.releaseStreams = function() { videos = []; isStopDrawingFrames = true; if (self.gainNode) { self.gainNode.disconnect(); self.gainNode = null; } if (self.audioSources.length) { self.audioSources.forEach(function(source) { source.disconnect(); }); self.audioSources = []; } if (self.audioDestination) { self.audioDestination.disconnect(); self.audioDestination = null; } if (self.audioContext) { self.audioContext.close(); } self.audioContext = null; context.clearRect(0, 0, canvas.width, canvas.height); if (canvas.stream) { canvas.stream.stop(); canvas.stream = null; } }; this.resetVideoStreams = function(streams) { if (streams && !(streams instanceof Array)) { streams = [streams]; } resetVideoStreams(streams); }; function resetVideoStreams(streams) { videos = []; streams = streams || arrayOfMediaStreams; // via: @adrian-ber streams.forEach(function(stream) { if ( !stream.getTracks().filter(function(t) { return t.kind === "video"; }).length ) { return; } var video = getVideo(stream); video.stream = stream; videos.push(video); }); } // for debugging this.name = "MultiStreamsMixer"; this.toString = function() { return this.name; }; this.getMixedStream = getMixedStream; } if (typeof RecordRTC === "undefined") { if (typeof module !== "undefined" /* && !!module.exports*/ ) { module.exports = MultiStreamsMixer; } if (typeof define === "function" && define.amd) { define("MultiStreamsMixer", [], function() { return MultiStreamsMixer; }); } }