1// Last time updated: 2019-06-21 4:09:42 AM UTC 2 3// ________________________ 4// MultiStreamsMixer v1.2.2 5 6// Open-Sourced: https://github.com/muaz-khan/MultiStreamsMixer 7 8// -------------------------------------------------- 9// Muaz Khan - www.MuazKhan.com 10// MIT License - www.WebRTC-Experiment.com/licence 11// -------------------------------------------------- 12 13function MultiStreamsMixer(arrayOfMediaStreams, elementClass) { 14 15 var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45'; 16 17 (function(that) { 18 if (typeof RecordRTC !== 'undefined') { 19 return; 20 } 21 22 if (!that) { 23 return; 24 } 25 26 if (typeof window !== 'undefined') { 27 return; 28 } 29 30 if (typeof global === 'undefined') { 31 return; 32 } 33 34 global.navigator = { 35 userAgent: browserFakeUserAgent, 36 getUserMedia: function() {} 37 }; 38 39 if (!global.console) { 40 global.console = {}; 41 } 42 43 if (typeof global.console.log === 'undefined' || typeof global.console.error === 'undefined') { 44 global.console.error = global.console.log = global.console.log || function() { 45 console.log(arguments); 46 }; 47 } 48 49 if (typeof document === 'undefined') { 50 /*global document:true */ 51 that.document = { 52 documentElement: { 53 appendChild: function() { 54 return ''; 55 } 56 } 57 }; 58 59 document.createElement = document.captureStream = document.mozCaptureStream = function() { 60 var obj = { 61 getContext: function() { 62 return obj; 63 }, 64 play: function() {}, 65 pause: function() {}, 66 drawImage: function() {}, 67 toDataURL: function() { 68 return ''; 69 }, 70 style: {} 71 }; 72 return obj; 73 }; 74 75 that.HTMLVideoElement = function() {}; 76 } 77 78 if (typeof location === 'undefined') { 79 /*global location:true */ 80 that.location = { 81 protocol: 'file:', 82 href: '', 83 hash: '' 84 }; 85 } 86 87 if (typeof screen === 'undefined') { 88 /*global screen:true */ 89 that.screen = { 90 width: 0, 91 height: 0 92 }; 93 } 94 95 if (typeof URL === 'undefined') { 96 /*global screen:true */ 97 that.URL = { 98 createObjectURL: function() { 99 return ''; 100 }, 101 revokeObjectURL: function() { 102 return ''; 103 } 104 }; 105 } 106 107 /*global window:true */ 108 that.window = global; 109 })(typeof global !== 'undefined' ? global : null); 110 111 // requires: chrome://flags/#enable-experimental-web-platform-features 112 113 elementClass = elementClass || 'multi-streams-mixer'; 114 115 var videos = []; 116 var isStopDrawingFrames = false; 117 118 var canvas = document.createElement('canvas'); 119 var context = canvas.getContext('2d'); 120 canvas.style.opacity = 0; 121 canvas.style.position = 'absolute'; 122 canvas.style.zIndex = -1; 123 canvas.style.top = '-1000em'; 124 canvas.style.left = '-1000em'; 125 canvas.className = elementClass; 126 (document.body || document.documentElement).appendChild(canvas); 127 128 this.disableLogs = false; 129 this.frameInterval = 10; 130 131 this.width = 360; 132 this.height = 240; 133 134 // use gain node to prevent echo 135 this.useGainNode = true; 136 137 var self = this; 138 139 // _____________________________ 140 // Cross-Browser-Declarations.js 141 142 // WebAudio API representer 143 var AudioContext = window.AudioContext; 144 145 if (typeof AudioContext === 'undefined') { 146 if (typeof webkitAudioContext !== 'undefined') { 147 /*global AudioContext:true */ 148 AudioContext = webkitAudioContext; 149 } 150 151 if (typeof mozAudioContext !== 'undefined') { 152 /*global AudioContext:true */ 153 AudioContext = mozAudioContext; 154 } 155 } 156 157 /*jshint -W079 */ 158 var URL = window.URL; 159 160 if (typeof URL === 'undefined' && typeof webkitURL !== 'undefined') { 161 /*global URL:true */ 162 URL = webkitURL; 163 } 164 165 if (typeof navigator !== 'undefined' && typeof navigator.getUserMedia === 'undefined') { // maybe window.navigator? 166 if (typeof navigator.webkitGetUserMedia !== 'undefined') { 167 navigator.getUserMedia = navigator.webkitGetUserMedia; 168 } 169 170 if (typeof navigator.mozGetUserMedia !== 'undefined') { 171 navigator.getUserMedia = navigator.mozGetUserMedia; 172 } 173 } 174 175 var MediaStream = window.MediaStream; 176 177 if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') { 178 MediaStream = webkitMediaStream; 179 } 180 181 /*global MediaStream:true */ 182 if (typeof MediaStream !== 'undefined') { 183 // override "stop" method for all browsers 184 if (typeof MediaStream.prototype.stop === 'undefined') { 185 MediaStream.prototype.stop = function() {
186 this.getTracks().forEach(function(track) { 187 track.stop(); 188 }); 189 }; 190 } 191 } 192 193 var Storage = {}; 194 195 if (typeof AudioContext !== 'undefined') { 196 Storage.AudioContext = AudioContext; 197 } else if (typeof webkitAudioContext !== 'undefined') { 198 Storage.AudioContext = webkitAudioContext; 199 } 200 201 function setSrcObject(stream, element) { 202 if ('srcObject' in element) { 203 element.srcObject = stream; 204 } else if ('mozSrcObject' in element) { 205 element.mozSrcObject = stream; 206 } else { 207 element.srcObject = stream; 208 } 209 } 210 211 this.startDrawingFrames = function() { 212 drawVideosToCanvas(); 213 }; 214 215 function drawVideosToCanvas() { 216 if (isStopDrawingFrames) { 217 return; 218 } 219 220 var videosLength = videos.length; 221 222 var fullcanvas = false; 223 var remaining = []; 224 videos.forEach(function(video) { 225 if (!video.stream) { 226 video.stream = {}; 227 } 228 229 if (video.stream.fullcanvas) { 230 fullcanvas = video; 231 } else { 232 // todo: video.stream.active or video.stream.live to fix blank frames issues? 233 remaining.push(video); 234 } 235 }); 236 237 if (fullcanvas) { 238 canvas.width = fullcanvas.stream.width; 239 canvas.height = fullcanvas.stream.height; 240 } else if (remaining.length) { 241 canvas.width = videosLength > 1 ? remaining[0].width * 2 : remaining[0].width; 242 243 var height = 1; 244 if (videosLength === 3 || videosLength === 4) { 245 height = 2; 246 } 247 if (videosLength === 5 || videosLength === 6) { 248 height = 3; 249 } 250 if (videosLength === 7 || videosLength === 8) { 251 height = 4; 252 } 253 if (videosLength === 9 || videosLength === 10) { 254 height = 5; 255 } 256 canvas.height = remaining[0].height * height; 257 } else { 258 canvas.width = self.width || 360; 259 canvas.height = self.height || 240; 260 } 261 262 if (fullcanvas && fullcanvas instanceof HTMLVideoElement) { 263 drawImage(fullcanvas); 264 } 265 266 remaining.forEach(function(video, idx) { 267 drawImage(video, idx); 268 }); 269 270 setTimeout(drawVideosToCanvas, self.frameInterval); 271 } 272 273 function drawImage(video, idx) { 274 if (isStopDrawingFrames) { 275 return; 276 } 277 278 var x = 0; 279 var y = 0; 280 var width = video.width; 281 var height = video.height; 282 283 if (idx === 1) { 284 x = video.width; 285 } 286 287 if (idx === 2) { 288 y = video.height; 289 } 290 291 if (idx === 3) { 292 x = video.width; 293 y = video.height; 294 } 295 296 if (idx === 4) { 297 y = video.height * 2; 298 } 299 300 if (idx === 5) { 301 x = video.width; 302 y = video.height * 2; 303 } 304 305 if (idx === 6) { 306 y = video.height * 3; 307 } 308 309 if (idx === 7) { 310 x = video.width; 311 y = video.height * 3; 312 } 313 314 if (typeof video.stream.left !== 'undefined') { 315 x = video.stream.left; 316 } 317 318 if (typeof video.stream.top !== 'undefined') { 319 y = video.stream.top; 320 } 321 322 if (typeof video.stream.width !== 'undefined') { 323 width = video.stream.width; 324 } 325 326 if (typeof video.stream.height !== 'undefined') { 327 height = video.stream.height; 328 } 329 330 context.drawImage(video, x, y, width, height); 331 332 if (typeof video.stream.onRender === 'function') { 333 video.stream.onRender(context, x, y, width, height, idx); 334 } 335 } 336 337 function getMixedStream() { 338 isStopDrawingFrames = false; 339 var mixedVideoStream = getMixedVideoStream(); 340 341 var mixedAudioStream = getMixedAudioStream(); 342 if (mixedAudioStream) { 343 mixedAudioStream.getTracks().filter(function(t) { 344 return t.kind === 'audio'; 345 }
345).forEach(function(track) { 346 mixedVideoStream.addTrack(track); 347 }); 348 } 349 350 var fullcanvas; 351 arrayOfMediaStreams.forEach(function(stream) { 352 if (stream.fullcanvas) { 353 fullcanvas = true; 354 } 355 }); 356 357 // mixedVideoStream.prototype.appendStreams = appendStreams; 358 // mixedVideoStream.prototype.resetVideoStreams = resetVideoStreams; 359 // mixedVideoStream.prototype.clearRecordedData = clearRecordedData; 360 361 return mixedVideoStream; 362 } 363 364 function getMixedVideoStream() { 365 resetVideoStreams(); 366 367 var capturedStream; 368 369 if ('captureStream' in canvas) { 370 capturedStream = canvas.captureStream(); 371 } else if ('mozCaptureStream' in canvas) { 372 capturedStream = canvas.mozCaptureStream(); 373 } else if (!self.disableLogs) { 374 console.error('Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features'); 375 } 376 377 var videoStream = new MediaStream(); 378 379 capturedStream.getTracks().filter(function(t) { 380 return t.kind === 'video'; 381 }
381).forEach(function(track) { 382 videoStream.addTrack(track); 383 }); 384 385 canvas.stream = videoStream; 386 387 return videoStream; 388 } 389 390 function getMixedAudioStream() { 391 // via: @pehrsons 392 if (!Storage.AudioContextConstructor) { 393 Storage.AudioContextConstructor = new Storage.AudioContext(); 394 } 395 396 self.audioContext = Storage.AudioContextConstructor; 397 398 self.audioSources = []; 399 400 if (self.useGainNode === true) { 401 self.gainNode = self.audioContext.createGain(); 402 self.gainNode.connect(self.audioContext.destination); 403 self.gainNode.gain.value = 0; // don't hear self 404 } 405 406 var audioTracksLength = 0; 407 arrayOfMediaStreams.forEach(function(stream) { 408 if (!stream.getTracks().filter(function(t) { 409 return t.kind === 'audio'; 410 }).length) { 411 return; 412 } 413 414 audioTracksLength++; 415 416 var audioSource = self.audioContext.createMediaStreamSource(stream); 417 418 if (self.useGainNode === true) { 419 audioSource.connect(self.gainNode); 420 } 421 422 self.audioSources.push(audioSource); 423 }); 424 425 if (!audioTracksLength) { 426 // because "self.audioContext" is not initialized 427 // that's why we've to ignore rest of the code 428 return; 429 } 430 431 self.audioDestination = self.audioContext.createMediaStreamDestination(); 432 self.audioSources.forEach(function(audioSource) { 433 audioSource.connect(self.audioDestination); 434 }); 435 return self.audioDestination.stream; 436 } 437 438 function getVideo(stream) { 439 var video = document.createElement('video'); 440 441 setSrcObject(stream, video); 442 443 video.className = elementClass; 444 445 video.muted = true; 446 video.volume = 0; 447 448 video.width = stream.width || self.width || 360; 449 video.height = stream.height || self.height || 240; 450 451 video.play(); 452 453 return video; 454 } 455 456 this.appendStreams = function(streams) { 457 if (!streams) { 458 throw 'First parameter is required.'; 459 } 460 461 if (!(streams instanceof Array)) { 462 streams = [streams]; 463 } 464 465 streams.forEach(function(stream) { 466 var newStream = new MediaStream(); 467 468 if (stream.getTracks().filter(function(t) { 469 return t.kind === 'video'; 470 }).length) { 471 var video = getVideo(stream); 472 video.stream = stream; 473 videos.push(video); 474 475 newStream.addTrack(stream.getTracks().filter(function(t) { 476 return t.kind === 'video'; 477 })[0]); 478 } 479 480 if (stream.getTracks().filter(function(t) { 481 return t.kind === 'audio'; 482 }).length) { 483 var audioSource = self.audioContext.createMediaStreamSource(stream); 484 self.audioDestination = self.audioContext.createMediaStreamDestination(); 485 audioSource.connect(self.audioDestination); 486 487 newStream.addTrack(self.audioDestination.stream.getTracks().filter(function(t) { 488 return t.kind === 'audio'; 489 })[0]); 490 } 491 492 arrayOfMediaStreams.push(newStream); 493 }); 494 }; 495 496 this.releaseStreams = function() { 497 videos = []; 498 isStopDrawingFrames = true; 499 500 if (self.gainNode) { 501 self.gainNode.disconnect(); 502 self.gainNode = null; 503 } 504 505 if (self.audioSources.length) { 506 self.audioSources.forEach(function(source) { 507 source.disconnect(); 508 }); 509 self.audioSources = []; 510 } 511 512 if (self.audioDestination) { 513 self.audioDestination.disconnect(); 514 self.audioDestination = null; 515 } 516 517 if (self.audioContext) { 518 self.audioContext.close(); 519 } 520 521 self.audioContext = null; 522 523 context.clearRect(0, 0, canvas.width, canvas.height); 524 525 if (canvas.stream) { 526 canvas.stream.stop(); 527 canvas.stream = null; 528 } 529 }; 530 531 this.resetVideoStreams = function(streams) {
532 if (streams && !(streams instanceof Array)) { 533 streams = [streams]; 534 } 535 536 resetVideoStreams(streams); 537 }; 538 539 function resetVideoStreams(streams) { 540 videos = []; 541 streams = streams || arrayOfMediaStreams; 542 543 // via: @adrian-ber 544 streams.forEach(function(stream) { 545 if (!stream.getTracks().filter(function(t) { 546 return t.kind === 'video'; 547 }).length) { 548 return; 549 } 550 551 var video = getVideo(stream); 552 video.stream = stream; 553 videos.push(video); 554 }); 555 } 556 557 // for debugging 558 this.name = 'MultiStreamsMixer'; 559 this.toString = function() { 560 return this.name; 561 }; 562 563 this.getMixedStream = getMixedStream; 564 565} 566 567if (typeof RecordRTC === 'undefined') { 568 if (typeof module !== 'undefined' /* && !!module.exports*/ ) { 569 module.exports = MultiStreamsMixer; 570 } 571 572 if (typeof define === 'function' && define.amd) { 573 define('MultiStreamsMixer', [], function() { 574 return MultiStreamsMixer; 575 }); 576 } 577}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.