469 lines
13 KiB
JavaScript
469 lines
13 KiB
JavaScript
|
// Last time updated: 2018-03-02 2:56:28 AM UTC
|
||
|
|
||
|
// ________________________
|
||
|
// MultiStreamsMixer v1.0.5
|
||
|
|
||
|
// Open-Sourced: https://github.com/muaz-khan/MultiStreamsMixer
|
||
|
|
||
|
// --------------------------------------------------
|
||
|
// Muaz Khan - www.MuazKhan.com
|
||
|
// MIT License - www.WebRTC-Experiment.com/licence
|
||
|
// --------------------------------------------------
|
||
|
|
||
|
function MultiStreamsMixer(arrayOfMediaStreams) {
|
||
|
|
||
|
// requires: chrome://flags/#enable-experimental-web-platform-features
|
||
|
|
||
|
var videos = [];
|
||
|
var isStopDrawingFrames = false;
|
||
|
|
||
|
var canvas = document.createElement('canvas');
|
||
|
var context = canvas.getContext('2d');
|
||
|
canvas.style = 'opacity:0;position:absolute;z-index:-1;top: -100000000;left:-1000000000; margin-top:-1000000000;margin-left:-1000000000;';
|
||
|
(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') {
|
||
|
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;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// 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, ignoreCreateObjectURL) {
|
||
|
if ('createObjectURL' in URL && !ignoreCreateObjectURL) {
|
||
|
try {
|
||
|
element.src = URL.createObjectURL(stream);
|
||
|
} catch (e) {
|
||
|
setSrcObject(stream, element, true);
|
||
|
return;
|
||
|
}
|
||
|
} else if ('srcObject' in element) {
|
||
|
element.srcObject = stream;
|
||
|
} else if ('mozSrcObject' in element) {
|
||
|
element.mozSrcObject = stream;
|
||
|
} else {
|
||
|
alert('createObjectURL/srcObject both are not supported.');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 {
|
||
|
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.getAudioTracks().forEach(function(track) {
|
||
|
mixedVideoStream.addTrack(track);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
var fullcanvas;
|
||
|
arrayOfMediaStreams.forEach(function(stream) {
|
||
|
if (stream.fullcanvas) {
|
||
|
fullcanvas = true;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
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.getVideoTracks().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.getAudioTracks().length) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
audioTracksLength++;
|
||
|
|
||
|
var audioSource = self.audioContext.createMediaStreamSource(stream);
|
||
|
|
||
|
if (self.useGainNode === true) {
|
||
|
audioSource.connect(self.gainNode);
|
||
|
}
|
||
|
|
||
|
self.audioSources.push(audioSource);
|
||
|
});
|
||
|
|
||
|
if (!audioTracksLength) {
|
||
|
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.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];
|
||
|
}
|
||
|
|
||
|
arrayOfMediaStreams.concat(streams);
|
||
|
|
||
|
streams.forEach(function(stream) {
|
||
|
if (stream.getVideoTracks().length) {
|
||
|
var video = getVideo(stream);
|
||
|
video.stream = stream;
|
||
|
videos.push(video);
|
||
|
}
|
||
|
|
||
|
if (stream.getAudioTracks().length && self.audioContext) {
|
||
|
var audioSource = self.audioContext.createMediaStreamSource(stream);
|
||
|
audioSource.connect(self.audioDestination);
|
||
|
self.audioSources.push(audioSource);
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
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.getVideoTracks().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;
|
||
|
|
||
|
}
|