PageSourceSearch

https://www.webrtc-experiment.com/MultiStreamsMixer.js

js webrtc-experiment.com collected 2026-09-24 09:56:14 UTC 16,212 bytes, 577 lines download raw bytes

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.