Initial OpenECOMP policy/engine commit
[policy/engine.git] / ecomp-sdk-app / src / main / webapp / app / fusion / scripts / webrtc / RTCMultiConnection.js
1 // Last time updated at Monday, December 21st, 2015, 5:25:26 PM 
2
3 // Quick-Demo for newbies: http://jsfiddle.net/c46de0L8/
4 // Another simple demo: http://jsfiddle.net/zar6fg60/
5
6 // Latest file can be found here: https://cdn.webrtc-experiment.com/RTCMultiConnection.js
7
8 // Muaz Khan     - www.MuazKhan.com
9 // MIT License   - www.WebRTC-Experiment.com/licence
10 // Documentation - www.RTCMultiConnection.org/docs
11 // FAQ           - www.RTCMultiConnection.org/FAQ
12 // Changes log   - www.RTCMultiConnection.org/changes-log/
13 // Demos         - www.WebRTC-Experiment.com/RTCMultiConnection
14
15 // _________________________
16 // RTCMultiConnection-v2.2.2
17
18 (function() {
19
20     // RMC == RTCMultiConnection
21     // usually page-URL is used as channel-id
22     // you can always override it!
23     // www.RTCMultiConnection.org/docs/channel-id/
24     window.RMCDefaultChannel = location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g, '').split('\n').join('').split('\r').join('');
25
26     // www.RTCMultiConnection.org/docs/constructor/
27     window.RTCMultiConnection = function(channel) {
28         // an instance of constructor
29         var connection = this;
30
31         // a reference to RTCMultiSession
32         var rtcMultiSession;
33
34         // setting default channel or channel passed through constructor
35         connection.channel = channel || RMCDefaultChannel;
36
37         // to allow single user to join multiple rooms;
38         // you can change this property at runtime!
39         connection.isAcceptNewSession = true;
40
41         // www.RTCMultiConnection.org/docs/open/
42         connection.open = function(args) {
43             connection.isAcceptNewSession = false;
44
45             // www.RTCMultiConnection.org/docs/session-initiator/
46             // you can always use this property to determine room owner!
47             connection.isInitiator = true;
48
49             var dontTransmit = false;
50
51             // a channel can contain multiple rooms i.e. sessions
52             if (args) {
53                 if (isString(args)) {
54                     connection.sessionid = args;
55                 } else {
56                     if (!isNull(args.transmitRoomOnce)) {
57                         connection.transmitRoomOnce = args.transmitRoomOnce;
58                     }
59
60                     if (!isNull(args.dontTransmit)) {
61                         dontTransmit = args.dontTransmit;
62                     }
63
64                     if (!isNull(args.sessionid)) {
65                         connection.sessionid = args.sessionid;
66                     }
67                 }
68             }
69
70             // if firebase && if session initiator
71             if (connection.socket && connection.socket.remove) {
72                 connection.socket.remove();
73             }
74
75             if (!connection.sessionid) connection.sessionid = connection.channel;
76             connection.sessionDescription = {
77                 sessionid: connection.sessionid,
78                 userid: connection.userid,
79                 session: connection.session,
80                 extra: connection.extra
81             };
82
83             if (!connection.sessionDescriptions[connection.sessionDescription.sessionid]) {
84                 connection.numberOfSessions++;
85                 connection.sessionDescriptions[connection.sessionDescription.sessionid] = connection.sessionDescription;
86             }
87
88             // connect with signaling channel
89             initRTCMultiSession(function() {
90                 // "captureUserMediaOnDemand" is disabled by default.
91                 // invoke "getUserMedia" only when first participant found.
92                 rtcMultiSession.captureUserMediaOnDemand = args ? !!args.captureUserMediaOnDemand : false;
93
94                 if (args && args.onMediaCaptured) {
95                     connection.onMediaCaptured = args.onMediaCaptured;
96                 }
97
98                 // for session-initiator, user-media is captured as soon as "open" is invoked.
99                 if (!rtcMultiSession.captureUserMediaOnDemand) captureUserMedia(function() {
100                     rtcMultiSession.initSession({
101                         sessionDescription: connection.sessionDescription,
102                         dontTransmit: dontTransmit
103                     });
104
105                     invokeMediaCaptured(connection);
106                 });
107
108                 if (rtcMultiSession.captureUserMediaOnDemand) {
109                     rtcMultiSession.initSession({
110                         sessionDescription: connection.sessionDescription,
111                         dontTransmit: dontTransmit
112                     });
113                 }
114             });
115             return connection.sessionDescription;
116         };
117
118         // www.RTCMultiConnection.org/docs/connect/
119         connection.connect = function(sessionid) {
120             // a channel can contain multiple rooms i.e. sessions
121             if (sessionid) {
122                 connection.sessionid = sessionid;
123             }
124
125             // connect with signaling channel
126             initRTCMultiSession(function() {
127                 log('Signaling channel is ready.');
128             });
129
130             return this;
131         };
132
133         // www.RTCMultiConnection.org/docs/join/
134         connection.join = joinSession;
135
136         // www.RTCMultiConnection.org/docs/send/
137         connection.send = function(data, _channel) {
138             if (connection.numberOfConnectedUsers <= 0) {
139                 // no connections
140                 setTimeout(function() {
141                     // try again
142                     connection.send(data, _channel);
143                 }, 1000);
144                 return;
145             }
146
147             // send file/data or /text
148             if (!data)
149                 throw 'No file, data or text message to share.';
150
151             // connection.send([file1, file2, file3])
152             // you can share multiple files, strings or data objects using "send" method!
153             if (data instanceof Array && !isNull(data[0].size) && !isNull(data[0].type)) {
154                 // this mechanism can cause failure for subsequent packets/data 
155                 // on Firefox especially; and on chrome as well!
156                 // todo: need to use setTimeout instead.
157                 for (var i = 0; i < data.length; i++) {
158                     data[i].size && data[i].type && connection.send(data[i], _channel);
159                 }
160                 return;
161             }
162
163             // File or Blob object MUST have "type" and "size" properties
164             if (!isNull(data.size) && !isNull(data.type)) {
165                 if (!connection.enableFileSharing) {
166                     throw '"enableFileSharing" boolean MUST be "true" to support file sharing.';
167                 }
168
169                 if (!rtcMultiSession.fileBufferReader) {
170                     initFileBufferReader(connection, function(fbr) {
171                         rtcMultiSession.fileBufferReader = fbr;
172                         connection.send(data, _channel);
173                     });
174                     return;
175                 }
176
177                 var extra = merge({
178                     userid: connection.userid
179                 }, data.extra || connection.extra);
180
181                 rtcMultiSession.fileBufferReader.readAsArrayBuffer(data, function(uuid) {
182                     rtcMultiSession.fileBufferReader.getNextChunk(uuid, function(nextChunk, isLastChunk, extra) {
183                         if (_channel) _channel.send(nextChunk);
184                         else rtcMultiSession.send(nextChunk);
185                     });
186                 }, extra);
187             } else {
188                 // to allow longest string messages
189                 // and largest data objects
190                 // or anything of any size!
191                 // to send multiple data objects concurrently!
192
193                 TextSender.send({
194                     text: data,
195                     channel: rtcMultiSession,
196                     _channel: _channel,
197                     connection: connection
198                 });
199             }
200         };
201
202         function initRTCMultiSession(onSignalingReady) {
203             if (screenFrame) {
204                 loadScreenFrame();
205             }
206
207             // RTCMultiSession is the backbone object;
208             // this object MUST be initialized once!
209             if (rtcMultiSession) return onSignalingReady();
210
211             // your everything is passed over RTCMultiSession constructor!
212             rtcMultiSession = new RTCMultiSession(connection, onSignalingReady);
213         }
214
215         connection.disconnect = function() {
216             if (rtcMultiSession) rtcMultiSession.disconnect();
217             rtcMultiSession = null;
218         };
219
220         function joinSession(session, joinAs) {
221             if (isString(session)) {
222                 connection.skipOnNewSession = true;
223             }
224
225             if (!rtcMultiSession) {
226                 log('Signaling channel is not ready. Connecting...');
227                 // connect with signaling channel
228                 initRTCMultiSession(function() {
229                     log('Signaling channel is connected. Joining the session again...');
230                     setTimeout(function() {
231                         joinSession(session, joinAs);
232                     }, 1000);
233                 });
234                 return;
235             }
236
237             // connection.join('sessionid');
238             if (isString(session)) {
239                 if (connection.sessionDescriptions[session]) {
240                     session = connection.sessionDescriptions[session];
241                 } else
242                     return setTimeout(function() {
243                         log('Session-Descriptions not found. Rechecking..');
244                         joinSession(session, joinAs);
245                     }, 1000);
246             }
247
248             // connection.join('sessionid', { audio: true });
249             if (joinAs) {
250                 return captureUserMedia(function() {
251                     session.oneway = true;
252                     joinSession(session);
253                 }, joinAs);
254             }
255
256             if (!session || !session.userid || !session.sessionid) {
257                 error('missing arguments', arguments);
258
259                 var error = 'Invalid data passed over "connection.join" method.';
260                 connection.onstatechange({
261                     userid: 'browser',
262                     extra: {},
263                     name: 'Unexpected data detected.',
264                     reason: error
265                 });
266
267                 throw error;
268             }
269
270             if (!connection.dontOverrideSession) {
271                 connection.session = session.session;
272             }
273
274             var extra = connection.extra || session.extra || {};
275
276             // todo: need to verify that if-block statement works as expected.
277             // expectations: if it is oneway streaming; or if it is data-only connection
278             // then, it shouldn't capture user-media on participant's side.
279             if (session.oneway || isData(session)) {
280                 rtcMultiSession.joinSession(session, extra);
281             } else {
282                 captureUserMedia(function() {
283                     rtcMultiSession.joinSession(session, extra);
284                 });
285             }
286         }
287
288         var isFirstSession = true;
289
290         // www.RTCMultiConnection.org/docs/captureUserMedia/
291
292         function captureUserMedia(callback, _session, dontCheckChromExtension) {
293             // capture user's media resources
294             var session = _session || connection.session;
295
296             if (isEmpty(session)) {
297                 if (callback) callback();
298                 return;
299             }
300
301             // you can force to skip media capturing!
302             if (connection.dontCaptureUserMedia) {
303                 return callback();
304             }
305
306             // if it is data-only connection
307             // if it is one-way connection and current user is participant
308             if (isData(session) || (!connection.isInitiator && session.oneway)) {
309                 // www.RTCMultiConnection.org/docs/attachStreams/
310                 connection.attachStreams = [];
311                 return callback();
312             }
313
314             var constraints = {
315                 audio: !!session.audio ? {
316                     mandatory: {},
317                     optional: [{
318                         chromeRenderToAssociatedSink: true
319                     }]
320                 } : false,
321                 video: !!session.video
322             };
323
324             // if custom audio device is selected
325             if (connection._mediaSources.audio) {
326                 constraints.audio.optional.push({
327                     sourceId: connection._mediaSources.audio
328                 });
329             }
330
331             // if custom video device is selected
332             if (connection._mediaSources.video) {
333                 constraints.video = {
334                     optional: [{
335                         sourceId: connection._mediaSources.video
336                     }]
337                 };
338             }
339
340             // for connection.session = {};
341             if (!session.screen && !constraints.audio && !constraints.video) {
342                 return callback();
343             }
344
345             var screen_constraints = {
346                 audio: false,
347                 video: {
348                     mandatory: {
349                         chromeMediaSource: DetectRTC.screen.chromeMediaSource,
350                         maxWidth: screen.width > 1920 ? screen.width : 1920,
351                         maxHeight: screen.height > 1080 ? screen.height : 1080
352                     },
353                     optional: []
354                 }
355             };
356
357             if (isFirefox && session.screen) {
358                 if (location.protocol !== 'https:') {
359                     return error(SCREEN_COMMON_FAILURE);
360                 }
361                 warn(Firefox_Screen_Capturing_Warning);
362
363                 screen_constraints.video = merge(screen_constraints.video.mandatory, {
364                     mozMediaSource: 'window', // mozMediaSource is redundant here
365                     mediaSource: 'window' // 'screen' || 'window'
366                 });
367
368                 // Firefox is supporting audio+screen from single getUserMedia request
369                 // audio+video+screen will become audio+screen for Firefox
370                 // because Firefox isn't supporting multi-streams feature version < 38
371                 // version >38 supports multi-stream sharing.
372                 // we can use:  firefoxVersion < 38
373                 // however capturing audio and screen using single getUserMedia is a better option
374                 if (constraints.audio /* && !session.video */ ) {
375                     screen_constraints.audio = true;
376                     constraints = {};
377                 }
378
379                 delete screen_constraints.video.chromeMediaSource;
380             }
381
382             // if screen is prompted
383             if (session.screen) {
384                 if (isChrome && DetectRTC.screen.extensionid != ReservedExtensionID) {
385                     useCustomChromeExtensionForScreenCapturing = true;
386                 }
387
388                 if (isChrome && !useCustomChromeExtensionForScreenCapturing && !dontCheckChromExtension && !DetectRTC.screen.sourceId) {
389                     listenEventHandler('message', onIFrameCallback);
390
391                     function onIFrameCallback(event) {
392                         if (event.data && event.data.chromeMediaSourceId) {
393                             // this event listener is no more needed
394                             window.removeEventListener('message', onIFrameCallback);
395
396                             var sourceId = event.data.chromeMediaSourceId;
397
398                             DetectRTC.screen.sourceId = sourceId;
399                             DetectRTC.screen.chromeMediaSource = 'desktop';
400
401                             if (sourceId == 'PermissionDeniedError') {
402                                 var mediaStreamError = {
403                                     message: location.protocol == 'https:' ? 'User denied to share content of his screen.' : SCREEN_COMMON_FAILURE,
404                                     name: 'PermissionDeniedError',
405                                     constraintName: screen_constraints,
406                                     session: session
407                                 };
408                                 currentUserMediaRequest.mutex = false;
409                                 DetectRTC.screen.sourceId = null;
410                                 return connection.onMediaError(mediaStreamError);
411                             }
412
413                             captureUserMedia(callback, _session);
414                         }
415
416                         if (event.data && event.data.chromeExtensionStatus) {
417                             warn('Screen capturing extension status is:', event.data.chromeExtensionStatus);
418                             DetectRTC.screen.chromeMediaSource = 'screen';
419                             captureUserMedia(callback, _session, true);
420                         }
421                     }
422
423                     if (!screenFrame) {
424                         loadScreenFrame();
425                     }
426
427                     screenFrame.postMessage();
428                     return;
429                 }
430
431                 // check if screen capturing extension is installed.
432                 if (isChrome && useCustomChromeExtensionForScreenCapturing && !dontCheckChromExtension && DetectRTC.screen.chromeMediaSource == 'screen' && DetectRTC.screen.extensionid) {
433                     if (DetectRTC.screen.extensionid == ReservedExtensionID && document.domain.indexOf('webrtc-experiment.com') == -1) {
434                         return captureUserMedia(callback, _session, true);
435                     }
436
437                     log('checking if chrome extension is installed.');
438                     DetectRTC.screen.getChromeExtensionStatus(function(status) {
439                         if (status == 'installed-enabled') {
440                             DetectRTC.screen.chromeMediaSource = 'desktop';
441                         }
442
443                         captureUserMedia(callback, _session, true);
444                         log('chrome extension is installed?', DetectRTC.screen.chromeMediaSource == 'desktop');
445                     });
446                     return;
447                 }
448
449                 if (isChrome && useCustomChromeExtensionForScreenCapturing && DetectRTC.screen.chromeMediaSource == 'desktop' && !DetectRTC.screen.sourceId) {
450                     DetectRTC.screen.getSourceId(function(sourceId) {
451                         if (sourceId == 'PermissionDeniedError') {
452                             var mediaStreamError = {
453                                 message: 'User denied to share content of his screen.',
454                                 name: 'PermissionDeniedError',
455                                 constraintName: screen_constraints,
456                                 session: session
457                             };
458                             currentUserMediaRequest.mutex = false;
459                             DetectRTC.screen.chromeMediaSource = 'desktop';
460                             return connection.onMediaError(mediaStreamError);
461                         }
462
463                         if (sourceId == 'No-Response') {
464                             error('Chrome extension seems not available. Make sure that manifest.json#L16 has valid content-script matches pointing to your URL.');
465                             DetectRTC.screen.chromeMediaSource = 'screen';
466                             return captureUserMedia(callback, _session, true);
467                         }
468
469                         captureUserMedia(callback, _session, true);
470                     });
471                     return;
472                 }
473
474                 if (isChrome && DetectRTC.screen.chromeMediaSource == 'desktop') {
475                     screen_constraints.video.mandatory.chromeMediaSourceId = DetectRTC.screen.sourceId;
476                 }
477
478                 var _isFirstSession = isFirstSession;
479
480                 _captureUserMedia(screen_constraints, constraints.audio || constraints.video ? function() {
481
482                     if (_isFirstSession) isFirstSession = true;
483
484                     _captureUserMedia(constraints, callback);
485                 } : callback);
486             } else _captureUserMedia(constraints, callback, session.audio && !session.video);
487
488             function _captureUserMedia(forcedConstraints, forcedCallback, isRemoveVideoTracks, dontPreventSSLAutoAllowed) {
489                 connection.onstatechange({
490                     userid: 'browser',
491                     extra: {},
492                     name: 'fetching-usermedia',
493                     reason: 'About to capture user-media with constraints: ' + toStr(forcedConstraints)
494                 });
495
496
497                 if (connection.preventSSLAutoAllowed && !dontPreventSSLAutoAllowed && isChrome) {
498                     // if navigator.customGetUserMediaBar.js is missing
499                     if (!navigator.customGetUserMediaBar) {
500                         loadScript(connection.resources.customGetUserMediaBar, function() {
501                             _captureUserMedia(forcedConstraints, forcedCallback, isRemoveVideoTracks, dontPreventSSLAutoAllowed);
502                         });
503                         return;
504                     }
505
506                     navigator.customGetUserMediaBar(forcedConstraints, function() {
507                         _captureUserMedia(forcedConstraints, forcedCallback, isRemoveVideoTracks, true);
508                     }, function() {
509                         connection.onMediaError({
510                             name: 'PermissionDeniedError',
511                             message: 'User denied permission.',
512                             constraintName: forcedConstraints,
513                             session: session
514                         });
515                     });
516                     return;
517                 }
518
519                 var mediaConfig = {
520                     onsuccess: function(stream, returnBack, idInstance, streamid) {
521                         onStreamSuccessCallback(stream, returnBack, idInstance, streamid, forcedConstraints, forcedCallback, isRemoveVideoTracks, screen_constraints, constraints, session);
522                     },
523                     onerror: function(e, constraintUsed) {
524                         // http://goo.gl/hrwF1a
525                         if (isFirefox) {
526                             if (e == 'PERMISSION_DENIED') {
527                                 e = {
528                                     message: '',
529                                     name: 'PermissionDeniedError',
530                                     constraintName: constraintUsed,
531                                     session: session
532                                 };
533                             }
534                         }
535
536                         if (isFirefox && constraintUsed.video && constraintUsed.video.mozMediaSource) {
537                             mediaStreamError = {
538                                 message: Firefox_Screen_Capturing_Warning,
539                                 name: e.name || 'PermissionDeniedError',
540                                 constraintName: constraintUsed,
541                                 session: session
542                             };
543
544                             connection.onMediaError(mediaStreamError);
545                             return;
546                         }
547
548                         if (isString(e)) {
549                             return connection.onMediaError({
550                                 message: 'Unknown Error',
551                                 name: e,
552                                 constraintName: constraintUsed,
553                                 session: session
554                             });
555                         }
556
557                         // it seems that chrome 35+ throws "DevicesNotFoundError" exception 
558                         // when any of the requested media is either denied or absent
559                         if (e.name && (e.name == 'PermissionDeniedError' || e.name == 'DevicesNotFoundError')) {
560                             var mediaStreamError = 'Either: ';
561                             mediaStreamError += '\n Media resolutions are not permitted.';
562                             mediaStreamError += '\n Another application is using same media device.';
563                             mediaStreamError += '\n Media device is not attached or drivers not installed.';
564                             mediaStreamError += '\n You denied access once and it is still denied.';
565
566                             if (e.message && e.message.length) {
567                                 mediaStreamError += '\n ' + e.message;
568                             }
569
570                             mediaStreamError = {
571                                 message: mediaStreamError,
572                                 name: e.name,
573                                 constraintName: constraintUsed,
574                                 session: session
575                             };
576
577                             connection.onMediaError(mediaStreamError);
578
579                             if (isChrome && (session.audio || session.video)) {
580                                 // todo: this snippet fails if user has two or more 
581                                 // microphone/webcam attached.
582                                 DetectRTC.load(function() {
583                                     // it is possible to check presence of the microphone before using it!
584                                     if (session.audio && !DetectRTC.hasMicrophone) {
585                                         warn('It seems that you have no microphone attached to your device/system.');
586                                         session.audio = session.audio = false;
587
588                                         if (!session.video) {
589                                             alert('It seems that you are capturing microphone and there is no device available or access is denied. Reloading...');
590                                             location.reload();
591                                         }
592                                     }
593
594                                     // it is possible to check presence of the webcam before using it!
595                                     if (session.video && !DetectRTC.hasWebcam) {
596                                         warn('It seems that you have no webcam attached to your device/system.');
597                                         session.video = session.video = false;
598
599                                         if (!session.audio) {
600                                             alert('It seems that you are capturing webcam and there is no device available or access is denied. Reloading...');
601                                             location.reload();
602                                         }
603                                     }
604
605                                     if (!DetectRTC.hasMicrophone && !DetectRTC.hasWebcam) {
606                                         alert('It seems that either both microphone/webcam are not available or access is denied. Reloading...');
607                                         location.reload();
608                                     } else if (!connection.getUserMediaPromptedOnce) {
609                                         // make maximum two tries!
610                                         connection.getUserMediaPromptedOnce = true;
611                                         captureUserMedia(callback, session);
612                                     }
613                                 });
614                             }
615                         }
616
617                         if (e.name && e.name == 'ConstraintNotSatisfiedError') {
618                             var mediaStreamError = 'Either: ';
619                             mediaStreamError += '\n You are prompting unknown media resolutions.';
620                             mediaStreamError += '\n You are using invalid media constraints.';
621
622                             if (e.message && e.message.length) {
623                                 mediaStreamError += '\n ' + e.message;
624                             }
625
626                             mediaStreamError = {
627                                 message: mediaStreamError,
628                                 name: e.name,
629                                 constraintName: constraintUsed,
630                                 session: session
631                             };
632
633                             connection.onMediaError(mediaStreamError);
634                         }
635
636                         if (session.screen) {
637                             if (isFirefox) {
638                                 error(Firefox_Screen_Capturing_Warning);
639                             } else if (location.protocol !== 'https:') {
640                                 if (!isNodeWebkit && (location.protocol == 'file:' || location.protocol == 'http:')) {
641                                     error('You cannot use HTTP or file protocol for screen capturing. You must either use HTTPs or chrome extension page or Node-Webkit page.');
642                                 }
643                             } else {
644                                 error('Unable to detect actual issue. Maybe "deprecated" screen capturing flag was not set using command line or maybe you clicked "No" button or maybe chrome extension returned invalid "sourceId". Please install chrome-extension: http://bit.ly/webrtc-screen-extension');
645                             }
646                         }
647
648                         currentUserMediaRequest.mutex = false;
649
650                         // to make sure same stream can be captured again!
651                         var idInstance = JSON.stringify(constraintUsed);
652                         if (currentUserMediaRequest.streams[idInstance]) {
653                             delete currentUserMediaRequest.streams[idInstance];
654                         }
655                     },
656                     mediaConstraints: connection.mediaConstraints || {}
657                 };
658
659                 mediaConfig.constraints = forcedConstraints || constraints;
660                 mediaConfig.connection = connection;
661                 getUserMedia(mediaConfig);
662             }
663         }
664
665         function onStreamSuccessCallback(stream, returnBack, idInstance, streamid, forcedConstraints, forcedCallback, isRemoveVideoTracks, screen_constraints, constraints, session) {
666             if (!streamid) streamid = getRandomString();
667
668             connection.onstatechange({
669                 userid: 'browser',
670                 extra: {},
671                 name: 'usermedia-fetched',
672                 reason: 'Captured user media using constraints: ' + toStr(forcedConstraints)
673             });
674
675             if (isRemoveVideoTracks) {
676                 stream = convertToAudioStream(stream);
677             }
678
679             connection.localStreamids.push(streamid);
680             stream.onended = function() {
681                 if (streamedObject.mediaElement && !streamedObject.mediaElement.parentNode && document.getElementById(stream.streamid)) {
682                     streamedObject.mediaElement = document.getElementById(stream.streamid);
683                 }
684
685                 // when a stream is stopped; it must be removed from "attachStreams" array
686                 connection.attachStreams.forEach(function(_stream, index) {
687                     if (_stream == stream) {
688                         delete connection.attachStreams[index];
689                         connection.attachStreams = swap(connection.attachStreams);
690                     }
691                 });
692
693                 onStreamEndedHandler(streamedObject, connection);
694
695                 if (connection.streams[streamid]) {
696                     connection.removeStream(streamid);
697                 }
698
699                 // if user clicks "stop" button to close screen sharing
700                 var _stream = connection.streams[streamid];
701                 if (_stream && _stream.sockets.length) {
702                     _stream.sockets.forEach(function(socket) {
703                         socket.send({
704                             streamid: _stream.streamid,
705                             stopped: true
706                         });
707                     });
708                 }
709
710                 currentUserMediaRequest.mutex = false;
711                 // to make sure same stream can be captured again!
712                 if (currentUserMediaRequest.streams[idInstance]) {
713                     delete currentUserMediaRequest.streams[idInstance];
714                 }
715
716                 // to allow re-capturing of the screen
717                 DetectRTC.screen.sourceId = null;
718             };
719
720             if (!isIE) {
721                 stream.streamid = streamid;
722                 stream.isScreen = forcedConstraints == screen_constraints;
723                 stream.isVideo = forcedConstraints == constraints && !!constraints.video;
724                 stream.isAudio = forcedConstraints == constraints && !!constraints.audio && !constraints.video;
725
726                 // if muted stream is negotiated
727                 stream.preMuted = {
728                     audio: stream.getAudioTracks().length && !stream.getAudioTracks()[0].enabled,
729                     video: stream.getVideoTracks().length && !stream.getVideoTracks()[0].enabled
730                 };
731             }
732
733             var mediaElement = createMediaElement(stream, session);
734             mediaElement.muted = true;
735
736             var streamedObject = {
737                 stream: stream,
738                 streamid: streamid,
739                 mediaElement: mediaElement,
740                 blobURL: mediaElement.mozSrcObject ? URL.createObjectURL(stream) : mediaElement.src,
741                 type: 'local',
742                 userid: connection.userid,
743                 extra: connection.extra,
744                 session: session,
745                 isVideo: !!stream.isVideo,
746                 isAudio: !!stream.isAudio,
747                 isScreen: !!stream.isScreen,
748                 isInitiator: !!connection.isInitiator,
749                 rtcMultiConnection: connection
750             };
751
752             if (isFirstSession) {
753                 connection.attachStreams.push(stream);
754             }
755             isFirstSession = false;
756
757             connection.streams[streamid] = connection._getStream(streamedObject);
758
759             if (!returnBack) {
760                 connection.onstream(streamedObject);
761             }
762
763             if (connection.setDefaultEventsForMediaElement) {
764                 connection.setDefaultEventsForMediaElement(mediaElement, streamid);
765             }
766
767             if (forcedCallback) forcedCallback(stream, streamedObject);
768
769             if (connection.onspeaking) {
770                 initHark({
771                     stream: stream,
772                     streamedObject: streamedObject,
773                     connection: connection
774                 });
775             }
776         }
777
778         // www.RTCMultiConnection.org/docs/captureUserMedia/
779         connection.captureUserMedia = captureUserMedia;
780
781         // www.RTCMultiConnection.org/docs/leave/
782         connection.leave = function(userid) {
783             if (!rtcMultiSession) return;
784
785             isFirstSession = true;
786
787             if (userid) {
788                 connection.eject(userid);
789                 return;
790             }
791
792             rtcMultiSession.leave();
793         };
794
795         // www.RTCMultiConnection.org/docs/eject/
796         connection.eject = function(userid) {
797             if (!connection.isInitiator) throw 'Only session-initiator can eject a user.';
798             if (!connection.peers[userid]) throw 'You ejected invalid user.';
799             connection.peers[userid].sendCustomMessage({
800                 ejected: true
801             });
802         };
803
804         // www.RTCMultiConnection.org/docs/close/
805         connection.close = function() {
806             // close entire session
807             connection.autoCloseEntireSession = true;
808             connection.leave();
809         };
810
811         // www.RTCMultiConnection.org/docs/renegotiate/
812         connection.renegotiate = function(stream, session) {
813             if (connection.numberOfConnectedUsers <= 0) {
814                 // no connections
815                 setTimeout(function() {
816                     // try again
817                     connection.renegotiate(stream, session);
818                 }, 1000);
819                 return;
820             }
821
822             rtcMultiSession.addStream({
823                 renegotiate: session || merge({
824                     oneway: true
825                 }, connection.session),
826                 stream: stream
827             });
828         };
829
830         connection.attachExternalStream = function(stream, isScreen) {
831             var constraints = {};
832             if (stream.getAudioTracks && stream.getAudioTracks().length) {
833                 constraints.audio = true;
834             }
835             if (stream.getVideoTracks && stream.getVideoTracks().length) {
836                 constraints.video = true;
837             }
838
839             var screen_constraints = {
840                 video: {
841                     chromeMediaSource: 'fake'
842                 }
843             };
844             var forcedConstraints = isScreen ? screen_constraints : constraints;
845             onStreamSuccessCallback(stream, false, '', null, forcedConstraints, false, false, screen_constraints, constraints, constraints);
846         };
847
848         // www.RTCMultiConnection.org/docs/addStream/
849         connection.addStream = function(session, socket) {
850             // www.RTCMultiConnection.org/docs/renegotiation/
851
852             if (connection.numberOfConnectedUsers <= 0) {
853                 // no connections
854                 setTimeout(function() {
855                     // try again
856                     connection.addStream(session, socket);
857                 }, 1000);
858                 return;
859             }
860
861             // renegotiate new media stream
862             if (session) {
863                 var isOneWayStreamFromParticipant;
864                 if (!connection.isInitiator && session.oneway) {
865                     session.oneway = false;
866                     isOneWayStreamFromParticipant = true;
867                 }
868
869                 captureUserMedia(function(stream) {
870                     if (isOneWayStreamFromParticipant) {
871                         session.oneway = true;
872                     }
873                     addStream(stream);
874                 }, session);
875             } else addStream();
876
877             function addStream(stream) {
878                 rtcMultiSession.addStream({
879                     stream: stream,
880                     renegotiate: session || connection.session,
881                     socket: socket
882                 });
883             }
884         };
885
886         // www.RTCMultiConnection.org/docs/removeStream/
887         connection.removeStream = function(streamid, dontRenegotiate) {
888             if (connection.numberOfConnectedUsers <= 0) {
889                 // no connections
890                 setTimeout(function() {
891                     // try again
892                     connection.removeStream(streamid, dontRenegotiate);
893                 }, 1000);
894                 return;
895             }
896
897             if (!streamid) streamid = 'all';
898             if (!isString(streamid) || streamid.search(/all|audio|video|screen/gi) != -1) {
899                 function _detachStream(_stream, config) {
900                     if (config.local && _stream.type != 'local') return;
901                     if (config.remote && _stream.type != 'remote') return;
902
903                     // connection.removeStream({screen:true});
904                     if (config.screen && !!_stream.isScreen) {
905                         connection.detachStreams.push(_stream.streamid);
906                     }
907
908                     // connection.removeStream({audio:true});
909                     if (config.audio && !!_stream.isAudio) {
910                         connection.detachStreams.push(_stream.streamid);
911                     }
912
913                     // connection.removeStream({video:true});
914                     if (config.video && !!_stream.isVideo) {
915                         connection.detachStreams.push(_stream.streamid);
916                     }
917
918                     // connection.removeStream({});
919                     if (!config.audio && !config.video && !config.screen) {
920                         connection.detachStreams.push(_stream.streamid);
921                     }
922
923                     if (connection.detachStreams.indexOf(_stream.streamid) != -1) {
924                         log('removing stream', _stream.streamid);
925                         onStreamEndedHandler(_stream, connection);
926
927                         if (config.stop) {
928                             connection.stopMediaStream(_stream.stream);
929                         }
930                     }
931                 }
932
933                 for (var stream in connection.streams) {
934                     if (connection._skip.indexOf(stream) == -1) {
935                         _stream = connection.streams[stream];
936
937                         if (streamid == 'all') _detachStream(_stream, {
938                             audio: true,
939                             video: true,
940                             screen: true
941                         });
942
943                         else if (isString(streamid)) {
944                             // connection.removeStream('screen');
945                             var config = {};
946                             config[streamid] = true;
947                             _detachStream(_stream, config);
948                         } else _detachStream(_stream, streamid);
949                     }
950                 }
951
952                 if (!dontRenegotiate && connection.detachStreams.length) {
953                     connection.renegotiate();
954                 }
955
956                 return;
957             }
958
959             var stream = connection.streams[streamid];
960
961             // detach pre-attached streams
962             if (!stream) return warn('No such stream exists. Stream-id:', streamid);
963
964             // www.RTCMultiConnection.org/docs/detachStreams/
965             connection.detachStreams.push(stream.streamid);
966
967             log('removing stream', stream.streamid);
968             onStreamEndedHandler(stream, connection);
969
970             // todo: how to allow "stop" function?
971             // connection.stopMediaStream(stream.stream)
972
973             if (!dontRenegotiate) {
974                 connection.renegotiate();
975             }
976         };
977
978         connection.switchStream = function(session) {
979             if (connection.numberOfConnectedUsers <= 0) {
980                 // no connections
981                 setTimeout(function() {
982                     // try again
983                     connection.switchStream(session);
984                 }, 1000);
985                 return;
986             }
987
988             connection.removeStream('all', true);
989             connection.addStream(session);
990         };
991
992         // www.RTCMultiConnection.org/docs/sendCustomMessage/
993         connection.sendCustomMessage = function(message) {
994             if (!rtcMultiSession || !rtcMultiSession.defaultSocket) {
995                 return setTimeout(function() {
996                     connection.sendCustomMessage(message);
997                 }, 1000);
998             }
999
1000             rtcMultiSession.defaultSocket.send({
1001                 customMessage: true,
1002                 message: message
1003             });
1004         };
1005
1006         // set RTCMultiConnection defaults on constructor invocation
1007         setDefaults(connection);
1008     };
1009
1010     function RTCMultiSession(connection, callbackForSignalingReady) {
1011         var socketObjects = {};
1012         var sockets = [];
1013         var rtcMultiSession = this;
1014         var participants = {};
1015
1016         if (!rtcMultiSession.fileBufferReader && connection.session.data && connection.enableFileSharing) {
1017             initFileBufferReader(connection, function(fbr) {
1018                 rtcMultiSession.fileBufferReader = fbr;
1019             });
1020         }
1021
1022         var textReceiver = new TextReceiver(connection);
1023
1024         function onDataChannelMessage(e) {
1025             if (e.data.checkingPresence && connection.channels[e.userid]) {
1026                 connection.channels[e.userid].send({
1027                     presenceDetected: true
1028                 });
1029                 return;
1030             }
1031
1032             if (e.data.presenceDetected && connection.peers[e.userid]) {
1033                 connection.peers[e.userid].connected = true;
1034                 return;
1035             }
1036
1037             if (e.data.type === 'text') {
1038                 textReceiver.receive(e.data, e.userid, e.extra);
1039             } else {
1040                 if (connection.autoTranslateText) {
1041                     e.original = e.data;
1042                     connection.Translator.TranslateText(e.data, function(translatedText) {
1043                         e.data = translatedText;
1044                         connection.onmessage(e);
1045                     });
1046                 } else connection.onmessage(e);
1047             }
1048         }
1049
1050         function onNewSession(session) {
1051             if (connection.skipOnNewSession) return;
1052
1053             if (!session.session) session.session = {};
1054             if (!session.extra) session.extra = {};
1055
1056             // todo: make sure this works as expected.
1057             // i.e. "onNewSession" should be fired only for 
1058             // sessionid that is passed over "connect" method.
1059             if (connection.sessionid && session.sessionid != connection.sessionid) return;
1060
1061             if (connection.onNewSession) {
1062                 session.join = function(forceSession) {
1063                     if (!forceSession) return connection.join(session);
1064
1065                     for (var f in forceSession) {
1066                         session.session[f] = forceSession[f];
1067                     }
1068
1069                     // keeping previous state
1070                     var isDontCaptureUserMedia = connection.dontCaptureUserMedia;
1071
1072                     connection.dontCaptureUserMedia = false;
1073                     connection.captureUserMedia(function() {
1074                         connection.dontCaptureUserMedia = true;
1075                         connection.join(session);
1076
1077                         // returning back previous state
1078                         connection.dontCaptureUserMedia = isDontCaptureUserMedia;
1079                     }, forceSession);
1080                 };
1081                 if (!session.extra) session.extra = {};
1082
1083                 return connection.onNewSession(session);
1084             }
1085
1086             connection.join(session);
1087         }
1088
1089         function updateSocketForLocalStreams(socket) {
1090             for (var i = 0; i < connection.localStreamids.length; i++) {
1091                 var streamid = connection.localStreamids[i];
1092                 if (connection.streams[streamid]) {
1093                     // using "sockets" array to keep references of all sockets using 
1094                     // this media stream; so we can fire "onStreamEndedHandler" among all users.
1095                     connection.streams[streamid].sockets.push(socket);
1096                 }
1097             }
1098         }
1099
1100         function newPrivateSocket(_config) {
1101             var socketConfig = {
1102                 channel: _config.channel,
1103                 onmessage: socketResponse,
1104                 onopen: function(_socket) {
1105                     if (_socket) socket = _socket;
1106
1107                     if (isofferer && !peer) {
1108                         peerConfig.session = connection.session;
1109                         if (!peer) peer = new PeerConnection();
1110                         peer.create('offer', peerConfig);
1111                     }
1112
1113                     _config.socketIndex = socket.index = sockets.length;
1114                     socketObjects[socketConfig.channel] = socket;
1115                     sockets[_config.socketIndex] = socket;
1116
1117                     updateSocketForLocalStreams(socket);
1118
1119                     if (!socket.__push) {
1120                         socket.__push = socket.send;
1121                         socket.send = function(message) {
1122                             message.userid = message.userid || connection.userid;
1123                             message.extra = message.extra || connection.extra || {};
1124
1125                             socket.__push(message);
1126                         };
1127                     }
1128                 }
1129             };
1130
1131             socketConfig.callback = function(_socket) {
1132                 socket = _socket;
1133                 socketConfig.onopen();
1134             };
1135
1136             var socket = connection.openSignalingChannel(socketConfig);
1137             if (socket) socketConfig.onopen(socket);
1138
1139             var isofferer = _config.isofferer,
1140                 peer;
1141
1142             var peerConfig = {
1143                 onopen: onChannelOpened,
1144                 onicecandidate: function(candidate) {
1145                     if (!connection.candidates) throw 'ICE candidates are mandatory.';
1146                     if (!connection.iceProtocols) throw 'At least one must be true; UDP or TCP.';
1147
1148                     var iceCandidates = connection.candidates;
1149
1150                     var stun = iceCandidates.stun;
1151                     var turn = iceCandidates.turn;
1152
1153                     if (!isNull(iceCandidates.reflexive)) stun = iceCandidates.reflexive;
1154                     if (!isNull(iceCandidates.relay)) turn = iceCandidates.relay;
1155
1156                     if (!iceCandidates.host && !!candidate.candidate.match(/a=candidate.*typ host/g)) return;
1157                     if (!turn && !!candidate.candidate.match(/a=candidate.*typ relay/g)) return;
1158                     if (!stun && !!candidate.candidate.match(/a=candidate.*typ srflx/g)) return;
1159
1160                     var protocol = connection.iceProtocols;
1161
1162                     if (!protocol.udp && !!candidate.candidate.match(/a=candidate.* udp/g)) return;
1163                     if (!protocol.tcp && !!candidate.candidate.match(/a=candidate.* tcp/g)) return;
1164
1165                     if (!window.selfNPObject) window.selfNPObject = candidate;
1166
1167                     socket && socket.send({
1168                         candidate: JSON.stringify({
1169                             candidate: candidate.candidate,
1170                             sdpMid: candidate.sdpMid,
1171                             sdpMLineIndex: candidate.sdpMLineIndex
1172                         })
1173                     });
1174                 },
1175                 onmessage: function(data) {
1176                     if (!data) return;
1177
1178                     var abToStr = ab2str(data);
1179                     if (abToStr.indexOf('"userid":') != -1) {
1180                         abToStr = JSON.parse(abToStr);
1181                         onDataChannelMessage(abToStr);
1182                     } else if (data instanceof ArrayBuffer || data instanceof DataView) {
1183                         if (!connection.enableFileSharing) {
1184                             throw 'It seems that receiving data is either "Blob" or "File" but file sharing is disabled.';
1185                         }
1186
1187                         if (!rtcMultiSession.fileBufferReader) {
1188                             var that = this;
1189                             initFileBufferReader(connection, function(fbr) {
1190                                 rtcMultiSession.fileBufferReader = fbr;
1191                                 that.onmessage(data);
1192                             });
1193                             return;
1194                         }
1195
1196                         var fileBufferReader = rtcMultiSession.fileBufferReader;
1197
1198                         fileBufferReader.convertToObject(data, function(chunk) {
1199                             if (chunk.maxChunks || chunk.readyForNextChunk) {
1200                                 // if target peer requested next chunk
1201                                 if (chunk.readyForNextChunk) {
1202                                     fileBufferReader.getNextChunk(chunk.uuid, function(nextChunk, isLastChunk, extra) {
1203                                         rtcMultiSession.send(nextChunk);
1204                                     });
1205                                     return;
1206                                 }
1207
1208                                 // if chunk is received
1209                                 fileBufferReader.addChunk(chunk, function(promptNextChunk) {
1210                                     // request next chunk
1211                                     rtcMultiSession.send(promptNextChunk);
1212                                 });
1213                                 return;
1214                             }
1215
1216                             connection.onmessage({
1217                                 data: chunk,
1218                                 userid: _config.userid,
1219                                 extra: _config.extra
1220                             });
1221                         });
1222                         return;
1223                     }
1224                 },
1225                 onaddstream: function(stream, session) {
1226                     session = session || _config.renegotiate || connection.session;
1227
1228                     // if it is data-only connection; then return.
1229                     if (isData(session)) return;
1230
1231                     if (session.screen && (session.audio || session.video)) {
1232                         if (!_config.gotAudioOrVideo) {
1233                             // audio/video are fired earlier than screen
1234                             _config.gotAudioOrVideo = true;
1235                             session.screen = false;
1236                         } else {
1237                             // screen stream is always fired later
1238                             session.audio = false;
1239                             session.video = false;
1240                         }
1241                     }
1242
1243                     var preMuted = {};
1244
1245                     if (_config.streaminfo) {
1246                         var streaminfo = _config.streaminfo.split('----');
1247                         var strInfo = JSON.parse(streaminfo[streaminfo.length - 1]);
1248
1249                         if (!isIE) {
1250                             stream.streamid = strInfo.streamid;
1251                             stream.isScreen = !!strInfo.isScreen;
1252                             stream.isVideo = !!strInfo.isVideo;
1253                             stream.isAudio = !!strInfo.isAudio;
1254                             preMuted = strInfo.preMuted;
1255                         }
1256
1257                         streaminfo.pop();
1258                         _config.streaminfo = streaminfo.join('----');
1259                     }
1260
1261                     var mediaElement = createMediaElement(stream, merge({
1262                         remote: true
1263                     }, session));
1264
1265                     if (connection.setDefaultEventsForMediaElement) {
1266                         connection.setDefaultEventsForMediaElement(mediaElement, stream.streamid);
1267                     }
1268
1269                     if (!isPluginRTC && !stream.getVideoTracks().length) {
1270                         function eventListener() {
1271                             setTimeout(function() {
1272                                 mediaElement.muted = false;
1273                                 afterRemoteStreamStartedFlowing({
1274                                     mediaElement: mediaElement,
1275                                     session: session,
1276                                     stream: stream,
1277                                     preMuted: preMuted
1278                                 });
1279                             }, 3000);
1280
1281                             mediaElement.removeEventListener('play', eventListener);
1282                         }
1283                         return mediaElement.addEventListener('play', eventListener, false);
1284                     }
1285
1286                     waitUntilRemoteStreamStartsFlowing({
1287                         mediaElement: mediaElement,
1288                         session: session,
1289                         stream: stream,
1290                         preMuted: preMuted
1291                     });
1292                 },
1293
1294                 onremovestream: function(stream) {
1295                     if (stream && stream.streamid) {
1296                         stream = connection.streams[stream.streamid];
1297                         if (stream) {
1298                             log('on:stream:ended via on:remove:stream', stream);
1299                             onStreamEndedHandler(stream, connection);
1300                         }
1301                     } else log('on:remove:stream', stream);
1302                 },
1303
1304                 onclose: function(e) {
1305                     e.extra = _config.extra;
1306                     e.userid = _config.userid;
1307                     connection.onclose(e);
1308
1309                     // suggested in #71 by "efaj"
1310                     if (connection.channels[e.userid]) {
1311                         delete connection.channels[e.userid];
1312                     }
1313                 },
1314                 onerror: function(e) {
1315                     e.extra = _config.extra;
1316                     e.userid = _config.userid;
1317                     connection.onerror(e);
1318                 },
1319
1320                 oniceconnectionstatechange: function(event) {
1321                     log('oniceconnectionstatechange', toStr(event));
1322
1323                     if (peer.connection && peer.connection.iceConnectionState == 'connected' && peer.connection.iceGatheringState == 'complete' && peer.connection.signalingState == 'stable' && connection.numberOfConnectedUsers == 1) {
1324                         connection.onconnected({
1325                             userid: _config.userid,
1326                             extra: _config.extra,
1327                             peer: connection.peers[_config.userid],
1328                             targetuser: _config.userinfo
1329                         });
1330                     }
1331
1332                     if (!connection.isInitiator && peer.connection && peer.connection.iceConnectionState == 'connected' && peer.connection.iceGatheringState == 'complete' && peer.connection.signalingState == 'stable' && connection.numberOfConnectedUsers == 1) {
1333                         connection.onstatechange({
1334                             userid: _config.userid,
1335                             extra: _config.extra,
1336                             name: 'connected-with-initiator',
1337                             reason: 'ICE connection state seems connected; gathering state is completed; and signaling state is stable.'
1338                         });
1339                     }
1340
1341                     if (connection.peers[_config.userid] && connection.peers[_config.userid].oniceconnectionstatechange) {
1342                         connection.peers[_config.userid].oniceconnectionstatechange(event);
1343                     }
1344
1345                     // if ICE connectivity check is failed; renegotiate or redial
1346                     if (connection.peers[_config.userid] && connection.peers[_config.userid].peer.connection.iceConnectionState == 'failed') {
1347                         connection.onfailed({
1348                             userid: _config.userid,
1349                             extra: _config.extra,
1350                             peer: connection.peers[_config.userid],
1351                             targetuser: _config.userinfo
1352                         });
1353                     }
1354
1355                     if (connection.peers[_config.userid] && connection.peers[_config.userid].peer.connection.iceConnectionState == 'disconnected') {
1356                         !peer.connection.renegotiate && connection.ondisconnected({
1357                             userid: _config.userid,
1358                             extra: _config.extra,
1359                             peer: connection.peers[_config.userid],
1360                             targetuser: _config.userinfo
1361                         });
1362                         peer.connection.renegotiate = false;
1363                     }
1364
1365                     if (!connection.autoReDialOnFailure) return;
1366
1367                     if (connection.peers[_config.userid]) {
1368                         if (connection.peers[_config.userid].peer.connection.iceConnectionState != 'disconnected') {
1369                             _config.redialing = false;
1370                         }
1371
1372                         if (connection.peers[_config.userid].peer.connection.iceConnectionState == 'disconnected' && !_config.redialing) {
1373                             _config.redialing = true;
1374                             warn('Peer connection is closed.', toStr(connection.peers[_config.userid].peer.connection), 'ReDialing..');
1375                             connection.peers[_config.userid].socket.send({
1376                                 redial: true
1377                             });
1378
1379                             // to make sure all old "remote" streams are also removed!
1380                             connection.streams.remove({
1381                                 remote: true,
1382                                 userid: _config.userid
1383                             });
1384                         }
1385                     }
1386                 },
1387
1388                 onsignalingstatechange: function(event) {
1389                     log('onsignalingstatechange', toStr(event));
1390                 },
1391
1392                 attachStreams: connection.dontAttachStream ? [] : connection.attachStreams,
1393                 iceServers: connection.iceServers,
1394                 rtcConfiguration: connection.rtcConfiguration,
1395                 bandwidth: connection.bandwidth,
1396                 sdpConstraints: connection.sdpConstraints,
1397                 optionalArgument: connection.optionalArgument,
1398                 disableDtlsSrtp: connection.disableDtlsSrtp,
1399                 dataChannelDict: connection.dataChannelDict,
1400                 preferSCTP: connection.preferSCTP,
1401
1402                 onSessionDescription: function(sessionDescription, streaminfo) {
1403                     sendsdp({
1404                         sdp: sessionDescription,
1405                         socket: socket,
1406                         streaminfo: streaminfo
1407                     });
1408                 },
1409                 trickleIce: connection.trickleIce,
1410                 processSdp: connection.processSdp,
1411                 sendStreamId: function(stream) {
1412                     socket && socket.send({
1413                         streamid: stream.streamid,
1414                         isScreen: !!stream.isScreen,
1415                         isAudio: !!stream.isAudio,
1416                         isVideo: !!stream.isVideo
1417                     });
1418                 },
1419                 rtcMultiConnection: connection
1420             };
1421
1422             function waitUntilRemoteStreamStartsFlowing(args) {
1423                 // chrome for android may have some features missing
1424                 if (isMobileDevice || isPluginRTC || (isNull(connection.waitUntilRemoteStreamStartsFlowing) || !connection.waitUntilRemoteStreamStartsFlowing)) {
1425                     return afterRemoteStreamStartedFlowing(args);
1426                 }
1427
1428                 if (!args.numberOfTimes) args.numberOfTimes = 0;
1429                 args.numberOfTimes++;
1430
1431                 if (!(args.mediaElement.readyState <= HTMLMediaElement.HAVE_CURRENT_DATA || args.mediaElement.paused || args.mediaElement.currentTime <= 0)) {
1432                     return afterRemoteStreamStartedFlowing(args);
1433                 }
1434
1435                 if (args.numberOfTimes >= 60) { // wait 60 seconds while video is delivered!
1436                     return socket.send({
1437                         failedToReceiveRemoteVideo: true,
1438                         streamid: args.stream.streamid
1439                     });
1440                 }
1441
1442                 setTimeout(function() {
1443                     log('Waiting for incoming remote stream to be started flowing: ' + args.numberOfTimes + ' seconds.');
1444                     waitUntilRemoteStreamStartsFlowing(args);
1445                 }, 900);
1446             }
1447
1448             function initFakeChannel() {
1449                 if (!connection.fakeDataChannels || connection.channels[_config.userid]) return;
1450
1451                 // for non-data connections; allow fake data sender!
1452                 if (!connection.session.data) {
1453                     var fakeChannel = {
1454                         send: function(data) {
1455                             socket.send({
1456                                 fakeData: data
1457                             });
1458                         },
1459                         readyState: 'open'
1460                     };
1461                     // connection.channels['user-id'].send(data);
1462                     connection.channels[_config.userid] = {
1463                         channel: fakeChannel,
1464                         send: function(data) {
1465                             this.channel.send(data);
1466                         }
1467                     };
1468                     peerConfig.onopen(fakeChannel);
1469                 }
1470             }
1471
1472             function afterRemoteStreamStartedFlowing(args) {
1473                 var mediaElement = args.mediaElement;
1474                 var session = args.session;
1475                 var stream = args.stream;
1476
1477                 stream.onended = function() {
1478                     if (streamedObject.mediaElement && !streamedObject.mediaElement.parentNode && document.getElementById(stream.streamid)) {
1479                         streamedObject.mediaElement = document.getElementById(stream.streamid);
1480                     }
1481
1482                     onStreamEndedHandler(streamedObject, connection);
1483                 };
1484
1485                 var streamedObject = {
1486                     mediaElement: mediaElement,
1487
1488                     stream: stream,
1489                     streamid: stream.streamid,
1490                     session: session || connection.session,
1491
1492                     blobURL: isPluginRTC ? '' : mediaElement.mozSrcObject ? URL.createObjectURL(stream) : mediaElement.src,
1493                     type: 'remote',
1494
1495                     extra: _config.extra,
1496                     userid: _config.userid,
1497
1498                     isVideo: isPluginRTC ? !!session.video : !!stream.isVideo,
1499                     isAudio: isPluginRTC ? !!session.audio && !session.video : !!stream.isAudio,
1500                     isScreen: !!stream.isScreen,
1501                     isInitiator: !!_config.isInitiator,
1502
1503                     rtcMultiConnection: connection,
1504                     socket: socket
1505                 };
1506
1507                 // connection.streams['stream-id'].mute({audio:true})
1508                 connection.streams[stream.streamid] = connection._getStream(streamedObject);
1509                 connection.onstream(streamedObject);
1510
1511                 if (!isEmpty(args.preMuted) && (args.preMuted.audio || args.preMuted.video)) {
1512                     var fakeObject = merge({}, streamedObject);
1513                     fakeObject.session = merge(fakeObject.session, args.preMuted);
1514
1515                     fakeObject.isAudio = !!fakeObject.session.audio && !fakeObject.session.video;
1516                     fakeObject.isVideo = !!fakeObject.session.video;
1517                     fakeObject.isScreen = false;
1518
1519                     connection.onmute(fakeObject);
1520                 }
1521
1522                 log('on:add:stream', streamedObject);
1523
1524                 onSessionOpened();
1525
1526                 if (connection.onspeaking) {
1527                     initHark({
1528                         stream: stream,
1529                         streamedObject: streamedObject,
1530                         connection: connection
1531                     });
1532                 }
1533             }
1534
1535             function onChannelOpened(channel) {
1536                 _config.channel = channel;
1537
1538                 // connection.channels['user-id'].send(data);
1539                 connection.channels[_config.userid] = {
1540                     channel: _config.channel,
1541                     send: function(data) {
1542                         connection.send(data, this.channel);
1543                     }
1544                 };
1545
1546                 connection.onopen({
1547                     extra: _config.extra,
1548                     userid: _config.userid,
1549                     channel: channel
1550                 });
1551
1552                 // fetch files from file-queue
1553                 for (var q in connection.fileQueue) {
1554                     connection.send(connection.fileQueue[q], channel);
1555                 }
1556
1557                 if (isData(connection.session)) onSessionOpened();
1558
1559                 if (connection.partOfScreen && connection.partOfScreen.sharing) {
1560                     connection.peers[_config.userid].sharePartOfScreen(connection.partOfScreen);
1561                 }
1562             }
1563
1564             function updateSocket() {
1565                 // todo: need to check following {if-block} MUST not affect "redial" process
1566                 if (socket.userid == _config.userid)
1567                     return;
1568
1569                 socket.userid = _config.userid;
1570                 sockets[_config.socketIndex] = socket;
1571
1572                 connection.numberOfConnectedUsers++;
1573                 // connection.peers['user-id'].addStream({audio:true})
1574                 connection.peers[_config.userid] = {
1575                     socket: socket,
1576                     peer: peer,
1577                     userid: _config.userid,
1578                     extra: _config.extra,
1579                     userinfo: _config.userinfo,
1580                     addStream: function(session00) {
1581                         // connection.peers['user-id'].addStream({audio: true, video: true);
1582
1583                         connection.addStream(session00, this.socket);
1584                     },
1585                     removeStream: function(streamid) {
1586                         if (!connection.streams[streamid])
1587                             return warn('No such stream exists. Stream-id:', streamid);
1588
1589                         this.peer.connection.removeStream(connection.streams[streamid].stream);
1590                         this.renegotiate();
1591                     },
1592                     renegotiate: function(stream, session) {
1593                         // connection.peers['user-id'].renegotiate();
1594
1595                         connection.renegotiate(stream, session);
1596                     },
1597                     changeBandwidth: function(bandwidth) {
1598                         // connection.peers['user-id'].changeBandwidth();
1599
1600                         if (!bandwidth) throw 'You MUST pass bandwidth object.';
1601                         if (isString(bandwidth)) throw 'Pass object for bandwidth instead of string; e.g. {audio:10, video:20}';
1602
1603                         // set bandwidth for self
1604                         this.peer.bandwidth = bandwidth;
1605
1606                         // ask remote user to synchronize bandwidth
1607                         this.socket.send({
1608                             changeBandwidth: true,
1609                             bandwidth: bandwidth
1610                         });
1611                     },
1612                     sendCustomMessage: function(message) {
1613                         // connection.peers['user-id'].sendCustomMessage();
1614
1615                         this.socket.send({
1616                             customMessage: true,
1617                             message: message
1618                         });
1619                     },
1620                     onCustomMessage: function(message) {
1621                         log('Received "private" message from', this.userid,
1622                             isString(message) ? message : toStr(message));
1623                     },
1624                     drop: function(dontSendMessage) {
1625                         // connection.peers['user-id'].drop();
1626
1627                         for (var stream in connection.streams) {
1628                             if (connection._skip.indexOf(stream) == -1) {
1629                                 stream = connection.streams[stream];
1630
1631                                 if (stream.userid == connection.userid && stream.type == 'local') {
1632                                     this.peer.connection.removeStream(stream.stream);
1633                                     onStreamEndedHandler(stream, connection);
1634                                 }
1635
1636                                 if (stream.type == 'remote' && stream.userid == this.userid) {
1637                                     onStreamEndedHandler(stream, connection);
1638                                 }
1639                             }
1640                         }
1641
1642                         !dontSendMessage && this.socket.send({
1643                             drop: true
1644                         });
1645                     },
1646                     hold: function(holdMLine) {
1647                         // connection.peers['user-id'].hold();
1648
1649                         if (peer.prevCreateType == 'answer') {
1650                             this.socket.send({
1651                                 unhold: true,
1652                                 holdMLine: holdMLine || 'both',
1653                                 takeAction: true
1654                             });
1655                             return;
1656                         }
1657
1658                         this.socket.send({
1659                             hold: true,
1660                             holdMLine: holdMLine || 'both'
1661                         });
1662
1663                         this.peer.hold = true;
1664                         this.fireHoldUnHoldEvents({
1665                             kind: holdMLine,
1666                             isHold: true,
1667                             userid: connection.userid,
1668                             remoteUser: this.userid
1669                         });
1670                     },
1671                     unhold: function(holdMLine) {
1672                         // connection.peers['user-id'].unhold();
1673
1674                         if (peer.prevCreateType == 'answer') {
1675                             this.socket.send({
1676                                 unhold: true,
1677                                 holdMLine: holdMLine || 'both',
1678                                 takeAction: true
1679                             });
1680                             return;
1681                         }
1682
1683                         this.socket.send({
1684                             unhold: true,
1685                             holdMLine: holdMLine || 'both'
1686                         });
1687
1688                         this.peer.hold = false;
1689                         this.fireHoldUnHoldEvents({
1690                             kind: holdMLine,
1691                             isHold: false,
1692                             userid: connection.userid,
1693                             remoteUser: this.userid
1694                         });
1695                     },
1696                     fireHoldUnHoldEvents: function(e) {
1697                         // this method is for inner usages only!
1698
1699                         var isHold = e.isHold;
1700                         var kind = e.kind;
1701                         var userid = e.remoteUser || e.userid;
1702
1703                         // hold means inactive a specific media line!
1704                         // a media line can contain multiple synced sources (ssrc)
1705                         // i.e. a media line can reference multiple tracks!
1706                         // that's why hold will affect all relevant tracks in a specific media line!
1707                         for (var stream in connection.streams) {
1708                             if (connection._skip.indexOf(stream) == -1) {
1709                                 stream = connection.streams[stream];
1710
1711                                 if (stream.userid == userid) {
1712                                     // www.RTCMultiConnection.org/docs/onhold/
1713                                     if (isHold)
1714                                         connection.onhold(merge({
1715                                             kind: kind
1716                                         }, stream));
1717
1718                                     // www.RTCMultiConnection.org/docs/onunhold/
1719                                     if (!isHold)
1720                                         connection.onunhold(merge({
1721                                             kind: kind
1722                                         }, stream));
1723                                 }
1724                             }
1725                         }
1726                     },
1727                     redial: function() {
1728                         // connection.peers['user-id'].redial();
1729
1730                         // 1st of all; remove all relevant remote media streams
1731                         for (var stream in connection.streams) {
1732                             if (connection._skip.indexOf(stream) == -1) {
1733                                 stream = connection.streams[stream];
1734
1735                                 if (stream.userid == this.userid && stream.type == 'remote') {
1736                                     onStreamEndedHandler(stream, connection);
1737                                 }
1738                             }
1739                         }
1740
1741                         log('ReDialing...');
1742
1743                         socket.send({
1744                             recreatePeer: true
1745                         });
1746
1747                         peer = new PeerConnection();
1748                         peer.create('offer', peerConfig);
1749                     },
1750                     sharePartOfScreen: function(args) {
1751                         // www.RTCMultiConnection.org/docs/onpartofscreen/
1752                         var that = this;
1753                         var lastScreenshot = '';
1754
1755                         function partOfScreenCapturer() {
1756                             // if stopped
1757                             if (that.stopPartOfScreenSharing) {
1758                                 that.stopPartOfScreenSharing = false;
1759
1760                                 if (connection.onpartofscreenstopped) {
1761                                     connection.onpartofscreenstopped();
1762                                 }
1763                                 return;
1764                             }
1765
1766                             // if paused
1767                             if (that.pausePartOfScreenSharing) {
1768                                 if (connection.onpartofscreenpaused) {
1769                                     connection.onpartofscreenpaused();
1770                                 }
1771
1772                                 return setTimeout(partOfScreenCapturer, args.interval || 200);
1773                             }
1774
1775                             capturePartOfScreen({
1776                                 element: args.element,
1777                                 connection: connection,
1778                                 callback: function(screenshot) {
1779                                     if (!connection.channels[that.userid]) {
1780                                         throw 'No such data channel exists.';
1781                                     }
1782
1783                                     // don't share repeated content
1784                                     if (screenshot != lastScreenshot) {
1785                                         lastScreenshot = screenshot;
1786                                         connection.channels[that.userid].send({
1787                                             screenshot: screenshot,
1788                                             isPartOfScreen: true
1789                                         });
1790                                     }
1791
1792                                     // "once" can be used to share single screenshot
1793                                     !args.once && setTimeout(partOfScreenCapturer, args.interval || 200);
1794                                 }
1795                             });
1796                         }
1797
1798                         partOfScreenCapturer();
1799                     },
1800                     getConnectionStats: function(callback, interval) {
1801                         if (!callback) throw 'callback is mandatory.';
1802
1803                         if (!window.getConnectionStats) {
1804                             loadScript(connection.resources.getConnectionStats, invoker);
1805                         } else invoker();
1806
1807                         function invoker() {
1808                             RTCPeerConnection.prototype.getConnectionStats = window.getConnectionStats;
1809                             peer.connection && peer.connection.getConnectionStats(callback, interval);
1810                         }
1811                     },
1812                     takeSnapshot: function(callback) {
1813                         takeSnapshot({
1814                             userid: this.userid,
1815                             connection: connection,
1816                             callback: callback
1817                         });
1818                     }
1819                 };
1820             }
1821
1822             function onSessionOpened() {
1823                 // original conferencing infrastructure!
1824                 if (connection.isInitiator && getLength(participants) && getLength(participants) <= connection.maxParticipantsAllowed) {
1825                     if (!connection.session.oneway && !connection.session.broadcast) {
1826                         defaultSocket.send({
1827                             sessionid: connection.sessionid,
1828                             newParticipant: _config.userid || socket.channel,
1829                             userData: {
1830                                 userid: _config.userid || socket.channel,
1831                                 extra: _config.extra
1832                             }
1833                         });
1834                     }
1835                 }
1836
1837                 // 1st: renegotiation is supported only on chrome
1838                 // 2nd: must not renegotiate same media multiple times
1839                 // 3rd: todo: make sure that target-user has no such "renegotiated" media.
1840                 if (_config.userinfo.browser == 'chrome' && !_config.renegotiatedOnce) {
1841                     // this code snippet is added to make sure that "previously-renegotiated" streams are also 
1842                     // renegotiated to this new user
1843                     for (var rSession in connection.renegotiatedSessions) {
1844                         _config.renegotiatedOnce = true;
1845
1846                         if (connection.renegotiatedSessions[rSession] && connection.renegotiatedSessions[rSession].stream) {
1847                             connection.peers[_config.userid].renegotiate(connection.renegotiatedSessions[rSession].stream, connection.renegotiatedSessions[rSession].session);
1848                         }
1849                     }
1850                 }
1851             }
1852
1853             function socketResponse(response) {
1854                 if (isRMSDeleted) return;
1855
1856                 if (response.userid == connection.userid)
1857                     return;
1858
1859                 if (response.sdp) {
1860                     _config.userid = response.userid;
1861                     _config.extra = response.extra || {};
1862                     _config.renegotiate = response.renegotiate;
1863                     _config.streaminfo = response.streaminfo;
1864                     _config.isInitiator = response.isInitiator;
1865                     _config.userinfo = response.userinfo;
1866
1867                     var sdp = JSON.parse(response.sdp);
1868
1869                     if (sdp.type == 'offer') {
1870                         // to synchronize SCTP or RTP
1871                         peerConfig.preferSCTP = !!response.preferSCTP;
1872                         connection.fakeDataChannels = !!response.fakeDataChannels;
1873                     }
1874
1875                     // initializing fake channel
1876                     initFakeChannel();
1877
1878                     sdpInvoker(sdp, response.labels);
1879                 }
1880
1881                 if (response.candidate) {
1882                     peer && peer.addIceCandidate(JSON.parse(response.candidate));
1883                 }
1884
1885                 if (response.streamid) {
1886                     if (!rtcMultiSession.streamids) {
1887                         rtcMultiSession.streamids = {};
1888                     }
1889                     if (!rtcMultiSession.streamids[response.streamid]) {
1890                         rtcMultiSession.streamids[response.streamid] = response.streamid;
1891                         connection.onstreamid(response);
1892                     }
1893                 }
1894
1895                 if (response.mute || response.unmute) {
1896                     if (response.promptMuteUnmute) {
1897                         if (!connection.privileges.canMuteRemoteStream) {
1898                             connection.onstatechange({
1899                                 userid: response.userid,
1900                                 extra: response.extra,
1901                                 name: 'mute-request-denied',
1902                                 reason: response.userid + ' tried to mute your stream; however "privileges.canMuteRemoteStream" is "false".'
1903                             });
1904                             return;
1905                         }
1906
1907                         if (connection.streams[response.streamid]) {
1908                             if (response.mute && !connection.streams[response.streamid].muted) {
1909                                 connection.streams[response.streamid].mute(response.session);
1910                             }
1911                             if (response.unmute && connection.streams[response.streamid].muted) {
1912                                 connection.streams[response.streamid].unmute(response.session);
1913                             }
1914                         }
1915                     } else {
1916                         var streamObject = {};
1917                         if (connection.streams[response.streamid]) {
1918                             streamObject = connection.streams[response.streamid];
1919                         }
1920
1921                         var session = response.session;
1922                         var fakeObject = merge({}, streamObject);
1923                         fakeObject.session = session;
1924
1925                         fakeObject.isAudio = !!fakeObject.session.audio && !fakeObject.session.video;
1926                         fakeObject.isVideo = !!fakeObject.session.video;
1927                         fakeObject.isScreen = !!fakeObject.session.screen;
1928
1929                         if (response.mute) connection.onmute(fakeObject || response);
1930                         if (response.unmute) connection.onunmute(fakeObject || response);
1931                     }
1932                 }
1933
1934                 if (response.isVolumeChanged) {
1935                     log('Volume of stream: ' + response.streamid + ' has changed to: ' + response.volume);
1936                     if (connection.streams[response.streamid]) {
1937                         var mediaElement = connection.streams[response.streamid].mediaElement;
1938                         if (mediaElement) mediaElement.volume = response.volume;
1939                     }
1940                 }
1941
1942                 // to stop local stream
1943                 if (response.stopped) {
1944                     if (connection.streams[response.streamid]) {
1945                         onStreamEndedHandler(connection.streams[response.streamid], connection);
1946                     }
1947                 }
1948
1949                 // to stop remote stream
1950                 if (response.promptStreamStop /* && !connection.isInitiator */ ) {
1951                     if (!connection.privileges.canStopRemoteStream) {
1952                         connection.onstatechange({
1953                             userid: response.userid,
1954                             extra: response.extra,
1955                             name: 'stop-request-denied',
1956                             reason: response.userid + ' tried to stop your stream; however "privileges.canStopRemoteStream" is "false".'
1957                         });
1958                         return;
1959                     }
1960                     warn('Remote stream has been manually stopped!');
1961                     if (connection.streams[response.streamid]) {
1962                         connection.streams[response.streamid].stop();
1963                     }
1964                 }
1965
1966                 if (response.left) {
1967                     // firefox is unable to stop remote streams
1968                     // firefox doesn't auto stop streams when peer.close() is called.
1969                     if (isFirefox) {
1970                         var userLeft = response.userid;
1971                         for (var stream in connection.streams) {
1972                             stream = connection.streams[stream];
1973                             if (stream.userid == userLeft) {
1974                                 connection.stopMediaStream(stream);
1975                                 onStreamEndedHandler(stream, connection);
1976                             }
1977                         }
1978                     }
1979
1980                     if (peer && peer.connection) {
1981                         // todo: verify if-block's 2nd condition
1982                         if (peer.connection.signalingState != 'closed' && peer.connection.iceConnectionState.search(/disconnected|failed/gi) == -1) {
1983                             peer.connection.close();
1984                         }
1985                         peer.connection = null;
1986                     }
1987
1988                     if (participants[response.userid]) delete participants[response.userid];
1989
1990                     if (response.closeEntireSession) {
1991                         connection.onSessionClosed(response);
1992                         connection.leave();
1993                         return;
1994                     }
1995
1996                     connection.remove(response.userid);
1997
1998                     onLeaveHandler({
1999                         userid: response.userid,
2000                         extra: response.extra || {},
2001                         entireSessionClosed: !!response.closeEntireSession
2002                     }, connection);
2003                 }
2004
2005                 // keeping session active even if initiator leaves
2006                 if (response.playRoleOfBroadcaster) {
2007                     if (response.extra) {
2008                         // clone extra-data from initial moderator
2009                         connection.extra = merge(connection.extra, response.extra);
2010                     }
2011                     if (response.participants) {
2012                         participants = response.participants;
2013
2014                         // make sure that if 2nd initiator leaves; control is shifted to 3rd person.
2015                         if (participants[connection.userid]) {
2016                             delete participants[connection.userid];
2017                         }
2018
2019                         if (sockets[0] && sockets[0].userid == response.userid) {
2020                             delete sockets[0];
2021                             sockets = swap(sockets);
2022                         }
2023
2024                         if (socketObjects[response.userid]) {
2025                             delete socketObjects[response.userid];
2026                         }
2027                     }
2028
2029                     setTimeout(connection.playRoleOfInitiator, 2000);
2030                 }
2031
2032                 if (response.changeBandwidth) {
2033                     if (!connection.peers[response.userid]) throw 'No such peer exists.';
2034
2035                     // synchronize bandwidth
2036                     connection.peers[response.userid].peer.bandwidth = response.bandwidth;
2037
2038                     // renegotiate to apply bandwidth
2039                     connection.peers[response.userid].renegotiate();
2040                 }
2041
2042                 if (response.customMessage) {
2043                     if (!connection.peers[response.userid]) throw 'No such peer exists.';
2044                     if (response.message.ejected) {
2045                         if (connection.sessionDescriptions[connection.sessionid].userid != response.userid) {
2046                             throw 'only initiator can eject a user.';
2047                         }
2048                         // initiator ejected this user
2049                         connection.leave();
2050
2051                         connection.onSessionClosed({
2052                             userid: response.userid,
2053                             extra: response.extra || _config.extra,
2054                             isEjected: true
2055                         });
2056                     } else connection.peers[response.userid].onCustomMessage(response.message);
2057                 }
2058
2059                 if (response.drop) {
2060                     if (!connection.peers[response.userid]) throw 'No such peer exists.';
2061                     connection.peers[response.userid].drop(true);
2062                     connection.peers[response.userid].renegotiate();
2063
2064                     connection.ondrop(response.userid);
2065                 }
2066
2067                 if (response.hold || response.unhold) {
2068                     if (!connection.peers[response.userid]) throw 'No such peer exists.';
2069
2070                     if (response.takeAction) {
2071                         connection.peers[response.userid][!!response.hold ? 'hold' : 'unhold'](response.holdMLine);
2072                         return;
2073                     }
2074
2075                     connection.peers[response.userid].peer.hold = !!response.hold;
2076                     connection.peers[response.userid].peer.holdMLine = response.holdMLine;
2077
2078                     socket.send({
2079                         isRenegotiate: true
2080                     });
2081
2082                     connection.peers[response.userid].fireHoldUnHoldEvents({
2083                         kind: response.holdMLine,
2084                         isHold: !!response.hold,
2085                         userid: response.userid
2086                     });
2087                 }
2088
2089                 if (response.isRenegotiate) {
2090                     connection.peers[response.userid].renegotiate(null, connection.peers[response.userid].peer.session);
2091                 }
2092
2093                 // fake data channels!
2094                 if (response.fakeData) {
2095                     peerConfig.onmessage(response.fakeData);
2096                 }
2097
2098                 // sometimes we don't need to renegotiate e.g. when peers are disconnected
2099                 // or if it is firefox
2100                 if (response.recreatePeer) {
2101                     peer = new PeerConnection();
2102                 }
2103
2104                 // remote video failed either out of ICE gathering process or ICE connectivity check-up
2105                 // or IceAgent was unable to locate valid candidates/ports.
2106                 if (response.failedToReceiveRemoteVideo) {
2107                     log('Remote peer hasn\'t received stream: ' + response.streamid + '. Renegotiating...');
2108                     if (connection.peers[response.userid]) {
2109                         connection.peers[response.userid].renegotiate();
2110                     }
2111                 }
2112
2113                 if (response.redial) {
2114                     if (connection.peers[response.userid]) {
2115                         if (connection.peers[response.userid].peer.connection.iceConnectionState != 'disconnected') {
2116                             _config.redialing = false;
2117                         }
2118
2119                         if (connection.peers[response.userid].peer.connection.iceConnectionState == 'disconnected' && !_config.redialing) {
2120                             _config.redialing = true;
2121
2122                             warn('Peer connection is closed.', toStr(connection.peers[response.userid].peer.connection), 'ReDialing..');
2123                             connection.peers[response.userid].redial();
2124                         }
2125                     }
2126                 }
2127             }
2128
2129             connection.playRoleOfInitiator = function() {
2130                 connection.dontCaptureUserMedia = true;
2131                 connection.open();
2132                 sockets = swap(sockets);
2133                 connection.dontCaptureUserMedia = false;
2134             };
2135
2136             connection.askToShareParticipants = function() {
2137                 defaultSocket && defaultSocket.send({
2138                     askToShareParticipants: true
2139                 });
2140             };
2141
2142             connection.shareParticipants = function(args) {
2143                 var message = {
2144                     joinUsers: participants,
2145                     userid: connection.userid,
2146                     extra: connection.extra
2147                 };
2148
2149                 if (args) {
2150                     if (args.dontShareWith) message.dontShareWith = args.dontShareWith;
2151                     if (args.shareWith) message.shareWith = args.shareWith;
2152                 }
2153
2154                 defaultSocket.send(message);
2155             };
2156
2157             function sdpInvoker(sdp, labels) {
2158                 if (sdp.type == 'answer') {
2159                     peer.setRemoteDescription(sdp);
2160                     updateSocket();
2161                     return;
2162                 }
2163                 if (!_config.renegotiate && sdp.type == 'offer') {
2164                     peerConfig.offerDescription = sdp;
2165
2166                     peerConfig.session = connection.session;
2167                     if (!peer) peer = new PeerConnection();
2168                     peer.create('answer', peerConfig);
2169
2170                     updateSocket();
2171                     return;
2172                 }
2173
2174                 var session = _config.renegotiate;
2175                 // detach streams
2176                 detachMediaStream(labels, peer.connection);
2177
2178                 if (session.oneway || isData(session)) {
2179                     createAnswer();
2180                     delete _config.renegotiate;
2181                 } else {
2182                     if (_config.capturing)
2183                         return;
2184
2185                     _config.capturing = true;
2186
2187                     connection.captureUserMedia(function(stream) {
2188                         _config.capturing = false;
2189
2190                         peer.addStream(stream);
2191
2192                         connection.renegotiatedSessions[JSON.stringify(_config.renegotiate)] = {
2193                             session: _config.renegotiate,
2194                             stream: stream
2195                         };
2196
2197                         delete _config.renegotiate;
2198
2199                         createAnswer();
2200                     }, _config.renegotiate);
2201                 }
2202
2203                 function createAnswer() {
2204                     peer.recreateAnswer(sdp, session, function(_sdp, streaminfo) {
2205                         sendsdp({
2206                             sdp: _sdp,
2207                             socket: socket,
2208                             streaminfo: streaminfo
2209                         });
2210                         connection.detachStreams = [];
2211                     });
2212                 }
2213             }
2214         }
2215
2216         function detachMediaStream(labels, peer) {
2217             if (!labels) return;
2218             for (var i = 0; i < labels.length; i++) {
2219                 var label = labels[i];
2220                 if (connection.streams[label]) {
2221                     peer.removeStream(connection.streams[label].stream);
2222                 }
2223             }
2224         }
2225
2226         function sendsdp(e) {
2227             e.socket.send({
2228                 sdp: JSON.stringify({
2229                     sdp: e.sdp.sdp,
2230                     type: e.sdp.type
2231                 }),
2232                 renegotiate: !!e.renegotiate ? e.renegotiate : false,
2233                 streaminfo: e.streaminfo || '',
2234                 labels: e.labels || [],
2235                 preferSCTP: !!connection.preferSCTP,
2236                 fakeDataChannels: !!connection.fakeDataChannels,
2237                 isInitiator: !!connection.isInitiator,
2238                 userinfo: {
2239                     browser: isFirefox ? 'firefox' : 'chrome'
2240                 }
2241             });
2242         }
2243
2244         // sharing new user with existing participants
2245
2246         function onNewParticipant(response) {
2247             var channel = response.newParticipant;
2248
2249             if (!channel || !!participants[channel] || channel == connection.userid)
2250                 return;
2251
2252             var new_channel = connection.token();
2253             newPrivateSocket({
2254                 channel: new_channel,
2255                 extra: response.userData ? response.userData.extra : response.extra,
2256                 userid: response.userData ? response.userData.userid : response.userid
2257             });
2258
2259             defaultSocket.send({
2260                 participant: true,
2261                 targetUser: channel,
2262                 channel: new_channel
2263             });
2264         }
2265
2266         // if a user leaves
2267
2268         function clearSession() {
2269             connection.numberOfConnectedUsers--;
2270
2271             var alertMessage = {
2272                 left: true,
2273                 extra: connection.extra || {},
2274                 userid: connection.userid,
2275                 sessionid: connection.sessionid
2276             };
2277
2278             if (connection.isInitiator) {
2279                 // if initiator wants to close entire session
2280                 if (connection.autoCloseEntireSession) {
2281                     alertMessage.closeEntireSession = true;
2282                 } else if (sockets[0]) {
2283                     // shift initiation control to another user
2284                     sockets[0].send({
2285                         playRoleOfBroadcaster: true,
2286                         userid: connection.userid,
2287                         extra: connection.extra,
2288                         participants: participants
2289                     });
2290                 }
2291             }
2292
2293             sockets.forEach(function(socket, i) {
2294                 socket.send(alertMessage);
2295
2296                 if (socketObjects[socket.channel]) {
2297                     delete socketObjects[socket.channel];
2298                 }
2299
2300                 delete sockets[i];
2301             });
2302
2303             sockets = swap(sockets);
2304
2305             connection.refresh();
2306
2307             webAudioMediaStreamSources.forEach(function(mediaStreamSource) {
2308                 // if source is connected; then chrome will crash on unload.
2309                 mediaStreamSource.disconnect();
2310             });
2311
2312             webAudioMediaStreamSources = [];
2313         }
2314
2315         // www.RTCMultiConnection.org/docs/remove/
2316         connection.remove = function(userid) {
2317             if (rtcMultiSession.requestsFrom && rtcMultiSession.requestsFrom[userid]) delete rtcMultiSession.requestsFrom[userid];
2318
2319             if (connection.peers[userid]) {
2320                 if (connection.peers[userid].peer && connection.peers[userid].peer.connection) {
2321                     if (connection.peers[userid].peer.connection.signalingState != 'closed') {
2322                         connection.peers[userid].peer.connection.close();
2323                     }
2324                     connection.peers[userid].peer.connection = null;
2325                 }
2326                 delete connection.peers[userid];
2327             }
2328             if (participants[userid]) {
2329                 delete participants[userid];
2330             }
2331
2332             for (var stream in connection.streams) {
2333                 stream = connection.streams[stream];
2334                 if (stream.userid == userid) {
2335                     onStreamEndedHandler(stream, connection);
2336                     delete connection.streams[stream];
2337                 }
2338             }
2339
2340             if (socketObjects[userid]) {
2341                 delete socketObjects[userid];
2342             }
2343         };
2344
2345         // www.RTCMultiConnection.org/docs/refresh/
2346         connection.refresh = function() {
2347             // if firebase; remove data from firebase servers
2348             if (connection.isInitiator && !!connection.socket && !!connection.socket.remove) {
2349                 connection.socket.remove();
2350             }
2351
2352             participants = {};
2353
2354             // to stop/remove self streams
2355             for (var i = 0; i < connection.attachStreams.length; i++) {
2356                 connection.stopMediaStream(connection.attachStreams[i]);
2357             }
2358
2359             // to allow capturing of identical streams
2360             currentUserMediaRequest = {
2361                 streams: [],
2362                 mutex: false,
2363                 queueRequests: []
2364             };
2365
2366             rtcMultiSession.isOwnerLeaving = true;
2367
2368             connection.isInitiator = false;
2369             connection.isAcceptNewSession = true;
2370             connection.attachMediaStreams = [];
2371             connection.sessionDescription = null;
2372             connection.sessionDescriptions = {};
2373             connection.localStreamids = [];
2374             connection.preRecordedMedias = {};
2375             connection.snapshots = {};
2376
2377             connection.numberOfConnectedUsers = 0;
2378             connection.numberOfSessions = 0;
2379
2380             connection.attachStreams = [];
2381             connection.detachStreams = [];
2382             connection.fileQueue = {};
2383             connection.channels = {};
2384             connection.renegotiatedSessions = {};
2385
2386             for (var peer in connection.peers) {
2387                 if (peer != connection.userid) {
2388                     delete connection.peers[peer];
2389                 }
2390             }
2391
2392             // to make sure remote streams are also removed!
2393             for (var stream in connection.streams) {
2394                 if (connection._skip.indexOf(stream) == -1) {
2395                     onStreamEndedHandler(connection.streams[stream], connection);
2396                     delete connection.streams[stream];
2397                 }
2398             }
2399
2400             socketObjects = {};
2401             sockets = [];
2402             participants = {};
2403         };
2404
2405         // www.RTCMultiConnection.org/docs/reject/
2406         connection.reject = function(userid) {
2407             if (!isString(userid)) userid = userid.userid;
2408             defaultSocket.send({
2409                 rejectedRequestOf: userid
2410             });
2411
2412             // remove relevant data to allow him join again
2413             connection.remove(userid);
2414         };
2415
2416         rtcMultiSession.leaveHandler = function(e) {
2417             if (!connection.leaveOnPageUnload) return;
2418
2419             if (isNull(e.keyCode)) {
2420                 return clearSession();
2421             }
2422
2423             if (e.keyCode == 116) {
2424                 clearSession();
2425             }
2426         };
2427
2428         listenEventHandler('beforeunload', rtcMultiSession.leaveHandler);
2429         listenEventHandler('keyup', rtcMultiSession.leaveHandler);
2430
2431         rtcMultiSession.onLineOffLineHandler = function() {
2432             if (!navigator.onLine) {
2433                 rtcMultiSession.isOffLine = true;
2434             } else if (rtcMultiSession.isOffLine) {
2435                 rtcMultiSession.isOffLine = !navigator.onLine;
2436
2437                 // defaultSocket = getDefaultSocketRef();
2438
2439                 // pending tasks should be resumed?
2440                 // sockets should be reconnected?
2441                 // peers should be re-established?
2442             }
2443         };
2444
2445         listenEventHandler('load', rtcMultiSession.onLineOffLineHandler);
2446         listenEventHandler('online', rtcMultiSession.onLineOffLineHandler);
2447         listenEventHandler('offline', rtcMultiSession.onLineOffLineHandler);
2448
2449         function onSignalingReady() {
2450             if (rtcMultiSession.signalingReady) return;
2451             rtcMultiSession.signalingReady = true;
2452
2453             setTimeout(callbackForSignalingReady, 1000);
2454
2455             if (!connection.isInitiator) {
2456                 // as soon as signaling gateway is connected;
2457                 // user should check existing rooms!
2458                 defaultSocket && defaultSocket.send({
2459                     searchingForRooms: true
2460                 });
2461             }
2462         }
2463
2464         function joinParticipants(joinUsers) {
2465             for (var user in joinUsers) {
2466                 if (!participants[joinUsers[user]]) {
2467                     onNewParticipant({
2468                         sessionid: connection.sessionid,
2469                         newParticipant: joinUsers[user],
2470                         userid: connection.userid,
2471                         extra: connection.extra
2472                     });
2473                 }
2474             }
2475         }
2476
2477         function getDefaultSocketRef() {
2478             return connection.openSignalingChannel({
2479                 onmessage: function(response) {
2480                     // RMS == RTCMultiSession
2481                     if (isRMSDeleted) return;
2482
2483                     // if message is sent by same user
2484                     if (response.userid == connection.userid) return;
2485
2486                     if (response.sessionid && response.userid) {
2487                         if (!connection.sessionDescriptions[response.sessionid]) {
2488                             connection.numberOfSessions++;
2489                             connection.sessionDescriptions[response.sessionid] = response;
2490
2491                             // fire "onNewSession" only if:
2492                             // 1) "isAcceptNewSession" boolean is true
2493                             // 2) "sessionDescriptions" object isn't having same session i.e. to prevent duplicate invocations
2494                             if (connection.isAcceptNewSession) {
2495
2496                                 if (!connection.dontOverrideSession) {
2497                                     connection.session = response.session;
2498                                 }
2499
2500                                 onNewSession(response);
2501                             }
2502                         }
2503                     }
2504
2505                     if (response.newParticipant && !connection.isAcceptNewSession && rtcMultiSession.broadcasterid === response.userid) {
2506                         if (response.newParticipant != connection.userid) {
2507                             onNewParticipant(response);
2508                         }
2509                     }
2510
2511                     if (getLength(participants) < connection.maxParticipantsAllowed && response.targetUser == connection.userid && response.participant) {
2512                         if (connection.peers[response.userid] && !connection.peers[response.userid].peer) {
2513                             delete participants[response.userid];
2514                             delete connection.peers[response.userid];
2515                             connection.isAcceptNewSession = true;
2516                             return acceptRequest(response);
2517                         }
2518
2519                         if (!participants[response.userid]) {
2520                             acceptRequest(response);
2521                         }
2522                     }
2523
2524                     if (response.acceptedRequestOf == connection.userid) {
2525                         connection.onstatechange({
2526                             userid: response.userid,
2527                             extra: response.extra,
2528                             name: 'request-accepted',
2529                             reason: response.userid + ' accepted your participation request.'
2530                         });
2531                     }
2532
2533                     if (response.rejectedRequestOf == connection.userid) {
2534                         connection.onstatechange({
2535                             userid: response.userid,
2536                             extra: response.extra,
2537                             name: 'request-rejected',
2538                             reason: response.userid + ' rejected your participation request.'
2539                         });
2540                     }
2541
2542                     if (response.customMessage) {
2543                         if (response.message.drop) {
2544                             connection.ondrop(response.userid);
2545
2546                             connection.attachStreams = [];
2547                             // "drop" should detach all local streams
2548                             for (var stream in connection.streams) {
2549                                 if (connection._skip.indexOf(stream) == -1) {
2550                                     stream = connection.streams[stream];
2551                                     if (stream.type == 'local') {
2552                                         connection.detachStreams.push(stream.streamid);
2553                                         onStreamEndedHandler(stream, connection);
2554                                     } else onStreamEndedHandler(stream, connection);
2555                                 }
2556                             }
2557
2558                             if (response.message.renegotiate) {
2559                                 // renegotiate; so "peer.removeStream" happens.
2560                                 connection.renegotiate();
2561                             }
2562                         } else if (connection.onCustomMessage) {
2563                             connection.onCustomMessage(response.message);
2564                         }
2565                     }
2566
2567                     if (connection.isInitiator && response.searchingForRooms) {
2568                         defaultSocket && defaultSocket.send({
2569                             sessionDescription: connection.sessionDescription,
2570                             responseFor: response.userid
2571                         });
2572                     }
2573
2574                     if (response.sessionDescription && response.responseFor == connection.userid) {
2575                         var sessionDescription = response.sessionDescription;
2576                         if (!connection.sessionDescriptions[sessionDescription.sessionid]) {
2577                             connection.numberOfSessions++;
2578                             connection.sessionDescriptions[sessionDescription.sessionid] = sessionDescription;
2579                         }
2580                     }
2581
2582                     if (connection.isInitiator && response.askToShareParticipants && defaultSocket) {
2583                         connection.shareParticipants({
2584                             shareWith: response.userid
2585                         });
2586                     }
2587
2588                     // participants are shared with single user
2589                     if (response.shareWith == connection.userid && response.dontShareWith != connection.userid && response.joinUsers) {
2590                         joinParticipants(response.joinUsers);
2591                     }
2592
2593                     // participants are shared with all users
2594                     if (!response.shareWith && response.joinUsers) {
2595                         if (response.dontShareWith) {
2596                             if (connection.userid != response.dontShareWith) {
2597                                 joinParticipants(response.joinUsers);
2598                             }
2599                         } else joinParticipants(response.joinUsers);
2600                     }
2601
2602                     if (response.messageFor == connection.userid && response.presenceState) {
2603                         if (response.presenceState == 'checking') {
2604                             defaultSocket.send({
2605                                 messageFor: response.userid,
2606                                 presenceState: 'available',
2607                                 _config: response._config
2608                             });
2609                             log('participant asked for availability');
2610                         }
2611
2612                         if (response.presenceState == 'available') {
2613                             rtcMultiSession.presenceState = 'available';
2614
2615                             connection.onstatechange({
2616                                 userid: 'browser',
2617                                 extra: {},
2618                                 name: 'room-available',
2619                                 reason: 'Initiator is available and room is active.'
2620                             });
2621
2622                             joinSession(response._config);
2623                         }
2624                     }
2625
2626                     if (response.donotJoin && response.messageFor == connection.userid) {
2627                         log(response.userid, 'is not joining your room.');
2628                     }
2629
2630                     // if initiator disconnects sockets, participants should also disconnect
2631                     if (response.isDisconnectSockets) {
2632                         log('Disconnecting your sockets because initiator also disconnected his sockets.');
2633                         connection.disconnect();
2634                     }
2635                 },
2636                 callback: function(socket) {
2637                     socket && this.onopen(socket);
2638                 },
2639                 onopen: function(socket) {
2640                     if (socket) defaultSocket = socket;
2641                     if (onSignalingReady) onSignalingReady();
2642
2643                     rtcMultiSession.defaultSocket = defaultSocket;
2644
2645                     if (!defaultSocket.__push) {
2646                         defaultSocket.__push = defaultSocket.send;
2647                         defaultSocket.send = function(message) {
2648                             message.userid = message.userid || connection.userid;
2649                             message.extra = message.extra || connection.extra || {};
2650
2651                             defaultSocket.__push(message);
2652                         };
2653                     }
2654                 }
2655             });
2656         }
2657
2658         // default-socket is a common socket shared among all users in a specific channel;
2659         // to share participation requests; room descriptions; and other stuff.
2660         var defaultSocket = getDefaultSocketRef();
2661
2662         rtcMultiSession.defaultSocket = defaultSocket;
2663
2664         if (defaultSocket && onSignalingReady) setTimeout(onSignalingReady, 2000);
2665
2666         if (connection.session.screen) {
2667             loadScreenFrame();
2668         }
2669         // CUSTOM CODE //
2670         
2671         /* Commenting this
2672         connection.getExternalIceServers  && loadIceFrame(function(iceServers)
2673                         {
2674             connection.iceServers = connection.iceServers.concat(iceServers);
2675         });
2676         */
2677         
2678        // CUSTOM CODE //
2679
2680         if (connection.log == false) connection.skipLogs();
2681         if (connection.onlog) {
2682             log = warn = error = function() {
2683                 var log = {};
2684                 var index = 0;
2685                 Array.prototype.slice.call(arguments).forEach(function(argument) {
2686                     log[index++] = toStr(argument);
2687                 });
2688                 toStr = function(str) {
2689                     return str;
2690                 };
2691                 connection.onlog(log);
2692             };
2693         }
2694
2695         function setDirections() {
2696             var userMaxParticipantsAllowed = 0;
2697
2698             // if user has set a custom max participant setting, remember it
2699             if (connection.maxParticipantsAllowed != 256) {
2700                 userMaxParticipantsAllowed = connection.maxParticipantsAllowed;
2701             }
2702
2703             if (connection.direction == 'one-way') connection.session.oneway = true;
2704             if (connection.direction == 'one-to-one') connection.maxParticipantsAllowed = 1;
2705             if (connection.direction == 'one-to-many') connection.session.broadcast = true;
2706             if (connection.direction == 'many-to-many') {
2707                 if (!connection.maxParticipantsAllowed || connection.maxParticipantsAllowed == 1) {
2708                     connection.maxParticipantsAllowed = 256;
2709                 }
2710             }
2711
2712             // if user has set a custom max participant setting, set it back
2713             if (userMaxParticipantsAllowed && connection.maxParticipantsAllowed != 1) {
2714                 connection.maxParticipantsAllowed = userMaxParticipantsAllowed;
2715             }
2716         }
2717
2718         // open new session
2719         this.initSession = function(args) {
2720             rtcMultiSession.isOwnerLeaving = false;
2721
2722             setDirections();
2723             participants = {};
2724
2725             rtcMultiSession.isOwnerLeaving = false;
2726
2727             if (!isNull(args.transmitRoomOnce)) {
2728                 connection.transmitRoomOnce = args.transmitRoomOnce;
2729             }
2730
2731             function transmit() {
2732                 if (defaultSocket && getLength(participants) < connection.maxParticipantsAllowed && !rtcMultiSession.isOwnerLeaving) {
2733                     defaultSocket.send(connection.sessionDescription);
2734                 }
2735
2736                 if (!connection.transmitRoomOnce && !rtcMultiSession.isOwnerLeaving)
2737                     setTimeout(transmit, connection.interval || 3000);
2738             }
2739
2740             // todo: test and fix next line.
2741             if (!args.dontTransmit /* || connection.transmitRoomOnce */ ) transmit();
2742         };
2743
2744         function joinSession(_config, skipOnStateChange) {
2745             if (rtcMultiSession.donotJoin && rtcMultiSession.donotJoin == _config.sessionid) {
2746                 return;
2747             }
2748
2749             // dontOverrideSession allows you force RTCMultiConnection
2750             // to not override default session for participants;
2751             // by default, session is always overridden and set to the session coming from initiator!
2752             if (!connection.dontOverrideSession) {
2753                 connection.session = _config.session || {};
2754             }
2755
2756             // make sure that inappropriate users shouldn't receive onNewSession event
2757             rtcMultiSession.broadcasterid = _config.userid;
2758
2759             if (_config.sessionid) {
2760                 // used later to prevent external rooms messages to be used by this user!
2761                 connection.sessionid = _config.sessionid;
2762             }
2763
2764             connection.isAcceptNewSession = false;
2765
2766             var channel = getRandomString();
2767             newPrivateSocket({
2768                 channel: channel,
2769                 extra: _config.extra || {},
2770                 userid: _config.userid
2771             });
2772
2773             var offers = {};
2774             if (connection.attachStreams.length) {
2775                 var stream = connection.attachStreams[connection.attachStreams.length - 1];
2776                 if (!!stream.getAudioTracks && stream.getAudioTracks().length) {
2777                     offers.audio = true;
2778                 }
2779                 if (stream.getVideoTracks().length) {
2780                     offers.video = true;
2781                 }
2782             }
2783
2784             if (!isEmpty(offers)) {
2785                 log(toStr(offers));
2786             } else log('Seems data-only connection.');
2787
2788             connection.onstatechange({
2789                 userid: _config.userid,
2790                 extra: {},
2791                 name: 'connecting-with-initiator',
2792                 reason: 'Checking presence of the initiator; and the room.'
2793             });
2794
2795             defaultSocket.send({
2796                 participant: true,
2797                 channel: channel,
2798                 targetUser: _config.userid,
2799                 session: connection.session,
2800                 offers: {
2801                     audio: !!offers.audio,
2802                     video: !!offers.video
2803                 }
2804             });
2805
2806             connection.skipOnNewSession = false;
2807             invokeMediaCaptured(connection);
2808         }
2809
2810         // join existing session
2811         this.joinSession = function(_config) {
2812             if (!defaultSocket)
2813                 return setTimeout(function() {
2814                     warn('Default-Socket is not yet initialized.');
2815                     rtcMultiSession.joinSession(_config);
2816                 }, 1000);
2817
2818             _config = _config || {};
2819             participants = {};
2820
2821             rtcMultiSession.presenceState = 'checking';
2822
2823             connection.onstatechange({
2824                 userid: _config.userid,
2825                 extra: _config.extra || {},
2826                 name: 'detecting-room-presence',
2827                 reason: 'Checking presence of the room.'
2828             });
2829
2830             function contactInitiator() {
2831                 defaultSocket.send({
2832                     messageFor: _config.userid,
2833                     presenceState: rtcMultiSession.presenceState,
2834                     _config: {
2835                         userid: _config.userid,
2836                         extra: _config.extra || {},
2837                         sessionid: _config.sessionid,
2838                         session: _config.session || false
2839                     }
2840                 });
2841             }
2842             contactInitiator();
2843
2844             function checker() {
2845                 if (rtcMultiSession.presenceState == 'checking') {
2846                     warn('Unable to reach initiator. Trying again...');
2847                     contactInitiator();
2848                     setTimeout(function() {
2849                         if (rtcMultiSession.presenceState == 'checking') {
2850                             connection.onstatechange({
2851                                 userid: _config.userid,
2852                                 extra: _config.extra || {},
2853                                 name: 'room-not-available',
2854                                 reason: 'Initiator seems absent. Waiting for someone to open the room.'
2855                             });
2856
2857                             connection.isAcceptNewSession = true;
2858                             setTimeout(checker, 2000);
2859                         }
2860                     }, 2000);
2861                 }
2862             }
2863
2864             setTimeout(checker, 3000);
2865         };
2866
2867         connection.donotJoin = function(sessionid) {
2868             rtcMultiSession.donotJoin = sessionid;
2869
2870             var session = connection.sessionDescriptions[sessionid];
2871             if (!session) return;
2872
2873             defaultSocket.send({
2874                 donotJoin: true,
2875                 messageFor: session.userid,
2876                 sessionid: sessionid
2877             });
2878
2879             participants = {};
2880             connection.isAcceptNewSession = true;
2881             connection.sessionid = null;
2882         };
2883
2884         // send file/data or text message
2885         this.send = function(message, _channel) {
2886             if (!(message instanceof ArrayBuffer || message instanceof DataView)) {
2887                 message = str2ab({
2888                     extra: connection.extra,
2889                     userid: connection.userid,
2890                     data: message
2891                 });
2892             }
2893
2894             if (_channel) {
2895                 if (_channel.readyState == 'open') {
2896                     _channel.send(message);
2897                 }
2898                 return;
2899             }
2900
2901             for (var dataChannel in connection.channels) {
2902                 var channel = connection.channels[dataChannel].channel;
2903                 if (channel.readyState == 'open') {
2904                     channel.send(message);
2905                 }
2906             }
2907         };
2908
2909         // leave session
2910         this.leave = function() {
2911             clearSession();
2912         };
2913
2914         // renegotiate new stream
2915         this.addStream = function(e) {
2916             var session = e.renegotiate;
2917
2918             if (!connection.renegotiatedSessions[JSON.stringify(e.renegotiate)]) {
2919                 connection.renegotiatedSessions[JSON.stringify(e.renegotiate)] = {
2920                     session: e.renegotiate,
2921                     stream: e.stream
2922                 };
2923             }
2924
2925             if (e.socket) {
2926                 if (e.socket.userid != connection.userid) {
2927                     addStream(connection.peers[e.socket.userid]);
2928                 }
2929             } else {
2930                 for (var peer in connection.peers) {
2931                     if (peer != connection.userid) {
2932                         addStream(connection.peers[peer]);
2933                     }
2934                 }
2935             }
2936
2937             function addStream(_peer) {
2938                 var socket = _peer.socket;
2939
2940                 if (!socket) {
2941                     warn(_peer, 'doesn\'t has socket.');
2942                     return;
2943                 }
2944
2945                 updateSocketForLocalStreams(socket);
2946
2947                 if (!_peer || !_peer.peer) {
2948                     throw 'No peer to renegotiate.';
2949                 }
2950
2951                 var peer = _peer.peer;
2952
2953                 if (e.stream) {
2954                     if (!peer.attachStreams) {
2955                         peer.attachStreams = [];
2956                     }
2957
2958                     peer.attachStreams.push(e.stream);
2959                 }
2960
2961                 // detaching old streams
2962                 detachMediaStream(connection.detachStreams, peer.connection);
2963
2964                 if (e.stream && (session.audio || session.video || session.screen)) {
2965                     peer.addStream(e.stream);
2966                 }
2967
2968                 peer.recreateOffer(session, function(sdp, streaminfo) {
2969                     sendsdp({
2970                         sdp: sdp,
2971                         socket: socket,
2972                         renegotiate: session,
2973                         labels: connection.detachStreams,
2974                         streaminfo: streaminfo
2975                     });
2976                     connection.detachStreams = [];
2977                 });
2978             }
2979         };
2980
2981         // www.RTCMultiConnection.org/docs/request/
2982         connection.request = function(userid, extra) {
2983             connection.captureUserMedia(function() {
2984                 // open private socket that will be used to receive offer-sdp
2985                 newPrivateSocket({
2986                     channel: connection.userid,
2987                     extra: extra || {},
2988                     userid: userid
2989                 });
2990
2991                 // ask other user to create offer-sdp
2992                 defaultSocket.send({
2993                     participant: true,
2994                     targetUser: userid
2995                 });
2996             });
2997         };
2998
2999         function acceptRequest(response) {
3000             if (!rtcMultiSession.requestsFrom) rtcMultiSession.requestsFrom = {};
3001             if (rtcMultiSession.requestsFrom[response.userid]) return;
3002
3003             var obj = {
3004                 userid: response.userid,
3005                 extra: response.extra,
3006                 channel: response.channel || response.userid,
3007                 session: response.session || connection.session
3008             };
3009
3010             // check how participant is willing to join
3011             if (response.offers) {
3012                 if (response.offers.audio && response.offers.video) {
3013                     log('target user has both audio/video streams.');
3014                 } else if (response.offers.audio && !response.offers.video) {
3015                     log('target user has only audio stream.');
3016                 } else if (!response.offers.audio && response.offers.video) {
3017                     log('target user has only video stream.');
3018                 } else {
3019                     log('target user has no stream; it seems one-way streaming or data-only connection.');
3020                 }
3021
3022                 var mandatory = connection.sdpConstraints.mandatory;
3023                 if (isNull(mandatory.OfferToReceiveAudio)) {
3024                     connection.sdpConstraints.mandatory.OfferToReceiveAudio = !!response.offers.audio;
3025                 }
3026                 if (isNull(mandatory.OfferToReceiveVideo)) {
3027                     connection.sdpConstraints.mandatory.OfferToReceiveVideo = !!response.offers.video;
3028                 }
3029
3030                 log('target user\'s SDP has?', toStr(connection.sdpConstraints.mandatory));
3031             }
3032
3033             rtcMultiSession.requestsFrom[response.userid] = obj;
3034
3035             // www.RTCMultiConnection.org/docs/onRequest/
3036             if (connection.onRequest && connection.isInitiator) {
3037                 connection.onRequest(obj);
3038             } else _accept(obj);
3039         }
3040
3041         function _accept(e) {
3042             if (rtcMultiSession.captureUserMediaOnDemand) {
3043                 rtcMultiSession.captureUserMediaOnDemand = false;
3044                 connection.captureUserMedia(function() {
3045                     _accept(e);
3046
3047                     invokeMediaCaptured(connection);
3048                 });
3049                 return;
3050             }
3051
3052             log('accepting request from', e.userid);
3053             participants[e.userid] = e.userid;
3054             newPrivateSocket({
3055                 isofferer: true,
3056                 userid: e.userid,
3057                 channel: e.channel,
3058                 extra: e.extra || {},
3059                 session: e.session || connection.session
3060             });
3061         }
3062
3063         // www.RTCMultiConnection.org/docs/accept/
3064         connection.accept = function(e) {
3065             // for backward compatibility
3066             if (arguments.length > 1 && isString(arguments[0])) {
3067                 e = {};
3068                 if (arguments[0]) e.userid = arguments[0];
3069                 if (arguments[1]) e.extra = arguments[1];
3070                 if (arguments[2]) e.channel = arguments[2];
3071             }
3072
3073             connection.captureUserMedia(function() {
3074                 _accept(e);
3075             });
3076         };
3077
3078         var isRMSDeleted = false;
3079         this.disconnect = function() {
3080             this.isOwnerLeaving = true;
3081
3082             if (!connection.keepStreamsOpened) {
3083                 for (var streamid in connection.localStreams) {
3084                     connection.localStreams[streamid].stop();
3085                 }
3086                 connection.localStreams = {};
3087
3088                 currentUserMediaRequest = {
3089                     streams: [],
3090                     mutex: false,
3091                     queueRequests: []
3092                 };
3093             }
3094
3095             if (connection.isInitiator) {
3096                 defaultSocket.send({
3097                     isDisconnectSockets: true
3098                 });
3099             }
3100
3101             connection.refresh();
3102
3103             rtcMultiSession.defaultSocket = defaultSocket = null;
3104             isRMSDeleted = true;
3105
3106             connection.ondisconnected({
3107                 userid: connection.userid,
3108                 extra: connection.extra,
3109                 peer: connection.peers[connection.userid],
3110                 isSocketsDisconnected: true
3111             });
3112
3113             // if there is any peer still opened; close it.
3114             connection.close();
3115
3116             window.removeEventListener('beforeunload', rtcMultiSession.leaveHandler);
3117             window.removeEventListener('keyup', rtcMultiSession.leaveHandler);
3118
3119             // it will not work, though :)
3120             delete this;
3121
3122             log('Disconnected your sockets, peers, streams and everything except RTCMultiConnection object.');
3123         };
3124     }
3125
3126     var webAudioMediaStreamSources = [];
3127
3128     function convertToAudioStream(mediaStream) {
3129         if (!mediaStream) throw 'MediaStream is mandatory.';
3130
3131         if (mediaStream.getVideoTracks && !mediaStream.getVideoTracks().length) {
3132             return mediaStream;
3133         }
3134
3135         var context = new AudioContext();
3136         var mediaStreamSource = context.createMediaStreamSource(mediaStream);
3137
3138         var destination = context.createMediaStreamDestination();
3139         mediaStreamSource.connect(destination);
3140
3141         webAudioMediaStreamSources.push(mediaStreamSource);
3142
3143         return destination.stream;
3144     }
3145
3146     var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
3147     var isFirefox = typeof window.InstallTrigger !== 'undefined';
3148     var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
3149     var isChrome = !!window.chrome && !isOpera;
3150     var isIE = !!document.documentMode;
3151
3152     var isPluginRTC = isSafari || isIE;
3153
3154     var isMobileDevice = !!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i);
3155
3156     // detect node-webkit
3157     var isNodeWebkit = !!(window.process && (typeof window.process == 'object') && window.process.versions && window.process.versions['node-webkit']);
3158
3159     window.MediaStream = window.MediaStream || window.webkitMediaStream;
3160     window.AudioContext = window.AudioContext || window.webkitAudioContext;
3161
3162     function getRandomString() {
3163         // suggested by @rvulpescu from #154
3164         if (window.crypto && crypto.getRandomValues && navigator.userAgent.indexOf('Safari') == -1) {
3165             var a = window.crypto.getRandomValues(new Uint32Array(3)),
3166                 token = '';
3167             for (var i = 0, l = a.length; i < l; i++) {
3168                 token += a[i].toString(36);
3169             }
3170             return token;
3171         } else {
3172             return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, '');
3173         }
3174     }
3175
3176     var chromeVersion = 50;
3177     var matchArray = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
3178     if (isChrome && matchArray && matchArray[2]) {
3179         chromeVersion = parseInt(matchArray[2], 10);
3180     }
3181
3182     var firefoxVersion = 50;
3183     matchArray = navigator.userAgent.match(/Firefox\/(.*)/);
3184     if (isFirefox && matchArray && matchArray[1]) {
3185         firefoxVersion = parseInt(matchArray[1], 10);
3186     }
3187
3188     function isData(session) {
3189         return !session.audio && !session.video && !session.screen && session.data;
3190     }
3191
3192     function isNull(obj) {
3193         return typeof obj == 'undefined';
3194     }
3195
3196     function isString(obj) {
3197         return typeof obj == 'string';
3198     }
3199
3200     function isEmpty(session) {
3201         var length = 0;
3202         for (var s in session) {
3203             length++;
3204         }
3205         return length == 0;
3206     }
3207
3208     // this method converts array-buffer into string
3209     function ab2str(buf) {
3210         var result = '';
3211         try {
3212             result = String.fromCharCode.apply(null, new Uint16Array(buf));
3213         } catch (e) {}
3214         return result;
3215     }
3216
3217     // this method converts string into array-buffer
3218     function str2ab(str) {
3219         if (!isString(str)) str = JSON.stringify(str);
3220
3221         var buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
3222         var bufView = new Uint16Array(buf);
3223         for (var i = 0, strLen = str.length; i < strLen; i++) {
3224             bufView[i] = str.charCodeAt(i);
3225         }
3226         return buf;
3227     }
3228
3229     function swap(arr) {
3230         var swapped = [],
3231             length = arr.length;
3232         for (var i = 0; i < length; i++)
3233             if (arr[i] && arr[i] !== true)
3234                 swapped.push(arr[i]);
3235         return swapped;
3236     }
3237
3238     function forEach(obj, callback) {
3239         for (var item in obj) {
3240             callback(obj[item], item);
3241         }
3242     }
3243
3244     var console = window.console || {
3245         log: function() {},
3246         error: function() {},
3247         warn: function() {}
3248     };
3249
3250     function log() {
3251         console.log(arguments);
3252     }
3253
3254     function error() {
3255         console.error(arguments);
3256     }
3257
3258     function warn() {
3259         console.warn(arguments);
3260     }
3261
3262     if (isChrome || isFirefox || isSafari) {
3263         var log = console.log.bind(console);
3264         var error = console.error.bind(console);
3265         var warn = console.warn.bind(console);
3266     }
3267
3268     function toStr(obj) {
3269         return JSON.stringify(obj, function(key, value) {
3270             if (value && value.sdp) {
3271                 log(value.sdp.type, '\t', value.sdp.sdp);
3272                 return '';
3273             } else return value;
3274         }, '\t');
3275     }
3276
3277     function getLength(obj) {
3278         var length = 0;
3279         for (var o in obj)
3280             if (o) length++;
3281         return length;
3282     }
3283
3284     // Get HTMLAudioElement/HTMLVideoElement accordingly
3285
3286     function createMediaElement(stream, session) {
3287         var mediaElement = document.createElement(stream.isAudio ? 'audio' : 'video');
3288         mediaElement.id = stream.streamid;
3289
3290         if (isPluginRTC) {
3291             var body = (document.body || document.documentElement);
3292             body.insertBefore(mediaElement, body.firstChild);
3293
3294             setTimeout(function() {
3295                 Plugin.attachMediaStream(mediaElement, stream)
3296             }, 1000);
3297
3298             return Plugin.attachMediaStream(mediaElement, stream);
3299         }
3300
3301         // "mozSrcObject" is always preferred over "src"!!
3302         mediaElement[isFirefox ? 'mozSrcObject' : 'src'] = isFirefox ? stream : (window.URL || window.webkitURL).createObjectURL(stream);
3303
3304         mediaElement.controls = true;
3305         mediaElement.autoplay = !!session.remote;
3306         mediaElement.muted = session.remote ? false : true;
3307
3308         // http://goo.gl/WZ5nFl
3309         // Firefox don't yet support onended for any stream (remote/local)
3310         isFirefox && mediaElement.addEventListener('ended', function() {
3311             stream.onended();
3312         }, false);
3313
3314         mediaElement.play();
3315
3316         return mediaElement;
3317     }
3318
3319     var onStreamEndedHandlerFiredFor = {};
3320
3321     function onStreamEndedHandler(streamedObject, connection) {
3322         if (streamedObject.mediaElement && !streamedObject.mediaElement.parentNode) return;
3323
3324         if (onStreamEndedHandlerFiredFor[streamedObject.streamid]) return;
3325         onStreamEndedHandlerFiredFor[streamedObject.streamid] = streamedObject;
3326         connection.onstreamended(streamedObject);
3327     }
3328
3329     var onLeaveHandlerFiredFor = {};
3330
3331     function onLeaveHandler(event, connection) {
3332         if (onLeaveHandlerFiredFor[event.userid]) return;
3333         onLeaveHandlerFiredFor[event.userid] = event;
3334         connection.onleave(event);
3335     }
3336
3337     function takeSnapshot(args) {
3338         var userid = args.userid;
3339         var connection = args.connection;
3340
3341         function _takeSnapshot(video) {
3342             var canvas = document.createElement('canvas');
3343             canvas.width = video.videoWidth || video.clientWidth;
3344             canvas.height = video.videoHeight || video.clientHeight;
3345
3346             var context = canvas.getContext('2d');
3347             context.drawImage(video, 0, 0, canvas.width, canvas.height);
3348
3349             connection.snapshots[userid] = canvas.toDataURL('image/png');
3350             args.callback && args.callback(connection.snapshots[userid]);
3351         }
3352
3353         if (args.mediaElement) return _takeSnapshot(args.mediaElement);
3354
3355         for (var stream in connection.streams) {
3356             stream = connection.streams[stream];
3357             if (stream.userid == userid && stream.stream && stream.stream.getVideoTracks && stream.stream.getVideoTracks().length) {
3358                 _takeSnapshot(stream.mediaElement);
3359                 continue;
3360             }
3361         }
3362     }
3363
3364     function invokeMediaCaptured(connection) {
3365         // to let user know that media resource has been captured
3366         // now, he can share "sessionDescription" using sockets
3367         if (connection.onMediaCaptured) {
3368             connection.onMediaCaptured();
3369             delete connection.onMediaCaptured;
3370         }
3371     }
3372
3373     function merge(mergein, mergeto) {
3374         if (!mergein) mergein = {};
3375         if (!mergeto) return mergein;
3376
3377         for (var item in mergeto) {
3378             mergein[item] = mergeto[item];
3379         }
3380         return mergein;
3381     }
3382
3383     function loadScript(src, onload) {
3384         var script = document.createElement('script');
3385         script.src = src;
3386         script.onload = function() {
3387             log('loaded resource:', src);
3388             if (onload) onload();
3389         };
3390         document.documentElement.appendChild(script);
3391     }
3392
3393     function capturePartOfScreen(args) {
3394         var connection = args.connection;
3395         var element = args.element;
3396
3397         if (!window.html2canvas) {
3398             return loadScript(connection.resources.html2canvas, function() {
3399                 capturePartOfScreen(args);
3400             });
3401         }
3402
3403         if (isString(element)) {
3404             element = document.querySelector(element);
3405             if (!element) element = document.getElementById(element);
3406         }
3407         if (!element) throw 'HTML DOM Element is not accessible!';
3408
3409         // todo: store DOM element somewhere to minimize DOM querying issues
3410
3411         // html2canvas.js is used to take screenshots
3412         html2canvas(element, {
3413             onrendered: function(canvas) {
3414                 args.callback(canvas.toDataURL());
3415             }
3416         });
3417     }
3418
3419     function initFileBufferReader(connection, callback) {
3420         if (!window.FileBufferReader) {
3421             loadScript(connection.resources.FileBufferReader, function() {
3422                 initFileBufferReader(connection, callback);
3423             });
3424             return;
3425         }
3426
3427         function _private(chunk) {
3428             chunk.userid = chunk.extra.userid;
3429             return chunk;
3430         }
3431
3432         var fileBufferReader = new FileBufferReader();
3433         fileBufferReader.onProgress = function(chunk) {
3434             connection.onFileProgress(_private(chunk), chunk.uuid);
3435         };
3436
3437         fileBufferReader.onBegin = function(file) {
3438             connection.onFileStart(_private(file));
3439         };
3440
3441         fileBufferReader.onEnd = function(file) {
3442             connection.onFileEnd(_private(file));
3443         };
3444
3445         callback(fileBufferReader);
3446     }
3447
3448     var screenFrame, loadedScreenFrame;
3449
3450     function loadScreenFrame(skip) {
3451         if (DetectRTC.screen.extensionid != ReservedExtensionID) {
3452             return;
3453         }
3454
3455         if (loadedScreenFrame) return;
3456         if (!skip) return loadScreenFrame(true);
3457
3458         loadedScreenFrame = true;
3459
3460         var iframe = document.createElement('iframe');
3461         iframe.onload = function() {
3462             iframe.isLoaded = true;
3463             log('Screen Capturing frame is loaded.');
3464         };
3465         //iframe.src = 'https://www.webrtc-experiment.com/getSourceId/';
3466         // CUSTOM CODE //
3467         iframe.src = 'app/fusion/scripts/webrtc/getSourceId.html';
3468         //  CUSTOM CODE //
3469         iframe.style.display = 'none';
3470         (document.body || document.documentElement).appendChild(iframe);
3471
3472         screenFrame = {
3473             postMessage: function() {
3474                 if (!iframe.isLoaded) {
3475                     setTimeout(screenFrame.postMessage, 100);
3476                     return;
3477                 }
3478                 iframe.contentWindow.postMessage({
3479                     captureSourceId: true
3480                 }, '*');
3481             }
3482         };
3483     }
3484
3485     var iceFrame, loadedIceFrame;
3486
3487     function loadIceFrame(callback, skip) {
3488         if (loadedIceFrame) return;
3489         if (!skip) return loadIceFrame(callback, true);
3490
3491         loadedIceFrame = true;
3492
3493         var iframe = document.createElement('iframe');
3494         iframe.onload = function() {
3495             iframe.isLoaded = true;
3496
3497             listenEventHandler('message', iFrameLoaderCallback);
3498
3499             function iFrameLoaderCallback(event) {
3500                 if (!event.data || !event.data.iceServers) return;
3501                 callback(event.data.iceServers);
3502
3503                 // this event listener is no more needed
3504                 window.removeEventListener('message', iFrameLoaderCallback);
3505             }
3506
3507             iframe.contentWindow.postMessage('get-ice-servers', '*');
3508         };
3509         iframe.src = 'https://cdn.webrtc-experiment.com/getIceServers/';
3510         iframe.style.display = 'none';
3511         (document.body || document.documentElement).appendChild(iframe);
3512     }
3513
3514     function muteOrUnmute(e) {
3515         var stream = e.stream,
3516             root = e.root,
3517             session = e.session || {},
3518             enabled = e.enabled;
3519
3520         if (!session.audio && !session.video) {
3521             if (!isString(session)) {
3522                 session = merge(session, {
3523                     audio: true,
3524                     video: true
3525                 });
3526             } else {
3527                 session = {
3528                     audio: true,
3529                     video: true
3530                 };
3531             }
3532         }
3533
3534         // implementation from #68
3535         if (session.type) {
3536             if (session.type == 'remote' && root.type != 'remote') return;
3537             if (session.type == 'local' && root.type != 'local') return;
3538         }
3539
3540         log(enabled ? 'Muting' : 'UnMuting', 'session', toStr(session));
3541
3542         // enable/disable audio/video tracks
3543
3544         if (root.type == 'local' && session.audio && !!stream.getAudioTracks) {
3545             var audioTracks = stream.getAudioTracks()[0];
3546             if (audioTracks)
3547                 audioTracks.enabled = !enabled;
3548         }
3549
3550         if (root.type == 'local' && (session.video || session.screen) && !!stream.getVideoTracks) {
3551             var videoTracks = stream.getVideoTracks()[0];
3552             if (videoTracks)
3553                 videoTracks.enabled = !enabled;
3554         }
3555
3556         root.sockets.forEach(function(socket) {
3557             if (root.type == 'local') {
3558                 socket.send({
3559                     streamid: root.streamid,
3560                     mute: !!enabled,
3561                     unmute: !enabled,
3562                     session: session
3563                 });
3564             }
3565
3566             if (root.type == 'remote') {
3567                 socket.send({
3568                     promptMuteUnmute: true,
3569                     streamid: root.streamid,
3570                     mute: !!enabled,
3571                     unmute: !enabled,
3572                     session: session
3573                 });
3574             }
3575         });
3576
3577         if (root.type == 'remote') return;
3578
3579         // According to issue #135, onmute/onumute must be fired for self
3580         // "fakeObject" is used because we need to keep session for renegotiated streams; 
3581         // and MUST pass exact session over onStreamEndedHandler/onmute/onhold/etc. events.
3582         var fakeObject = merge({}, root);
3583         fakeObject.session = session;
3584
3585         fakeObject.isAudio = !!fakeObject.session.audio && !fakeObject.session.video;
3586         fakeObject.isVideo = !!fakeObject.session.video;
3587         fakeObject.isScreen = !!fakeObject.session.screen;
3588
3589         if (!!enabled) {
3590             // if muted stream is negotiated
3591             stream.preMuted = {
3592                 audio: stream.getAudioTracks().length && !stream.getAudioTracks()[0].enabled,
3593                 video: stream.getVideoTracks().length && !stream.getVideoTracks()[0].enabled
3594             };
3595             root.rtcMultiConnection.onmute(fakeObject);
3596         }
3597
3598         if (!enabled) {
3599             stream.preMuted = {};
3600             root.rtcMultiConnection.onunmute(fakeObject);
3601         }
3602     }
3603
3604     var Firefox_Screen_Capturing_Warning = 'Make sure that you are using Firefox Nightly and you enabled: media.getusermedia.screensharing.enabled flag from about:config page. You also need to add your domain in "media.getusermedia.screensharing.allowed_domains" flag. If you are using WinXP then also enable "media.getusermedia.screensharing.allow_on_old_platforms" flag. NEVER forget to use "only" HTTPs for screen capturing!';
3605     var SCREEN_COMMON_FAILURE = 'HTTPs i.e. SSL-based URI is mandatory to use screen capturing.';
3606     // CUSTOM CODE //
3607     var ReservedExtensionID = 'icgmlogfeajbfdffajhoebcfbibfhaen';
3608     //var ReservedExtensionID = 'ajhifddimkapgcifgcodmmfdlknahffk';
3609     
3610     // if application-developer deployed his own extension on Google App Store
3611     var useCustomChromeExtensionForScreenCapturing = document.domain.indexOf('webrtc-experiment.com') != -1;
3612
3613     function initHark(args) {
3614         if (!window.hark) {
3615             loadScript(args.connection.resources.hark, function() {
3616                 initHark(args);
3617             });
3618             return;
3619         }
3620
3621         var connection = args.connection;
3622         var streamedObject = args.streamedObject;
3623         var stream = args.stream;
3624
3625         var options = {};
3626         var speechEvents = hark(stream, options);
3627
3628         speechEvents.on('speaking', function() {
3629             if (connection.onspeaking) {
3630                 connection.onspeaking(streamedObject);
3631             }
3632         });
3633
3634         speechEvents.on('stopped_speaking', function() {
3635             if (connection.onsilence) {
3636                 connection.onsilence(streamedObject);
3637             }
3638         });
3639
3640         speechEvents.on('volume_change', function(volume, threshold) {
3641             if (connection.onvolumechange) {
3642                 connection.onvolumechange(merge({
3643                     volume: volume,
3644                     threshold: threshold
3645                 }, streamedObject));
3646             }
3647         });
3648     }
3649
3650     attachEventListener = function(video, type, listener, useCapture) {
3651         video.addEventListener(type, listener, useCapture);
3652     };
3653
3654     var Plugin = window.PluginRTC || {};
3655     window.onPluginRTCInitialized = function(pluginRTCObject) {
3656         Plugin = pluginRTCObject;
3657         MediaStreamTrack = Plugin.MediaStreamTrack;
3658         RTCPeerConnection = Plugin.RTCPeerConnection;
3659         RTCIceCandidate = Plugin.RTCIceCandidate;
3660         RTCSessionDescription = Plugin.RTCSessionDescription;
3661
3662         log(isPluginRTC ? 'Java-Applet' : 'ActiveX', 'plugin has been loaded.');
3663     };
3664     if (!isEmpty(Plugin)) window.onPluginRTCInitialized(Plugin);
3665
3666     // if IE or Safari
3667     if (isPluginRTC) {
3668         loadScript('https://cdn.webrtc-experiment.com/Plugin.EveryWhere.js');
3669         // loadScript('https://cdn.webrtc-experiment.com/Plugin.Temasys.js');
3670     }
3671
3672     var MediaStream = window.MediaStream;
3673
3674     if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') {
3675         MediaStream = webkitMediaStream;
3676     }
3677
3678     /*global MediaStream:true */
3679     if (typeof MediaStream !== 'undefined' && !('stop' in MediaStream.prototype)) {
3680         MediaStream.prototype.stop = function() {
3681             this.getAudioTracks().forEach(function(track) {
3682                 track.stop();
3683             });
3684
3685             this.getVideoTracks().forEach(function(track) {
3686                 track.stop();
3687             });
3688         };
3689     }
3690
3691     var defaultConstraints = {
3692         mandatory: {},
3693         optional: []
3694     };
3695
3696     /* by @FreCap pull request #41 */
3697     var currentUserMediaRequest = {
3698         streams: [],
3699         mutex: false,
3700         queueRequests: []
3701     };
3702
3703     function getUserMedia(options) {
3704         if (isPluginRTC) {
3705             if (!Plugin.getUserMedia) {
3706                 setTimeout(function() {
3707                     getUserMedia(options);
3708                 }, 1000);
3709                 return;
3710             }
3711
3712             return Plugin.getUserMedia(options.constraints || {
3713                 audio: true,
3714                 video: true
3715             }, options.onsuccess, options.onerror);
3716         }
3717
3718         if (currentUserMediaRequest.mutex === true) {
3719             currentUserMediaRequest.queueRequests.push(options);
3720             return;
3721         }
3722         currentUserMediaRequest.mutex = true;
3723
3724         var connection = options.connection;
3725
3726         // tools.ietf.org/html/draft-alvestrand-constraints-resolution-00
3727         var mediaConstraints = options.mediaConstraints || {};
3728         var videoConstraints = typeof mediaConstraints.video == 'boolean' ? mediaConstraints.video : mediaConstraints.video || mediaConstraints;
3729         var audioConstraints = typeof mediaConstraints.audio == 'boolean' ? mediaConstraints.audio : mediaConstraints.audio || defaultConstraints;
3730
3731         var n = navigator;
3732         var hints = options.constraints || {
3733             audio: defaultConstraints,
3734             video: defaultConstraints
3735         };
3736
3737         if (hints.video && hints.video.mozMediaSource) {
3738             // "mozMediaSource" is redundant
3739             // need to check "mediaSource" instead.
3740             videoConstraints = {};
3741         }
3742
3743         if (hints.video == true) hints.video = defaultConstraints;
3744         if (hints.audio == true) hints.audio = defaultConstraints;
3745
3746         // connection.mediaConstraints.audio = false;
3747         if (typeof audioConstraints == 'boolean' && hints.audio) {
3748             hints.audio = audioConstraints;
3749         }
3750
3751         // connection.mediaConstraints.video = false;
3752         if (typeof videoConstraints == 'boolean' && hints.video) {
3753             hints.video = videoConstraints;
3754         }
3755
3756         // connection.mediaConstraints.audio.mandatory = {prop:true};
3757         var audioMandatoryConstraints = audioConstraints.mandatory;
3758         if (!isEmpty(audioMandatoryConstraints)) {
3759             hints.audio.mandatory = merge(hints.audio.mandatory, audioMandatoryConstraints);
3760         }
3761
3762         // connection.media.min(320,180);
3763         // connection.media.max(1920,1080);
3764         var videoMandatoryConstraints = videoConstraints.mandatory;
3765         if (videoMandatoryConstraints) {
3766             var mandatory = {};
3767
3768             if (videoMandatoryConstraints.minWidth) {
3769                 mandatory.minWidth = videoMandatoryConstraints.minWidth;
3770             }
3771
3772             if (videoMandatoryConstraints.minHeight) {
3773                 mandatory.minHeight = videoMandatoryConstraints.minHeight;
3774             }
3775
3776             if (videoMandatoryConstraints.maxWidth) {
3777                 mandatory.maxWidth = videoMandatoryConstraints.maxWidth;
3778             }
3779
3780             if (videoMandatoryConstraints.maxHeight) {
3781                 mandatory.maxHeight = videoMandatoryConstraints.maxHeight;
3782             }
3783
3784             if (videoMandatoryConstraints.minAspectRatio) {
3785                 mandatory.minAspectRatio = videoMandatoryConstraints.minAspectRatio;
3786             }
3787
3788             if (videoMandatoryConstraints.maxFrameRate) {
3789                 mandatory.maxFrameRate = videoMandatoryConstraints.maxFrameRate;
3790             }
3791
3792             if (videoMandatoryConstraints.minFrameRate) {
3793                 mandatory.minFrameRate = videoMandatoryConstraints.minFrameRate;
3794             }
3795
3796             if (mandatory.minWidth && mandatory.minHeight) {
3797                 // http://goo.gl/IZVYsj
3798                 var allowed = ['1920:1080', '1280:720', '960:720', '640:360', '640:480', '320:240', '320:180'];
3799
3800                 if (allowed.indexOf(mandatory.minWidth + ':' + mandatory.minHeight) == -1 ||
3801                     allowed.indexOf(mandatory.maxWidth + ':' + mandatory.maxHeight) == -1) {
3802                     error('The min/max width/height constraints you passed "seems" NOT supported.', toStr(mandatory));
3803                 }
3804
3805                 if (mandatory.minWidth > mandatory.maxWidth || mandatory.minHeight > mandatory.maxHeight) {
3806                     error('Minimum value must not exceed maximum value.', toStr(mandatory));
3807                 }
3808
3809                 if (mandatory.minWidth >= 1280 && mandatory.minHeight >= 720) {
3810                     warn('Enjoy HD video! min/' + mandatory.minWidth + ':' + mandatory.minHeight + ', max/' + mandatory.maxWidth + ':' + mandatory.maxHeight);
3811                 }
3812             }
3813
3814             hints.video.mandatory = merge(hints.video.mandatory, mandatory);
3815         }
3816
3817         if (videoMandatoryConstraints) {
3818             hints.video.mandatory = merge(hints.video.mandatory, videoMandatoryConstraints);
3819         }
3820
3821         // videoConstraints.optional = [{prop:true}];
3822         if (videoConstraints.optional && videoConstraints.optional instanceof Array && videoConstraints.optional.length) {
3823             hints.video.optional = hints.video.optional ? hints.video.optional.concat(videoConstraints.optional) : videoConstraints.optional;
3824         }
3825
3826         // audioConstraints.optional = [{prop:true}];
3827         if (audioConstraints.optional && audioConstraints.optional instanceof Array && audioConstraints.optional.length) {
3828             hints.audio.optional = hints.audio.optional ? hints.audio.optional.concat(audioConstraints.optional) : audioConstraints.optional;
3829         }
3830
3831         if (hints.video.mandatory && !isEmpty(hints.video.mandatory) && connection._mediaSources.video) {
3832             hints.video.optional.forEach(function(video, index) {
3833                 if (video.sourceId == connection._mediaSources.video) {
3834                     delete hints.video.optional[index];
3835                 }
3836             });
3837
3838             hints.video.optional = swap(hints.video.optional);
3839
3840             hints.video.optional.push({
3841                 sourceId: connection._mediaSources.video
3842             });
3843         }
3844
3845         if (hints.audio.mandatory && !isEmpty(hints.audio.mandatory) && connection._mediaSources.audio) {
3846             hints.audio.optional.forEach(function(audio, index) {
3847                 if (audio.sourceId == connection._mediaSources.audio) {
3848                     delete hints.audio.optional[index];
3849                 }
3850             });
3851
3852             hints.audio.optional = swap(hints.audio.optional);
3853
3854             hints.audio.optional.push({
3855                 sourceId: connection._mediaSources.audio
3856             });
3857         }
3858
3859         if (hints.video && !hints.video.mozMediaSource && hints.video.optional && hints.video.mandatory) {
3860             if (!hints.video.optional.length && isEmpty(hints.video.mandatory)) {
3861                 hints.video = true;
3862             }
3863         }
3864
3865         if (isMobileDevice) {
3866             // Android fails for some constraints
3867             // so need to force {audio:true,video:true}
3868             hints = {
3869                 audio: !!hints.audio,
3870                 video: !!hints.video
3871             };
3872         }
3873
3874         // connection.mediaConstraints always overrides constraints
3875         // passed from "captureUserMedia" function.
3876         // todo: need to verify all possible situations
3877         log('invoked getUserMedia with constraints:', toStr(hints));
3878
3879         // easy way to match 
3880         var idInstance = JSON.stringify(hints);
3881
3882         function streaming(stream, returnBack, streamid) {
3883             if (!streamid) streamid = getRandomString();
3884
3885             // localStreams object will store stream
3886             // until it is removed using native-stop method.
3887             connection.localStreams[streamid] = stream;
3888
3889             var video = options.video;
3890             if (video) {
3891                 video[isFirefox ? 'mozSrcObject' : 'src'] = isFirefox ? stream : (window.URL || window.webkitURL).createObjectURL(stream);
3892                 video.play();
3893             }
3894
3895             options.onsuccess(stream, returnBack, idInstance, streamid);
3896             currentUserMediaRequest.streams[idInstance] = {
3897                 stream: stream,
3898                 streamid: streamid
3899             };
3900             currentUserMediaRequest.mutex = false;
3901             if (currentUserMediaRequest.queueRequests.length)
3902                 getUserMedia(currentUserMediaRequest.queueRequests.shift());
3903         }
3904
3905         if (currentUserMediaRequest.streams[idInstance]) {
3906             streaming(currentUserMediaRequest.streams[idInstance].stream, true, currentUserMediaRequest.streams[idInstance].streamid);
3907         } else {
3908             n.getMedia = n.webkitGetUserMedia || n.mozGetUserMedia;
3909
3910             // http://goo.gl/eETIK4
3911             n.getMedia(hints, streaming, function(error) {
3912                 options.onerror(error, hints);
3913             });
3914         }
3915     }
3916
3917     var RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription;
3918     var RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate;
3919
3920     var RTCPeerConnection;
3921     if (typeof mozRTCPeerConnection !== 'undefined') {
3922         RTCPeerConnection = mozRTCPeerConnection;
3923     } else if (typeof webkitRTCPeerConnection !== 'undefined') {
3924         RTCPeerConnection = webkitRTCPeerConnection;
3925     } else if (typeof window.RTCPeerConnection !== 'undefined') {
3926         RTCPeerConnection = window.RTCPeerConnection;
3927     } else {
3928         console.error('WebRTC 1.0 (RTCPeerConnection) API seems NOT available in this browser.');
3929     }
3930
3931     function setSdpConstraints(config) {
3932         var sdpConstraints;
3933
3934         var sdpConstraints_mandatory = {
3935             OfferToReceiveAudio: !!config.OfferToReceiveAudio,
3936             OfferToReceiveVideo: !!config.OfferToReceiveVideo
3937         };
3938
3939         sdpConstraints = {
3940             mandatory: sdpConstraints_mandatory,
3941             optional: [{
3942                 VoiceActivityDetection: false
3943             }]
3944         };
3945
3946         if (!!navigator.mozGetUserMedia && firefoxVersion > 34) {
3947             sdpConstraints = {
3948                 OfferToReceiveAudio: !!config.OfferToReceiveAudio,
3949                 OfferToReceiveVideo: !!config.OfferToReceiveVideo
3950             };
3951         }
3952
3953         return sdpConstraints;
3954     }
3955
3956     function PeerConnection() {
3957         return {
3958             create: function(type, options) {
3959                 merge(this, options);
3960
3961                 var self = this;
3962
3963                 this.type = type;
3964                 this.init();
3965                 this.attachMediaStreams();
3966
3967                 if (isFirefox && this.session.data) {
3968                     if (this.session.data && type == 'offer') {
3969                         this.createDataChannel();
3970                     }
3971
3972                     this.getLocalDescription(type);
3973
3974                     if (this.session.data && type == 'answer') {
3975                         this.createDataChannel();
3976                     }
3977                 } else self.getLocalDescription(type);
3978
3979                 return this;
3980             },
3981             getLocalDescription: function(createType) {
3982                 log('(getLocalDescription) peer createType is', createType);
3983
3984                 if (this.session.inactive && isNull(this.rtcMultiConnection.waitUntilRemoteStreamStartsFlowing)) {
3985                     // inactive session returns blank-stream
3986                     this.rtcMultiConnection.waitUntilRemoteStreamStartsFlowing = false;
3987                 }
3988
3989                 var self = this;
3990
3991                 if (createType == 'answer') {
3992                     this.setRemoteDescription(this.offerDescription, createDescription);
3993                 } else createDescription();
3994
3995                 function createDescription() {
3996                     self.connection[createType == 'offer' ? 'createOffer' : 'createAnswer'](function(sessionDescription) {
3997                         sessionDescription.sdp = self.serializeSdp(sessionDescription.sdp, createType);
3998                         self.connection.setLocalDescription(sessionDescription);
3999
4000                         if (self.trickleIce) {
4001                             self.onSessionDescription(sessionDescription, self.streaminfo);
4002                         }
4003
4004                         if (sessionDescription.type == 'offer') {
4005                             log('offer sdp', sessionDescription.sdp);
4006                         }
4007
4008                         self.prevCreateType = createType;
4009                     }, self.onSdpError, self.constraints);
4010                 }
4011             },
4012             serializeSdp: function(sdp, createType) {
4013                 // it is "connection.processSdp=function(sdp){return sdp;}"
4014                 sdp = this.processSdp(sdp);
4015
4016                 if (isFirefox) return sdp;
4017
4018                 if (this.session.inactive && !this.holdMLine) {
4019                     this.hold = true;
4020                     if ((this.session.screen || this.session.video) && this.session.audio) {
4021                         this.holdMLine = 'both';
4022                     } else if (this.session.screen || this.session.video) {
4023                         this.holdMLine = 'video';
4024                     } else if (this.session.audio) {
4025                         this.holdMLine = 'audio';
4026                     }
4027                 }
4028
4029                 sdp = this.setBandwidth(sdp);
4030                 if (this.holdMLine == 'both') {
4031                     if (this.hold) {
4032                         this.prevSDP = sdp;
4033                         sdp = sdp.replace(/a=sendonly|a=recvonly|a=sendrecv/g, 'a=inactive');
4034                     } else if (this.prevSDP) {
4035                         if (!this.session.inactive) {
4036                             // it means that DTSL key exchange already happened for single or multiple media lines.
4037                             // this block checks, key-exchange must be happened for all media lines.
4038                             sdp = this.prevSDP;
4039
4040                             // todo: test it: makes sense?
4041                             if (chromeVersion <= 35) {
4042                                 return sdp;
4043                             }
4044                         }
4045                     }
4046                 } else if (this.holdMLine == 'audio' || this.holdMLine == 'video') {
4047                     sdp = sdp.split('m=');
4048
4049                     var audio = '';
4050                     var video = '';
4051
4052                     if (sdp[1] && sdp[1].indexOf('audio') == 0) {
4053                         audio = 'm=' + sdp[1];
4054                     }
4055                     if (sdp[2] && sdp[2].indexOf('audio') == 0) {
4056                         audio = 'm=' + sdp[2];
4057                     }
4058
4059                     if (sdp[1] && sdp[1].indexOf('video') == 0) {
4060                         video = 'm=' + sdp[1];
4061                     }
4062                     if (sdp[2] && sdp[2].indexOf('video') == 0) {
4063                         video = 'm=' + sdp[2];
4064                     }
4065
4066                     if (this.holdMLine == 'audio') {
4067                         if (this.hold) {
4068                             this.prevSDP = sdp[0] + audio + video;
4069                             sdp = sdp[0] + audio.replace(/a=sendonly|a=recvonly|a=sendrecv/g, 'a=inactive') + video;
4070                         } else if (this.prevSDP) {
4071                             sdp = this.prevSDP;
4072                         }
4073                     }
4074
4075                     if (this.holdMLine == 'video') {
4076                         if (this.hold) {
4077                             this.prevSDP = sdp[0] + audio + video;
4078                             sdp = sdp[0] + audio + video.replace(/a=sendonly|a=recvonly|a=sendrecv/g, 'a=inactive');
4079                         } else if (this.prevSDP) {
4080                             sdp = this.prevSDP;
4081                         }
4082                     }
4083                 }
4084
4085                 if (!this.hold && this.session.inactive) {
4086                     // transport.cc&l=852 - http://goo.gl/0FxxqG
4087                     // dtlstransport.h&l=234 - http://goo.gl/7E4sYF
4088                     // http://tools.ietf.org/html/rfc4340
4089
4090                     // From RFC 4145, SDP setup attribute values.
4091                     // http://goo.gl/xETJEp && http://goo.gl/3Wgcau
4092                     if (createType == 'offer') {
4093                         sdp = sdp.replace(/a=setup:passive|a=setup:active|a=setup:holdconn/g, 'a=setup:actpass');
4094                     } else {
4095                         sdp = sdp.replace(/a=setup:actpass|a=setup:passive|a=setup:holdconn/g, 'a=setup:active');
4096                     }
4097
4098                     // whilst doing handshake, either media lines were "inactive"
4099                     // or no media lines were present
4100                     sdp = sdp.replace(/a=inactive/g, 'a=sendrecv');
4101                 }
4102                 // this.session.inactive = false;
4103                 return sdp;
4104             },
4105             init: function() {
4106                 this.setConstraints();
4107                 this.connection = new RTCPeerConnection(this.iceServers, this.optionalArgument);
4108
4109                 if (this.session.data) {
4110                     log('invoked: createDataChannel');
4111                     this.createDataChannel();
4112                 }
4113
4114                 this.connection.onicecandidate = function(event) {
4115                     if (!event.candidate) {
4116                         if (!self.trickleIce) {
4117                             returnSDP();
4118                         }
4119
4120                         return;
4121                     }
4122
4123                     if (!self.trickleIce) return;
4124
4125                     self.onicecandidate(event.candidate);
4126                 };
4127
4128                 function returnSDP() {
4129                     if (self.returnedSDP) {
4130                         self.returnedSDP = false;
4131                         return;
4132                     };
4133                     self.returnedSDP = true;
4134
4135                     self.onSessionDescription(self.connection.localDescription, self.streaminfo);
4136                 }
4137
4138                 this.connection.onaddstream = function(e) {
4139                     log('onaddstream', isPluginRTC ? e.stream : toStr(e.stream));
4140
4141                     self.onaddstream(e.stream, self.session);
4142                 };
4143
4144                 this.connection.onremovestream = function(e) {
4145                     self.onremovestream(e.stream);
4146                 };
4147
4148                 this.connection.onsignalingstatechange = function() {
4149                     self.connection && self.oniceconnectionstatechange({
4150                         iceConnectionState: self.connection.iceConnectionState,
4151                         iceGatheringState: self.connection.iceGatheringState,
4152                         signalingState: self.connection.signalingState
4153                     });
4154                 };
4155
4156                 this.connection.oniceconnectionstatechange = function() {
4157                     if (!self.connection) return;
4158
4159                     self.oniceconnectionstatechange({
4160                         iceConnectionState: self.connection.iceConnectionState,
4161                         iceGatheringState: self.connection.iceGatheringState,
4162                         signalingState: self.connection.signalingState
4163                     });
4164
4165                     if (self.trickleIce) return;
4166
4167                     if (self.connection.iceGatheringState == 'complete') {
4168                         log('iceGatheringState', self.connection.iceGatheringState);
4169                         returnSDP();
4170                     }
4171                 };
4172
4173                 var self = this;
4174             },
4175             setBandwidth: function(sdp) {
4176                 if (isMobileDevice || isFirefox || !this.bandwidth) return sdp;
4177
4178                 var bandwidth = this.bandwidth;
4179
4180                 if (this.session.screen) {
4181                     if (!bandwidth.screen) {
4182                         warn('It seems that you are not using bandwidth for screen. Screen sharing is expected to fail.');
4183                     } else if (bandwidth.screen < 300) {
4184                         warn('It seems that you are using wrong bandwidth value for screen. Screen sharing is expected to fail.');
4185                     }
4186                 }
4187
4188                 // if screen; must use at least 300kbs
4189                 if (bandwidth.screen && this.session.screen) {
4190                     sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, '');
4191                     sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.screen + '\r\n');
4192                 }
4193
4194                 // remove existing bandwidth lines
4195                 if (bandwidth.audio || bandwidth.video || bandwidth.data) {
4196                     sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, '');
4197                 }
4198
4199                 if (bandwidth.audio) {
4200                     sdp = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + bandwidth.audio + '\r\n');
4201                 }
4202
4203                 if (bandwidth.video) {
4204                     sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + (this.session.screen ? bandwidth.screen : bandwidth.video) + '\r\n');
4205                 }
4206
4207                 if (bandwidth.data && !this.preferSCTP) {
4208                     sdp = sdp.replace(/a=mid:data\r\n/g, 'a=mid:data\r\nb=AS:' + bandwidth.data + '\r\n');
4209                 }
4210
4211                 return sdp;
4212             },
4213             setConstraints: function() {
4214                 var sdpConstraints = setSdpConstraints({
4215                     OfferToReceiveAudio: !!this.session.audio,
4216                     OfferToReceiveVideo: !!this.session.video || !!this.session.screen
4217                 });
4218
4219                 if (this.sdpConstraints.mandatory) {
4220                     sdpConstraints = setSdpConstraints(this.sdpConstraints.mandatory);
4221                 }
4222
4223                 this.constraints = sdpConstraints;
4224
4225                 if (this.constraints) {
4226                     log('sdp-constraints', toStr(this.constraints));
4227                 }
4228
4229                 this.optionalArgument = {
4230                     optional: this.optionalArgument.optional || [],
4231                     mandatory: this.optionalArgument.mandatory || {}
4232                 };
4233
4234                 if (!this.preferSCTP) {
4235                     this.optionalArgument.optional.push({
4236                         RtpDataChannels: true
4237                     });
4238                 }
4239
4240                 log('optional-argument', toStr(this.optionalArgument));
4241
4242                 if (!isNull(this.iceServers)) {
4243                     var iceCandidates = this.rtcMultiConnection.candidates;
4244
4245                     var stun = iceCandidates.stun;
4246                     var turn = iceCandidates.turn;
4247                     var host = iceCandidates.host;
4248
4249                     if (!isNull(iceCandidates.reflexive)) stun = iceCandidates.reflexive;
4250                     if (!isNull(iceCandidates.relay)) turn = iceCandidates.relay;
4251
4252                     if (!host && !stun && turn) {
4253                         this.rtcConfiguration.iceTransports = 'relay';
4254                     } else if (!host && !stun && !turn) {
4255                         this.rtcConfiguration.iceTransports = 'none';
4256                     }
4257
4258                     this.iceServers = {
4259                         iceServers: this.iceServers,
4260                         iceTransports: this.rtcConfiguration.iceTransports
4261                     };
4262                 } else this.iceServers = null;
4263
4264                 log('rtc-configuration', toStr(this.iceServers));
4265             },
4266             onSdpError: function(e) {
4267                 var message = toStr(e);
4268
4269                 if (message && message.indexOf('RTP/SAVPF Expects at least 4 fields') != -1) {
4270                     message = 'It seems that you are trying to interop RTP-datachannels with SCTP. It is not supported!';
4271                 }
4272                 error('onSdpError:', message);
4273             },
4274             onSdpSuccess: function() {
4275                 log('sdp success');
4276             },
4277             onMediaError: function(err) {
4278                 error(toStr(err));
4279             },
4280             setRemoteDescription: function(sessionDescription, onSdpSuccess) {
4281                 if (!sessionDescription) throw 'Remote session description should NOT be NULL.';
4282
4283                 if (!this.connection) return;
4284
4285                 log('setting remote description', sessionDescription.type, sessionDescription.sdp);
4286
4287                 var self = this;
4288                 this.connection.setRemoteDescription(
4289                     new RTCSessionDescription(sessionDescription),
4290                     onSdpSuccess || this.onSdpSuccess,
4291                     function(error) {
4292                         if (error.search(/STATE_SENTINITIATE|STATE_INPROGRESS/gi) == -1) {
4293                             self.onSdpError(error);
4294                         }
4295                     }
4296                 );
4297             },
4298             addIceCandidate: function(candidate) {
4299                 var self = this;
4300                 if (isPluginRTC) {
4301                     RTCIceCandidate(candidate, function(iceCandidate) {
4302                         onAddIceCandidate(iceCandidate);
4303                     });
4304                 } else onAddIceCandidate(new RTCIceCandidate(candidate));
4305
4306                 function onAddIceCandidate(iceCandidate) {
4307                     self.connection.addIceCandidate(iceCandidate, function() {
4308                         log('added:', candidate.sdpMid, candidate.candidate);
4309                     }, function() {
4310                         error('onIceFailure', arguments, candidate.candidate);
4311                     });
4312                 }
4313             },
4314             createDataChannel: function(channelIdentifier) {
4315                 // skip 2nd invocation of createDataChannel
4316                 if (this.channels && this.channels.length) return;
4317
4318                 var self = this;
4319
4320                 if (!this.channels) this.channels = [];
4321
4322                 // protocol: 'text/chat', preset: true, stream: 16
4323                 // maxRetransmits:0 && ordered:false && outOfOrderAllowed: false
4324                 var dataChannelDict = {};
4325
4326                 if (this.dataChannelDict) dataChannelDict = this.dataChannelDict;
4327
4328                 if (isChrome && !this.preferSCTP) {
4329                     dataChannelDict.reliable = false; // Deprecated!
4330                 }
4331
4332                 log('dataChannelDict', toStr(dataChannelDict));
4333
4334                 if (this.type == 'answer' || isFirefox) {
4335                     this.connection.ondatachannel = function(event) {
4336                         self.setChannelEvents(event.channel);
4337                     };
4338                 }
4339
4340                 if ((isChrome && this.type == 'offer') || isFirefox) {
4341                     this.setChannelEvents(
4342                         this.connection.createDataChannel(channelIdentifier || 'channel', dataChannelDict)
4343                     );
4344                 }
4345             },
4346             setChannelEvents: function(channel) {
4347                 var self = this;
4348
4349                 channel.binaryType = 'arraybuffer';
4350
4351                 if (this.dataChannelDict.binaryType) {
4352                     channel.binaryType = this.dataChannelDict.binaryType;
4353                 }
4354
4355                 channel.onmessage = function(event) {
4356                     self.onmessage(event.data);
4357                 };
4358
4359                 var numberOfTimes = 0;
4360                 channel.onopen = function() {
4361                     channel.push = channel.send;
4362                     channel.send = function(data) {
4363                         if (self.connection.iceConnectionState == 'disconnected') {
4364                             return;
4365                         }
4366
4367                         if (channel.readyState.search(/closing|closed/g) != -1) {
4368                             return;
4369                         }
4370
4371                         if (channel.readyState.search(/connecting|open/g) == -1) {
4372                             return;
4373                         }
4374
4375                         if (channel.readyState == 'connecting') {
4376                             numberOfTimes++;
4377                             return setTimeout(function() {
4378                                 if (numberOfTimes < 20) {
4379                                     channel.send(data);
4380                                 } else throw 'Number of times exceeded to wait for WebRTC data connection to be opened.';
4381                             }, 1000);
4382                         }
4383                         try {
4384                             channel.push(data);
4385                         } catch (e) {
4386                             numberOfTimes++;
4387                             warn('Data transmission failed. Re-transmitting..', numberOfTimes, toStr(e));
4388                             if (numberOfTimes >= 20) throw 'Number of times exceeded to resend data packets over WebRTC data channels.';
4389                             setTimeout(function() {
4390                                 channel.send(data);
4391                             }, 100);
4392                         }
4393                     };
4394                     self.onopen(channel);
4395                 };
4396
4397                 channel.onerror = function(event) {
4398                     self.onerror(event);
4399                 };
4400
4401                 channel.onclose = function(event) {
4402                     self.onclose(event);
4403                 };
4404
4405                 this.channels.push(channel);
4406             },
4407             addStream: function(stream) {
4408                 if (!stream.streamid && !isIE) {
4409                     stream.streamid = getRandomString();
4410                 }
4411
4412                 // todo: maybe need to add isAudio/isVideo/isScreen if missing?
4413
4414                 log('attaching stream:', stream.streamid, isPluginRTC ? stream : toStr(stream));
4415
4416                 this.connection.addStream(stream);
4417
4418                 this.sendStreamId(stream);
4419                 this.getStreamInfo();
4420             },
4421             attachMediaStreams: function() {
4422                 var streams = this.attachStreams;
4423                 for (var i = 0; i < streams.length; i++) {
4424                     this.addStream(streams[i]);
4425                 }
4426             },
4427             getStreamInfo: function() {
4428                 this.streaminfo = '';
4429                 var streams = this.connection.getLocalStreams();
4430                 for (var i = 0; i < streams.length; i++) {
4431                     if (i == 0) {
4432                         this.streaminfo = JSON.stringify({
4433                             streamid: streams[i].streamid || '',
4434                             isScreen: !!streams[i].isScreen,
4435                             isAudio: !!streams[i].isAudio,
4436                             isVideo: !!streams[i].isVideo,
4437                             preMuted: streams[i].preMuted || {}
4438                         });
4439                     } else {
4440                         this.streaminfo += '----' + JSON.stringify({
4441                             streamid: streams[i].streamid || '',
4442                             isScreen: !!streams[i].isScreen,
4443                             isAudio: !!streams[i].isAudio,
4444                             isVideo: !!streams[i].isVideo,
4445                             preMuted: streams[i].preMuted || {}
4446                         });
4447                     }
4448                 }
4449             },
4450             recreateOffer: function(renegotiate, callback) {
4451                 log('recreating offer');
4452
4453                 this.type = 'offer';
4454                 this.session = renegotiate;
4455
4456                 // todo: make sure this doesn't affect renegotiation scenarios
4457                 // this.setConstraints();
4458
4459                 this.onSessionDescription = callback;
4460                 this.getStreamInfo();
4461
4462                 // one can renegotiate data connection in existing audio/video/screen connection!
4463                 if (this.session.data) {
4464                     this.createDataChannel();
4465                 }
4466
4467                 this.getLocalDescription('offer');
4468             },
4469             recreateAnswer: function(sdp, session, callback) {
4470                 // if(isFirefox) this.create(this.type, this);
4471
4472                 log('recreating answer');
4473
4474                 this.type = 'answer';
4475                 this.session = session;
4476
4477                 // todo: make sure this doesn't affect renegotiation scenarios
4478                 // this.setConstraints();
4479
4480                 this.onSessionDescription = callback;
4481                 this.offerDescription = sdp;
4482                 this.getStreamInfo();
4483
4484                 // one can renegotiate data connection in existing audio/video/screen connection!
4485                 if (this.session.data) {
4486                     this.createDataChannel();
4487                 }
4488
4489                 this.getLocalDescription('answer');
4490             }
4491         };
4492     }
4493
4494     var FileSaver = {
4495         SaveToDisk: invokeSaveAsDialog
4496     };
4497
4498
4499     function invokeSaveAsDialog(fileUrl, fileName) {
4500         /*
4501         if (typeof navigator.msSaveOrOpenBlob !== 'undefined') {
4502             return navigator.msSaveOrOpenBlob(file, fileFullName);
4503         } else if (typeof navigator.msSaveBlob !== 'undefined') {
4504             return navigator.msSaveBlob(file, fileFullName);
4505         }
4506         */
4507
4508         var hyperlink = document.createElement('a');
4509         hyperlink.href = fileUrl;
4510         hyperlink.target = '_blank';
4511         hyperlink.download = fileName || fileUrl;
4512
4513         if (!!navigator.mozGetUserMedia) {
4514             hyperlink.onclick = function() {
4515                 (document.body || document.documentElement).removeChild(hyperlink);
4516             };
4517             (document.body || document.documentElement).appendChild(hyperlink);
4518         }
4519
4520         var evt = new MouseEvent('click', {
4521             view: window,
4522             bubbles: true,
4523             cancelable: true
4524         });
4525
4526         hyperlink.dispatchEvent(evt);
4527
4528         if (!navigator.mozGetUserMedia) {
4529             URL.revokeObjectURL(hyperlink.href);
4530         }
4531     }
4532
4533     var TextSender = {
4534         send: function(config) {
4535             var connection = config.connection;
4536
4537             if (config.text instanceof ArrayBuffer || config.text instanceof DataView) {
4538                 return config.channel.send(config.text, config._channel);
4539             }
4540
4541             var channel = config.channel,
4542                 _channel = config._channel,
4543                 initialText = config.text,
4544                 packetSize = connection.chunkSize || 1000,
4545                 textToTransfer = '',
4546                 isobject = false;
4547
4548             if (!isString(initialText)) {
4549                 isobject = true;
4550                 initialText = JSON.stringify(initialText);
4551             }
4552
4553             // uuid is used to uniquely identify sending instance
4554             var uuid = getRandomString();
4555             var sendingTime = new Date().getTime();
4556
4557             sendText(initialText);
4558
4559             function sendText(textMessage, text) {
4560                 var data = {
4561                     type: 'text',
4562                     uuid: uuid,
4563                     sendingTime: sendingTime
4564                 };
4565
4566                 if (textMessage) {
4567                     text = textMessage;
4568                     data.packets = parseInt(text.length / packetSize);
4569                 }
4570
4571                 if (text.length > packetSize)
4572                     data.message = text.slice(0, packetSize);
4573                 else {
4574                     data.message = text;
4575                     data.last = true;
4576                     data.isobject = isobject;
4577                 }
4578
4579                 channel.send(data, _channel);
4580
4581                 textToTransfer = text.slice(data.message.length);
4582
4583                 if (textToTransfer.length) {
4584                     setTimeout(function() {
4585                         sendText(null, textToTransfer);
4586                     }, connection.chunkInterval || 100);
4587                 }
4588             }
4589         }
4590     };
4591
4592     function TextReceiver(connection) {
4593         var content = {};
4594
4595         function receive(data, userid, extra) {
4596             // uuid is used to uniquely identify sending instance
4597             var uuid = data.uuid;
4598             if (!content[uuid]) content[uuid] = [];
4599
4600             content[uuid].push(data.message);
4601             if (data.last) {
4602                 var message = content[uuid].join('');
4603                 if (data.isobject) message = JSON.parse(message);
4604
4605                 // latency detection
4606                 var receivingTime = new Date().getTime();
4607                 var latency = receivingTime - data.sendingTime;
4608
4609                 var e = {
4610                     data: message,
4611                     userid: userid,
4612                     extra: extra,
4613                     latency: latency
4614                 };
4615
4616                 if (message.preRecordedMediaChunk) {
4617                     if (!connection.preRecordedMedias[message.streamerid]) {
4618                         connection.shareMediaFile(null, null, message.streamerid);
4619                     }
4620                     connection.preRecordedMedias[message.streamerid].onData(message.chunk);
4621                 } else if (connection.autoTranslateText) {
4622                     e.original = e.data;
4623                     connection.Translator.TranslateText(e.data, function(translatedText) {
4624                         e.data = translatedText;
4625                         connection.onmessage(e);
4626                     });
4627                 } else if (message.isPartOfScreen) {
4628                     connection.onpartofscreen(message);
4629                 } else connection.onmessage(e);
4630
4631                 delete content[uuid];
4632             }
4633         }
4634
4635         return {
4636             receive: receive
4637         };
4638     }
4639
4640     // Last time updated at Sep 25, 2015, 08:32:23
4641
4642     // Latest file can be found here: https://cdn.webrtc-experiment.com/DetectRTC.js
4643
4644     // Muaz Khan     - www.MuazKhan.com
4645     // MIT License   - www.WebRTC-Experiment.com/licence
4646     // Documentation - github.com/muaz-khan/DetectRTC
4647     // ____________
4648     // DetectRTC.js
4649
4650     // DetectRTC.hasWebcam (has webcam device!)
4651     // DetectRTC.hasMicrophone (has microphone device!)
4652     // DetectRTC.hasSpeakers (has speakers!)
4653
4654     (function() {
4655
4656         'use strict';
4657
4658         var navigator = window.navigator;
4659
4660         if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
4661             // Firefox 38+ seems having support of enumerateDevices
4662             // Thanks @xdumaine/enumerateDevices
4663             navigator.enumerateDevices = function(callback) {
4664                 navigator.mediaDevices.enumerateDevices().then(callback);
4665             };
4666         }
4667
4668         if (typeof navigator !== 'undefined') {
4669             if (typeof navigator.webkitGetUserMedia !== 'undefined') {
4670                 navigator.getUserMedia = navigator.webkitGetUserMedia;
4671             }
4672
4673             if (typeof navigator.mozGetUserMedia !== 'undefined') {
4674                 navigator.getUserMedia = navigator.mozGetUserMedia;
4675             }
4676         } else {
4677             navigator = {
4678                 getUserMedia: function() {}
4679             };
4680         }
4681
4682         var isMobileDevice = !!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i);
4683         var isEdge = navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveOrOpenBlob || !!navigator.msSaveBlob);
4684
4685         // this one can also be used:
4686         // https://www.websocket.org/js/stuff.js (DetectBrowser.js)
4687
4688         function getBrowserInfo() {
4689             var nVer = navigator.appVersion;
4690             var nAgt = navigator.userAgent;
4691             var browserName = navigator.appName;
4692             var fullVersion = '' + parseFloat(navigator.appVersion);
4693             var majorVersion = parseInt(navigator.appVersion, 10);
4694             var nameOffset, verOffset, ix;
4695
4696             // In Opera, the true version is after 'Opera' or after 'Version'
4697             if ((verOffset = nAgt.indexOf('Opera')) !== -1) {
4698                 browserName = 'Opera';
4699                 fullVersion = nAgt.substring(verOffset + 6);
4700
4701                 if ((verOffset = nAgt.indexOf('Version')) !== -1) {
4702                     fullVersion = nAgt.substring(verOffset + 8);
4703                 }
4704             }
4705             // In MSIE, the true version is after 'MSIE' in userAgent
4706             else if ((verOffset = nAgt.indexOf('MSIE')) !== -1) {
4707                 browserName = 'IE';
4708                 fullVersion = nAgt.substring(verOffset + 5);
4709             }
4710             // In Chrome, the true version is after 'Chrome' 
4711             else if ((verOffset = nAgt.indexOf('Chrome')) !== -1) {
4712                 browserName = 'Chrome';
4713                 fullVersion = nAgt.substring(verOffset + 7);
4714             }
4715             // In Safari, the true version is after 'Safari' or after 'Version' 
4716             else if ((verOffset = nAgt.indexOf('Safari')) !== -1) {
4717                 browserName = 'Safari';
4718                 fullVersion = nAgt.substring(verOffset + 7);
4719
4720                 if ((verOffset = nAgt.indexOf('Version')) !== -1) {
4721                     fullVersion = nAgt.substring(verOffset + 8);
4722                 }
4723             }
4724             // In Firefox, the true version is after 'Firefox' 
4725             else if ((verOffset = nAgt.indexOf('Firefox')) !== -1) {
4726                 browserName = 'Firefox';
4727                 fullVersion = nAgt.substring(verOffset + 8);
4728             }
4729
4730             // In most other browsers, 'name/version' is at the end of userAgent 
4731             else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) {
4732                 browserName = nAgt.substring(nameOffset, verOffset);
4733                 fullVersion = nAgt.substring(verOffset + 1);
4734
4735                 if (browserName.toLowerCase() === browserName.toUpperCase()) {
4736                     browserName = navigator.appName;
4737                 }
4738             }
4739
4740             if (isEdge) {
4741                 browserName = 'Edge';
4742                 // fullVersion = navigator.userAgent.split('Edge/')[1];
4743                 fullVersion = parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10);
4744             }
4745
4746             // trim the fullVersion string at semicolon/space if present
4747             if ((ix = fullVersion.indexOf(';')) !== -1) {
4748                 fullVersion = fullVersion.substring(0, ix);
4749             }
4750
4751             if ((ix = fullVersion.indexOf(' ')) !== -1) {
4752                 fullVersion = fullVersion.substring(0, ix);
4753             }
4754
4755             majorVersion = parseInt('' + fullVersion, 10);
4756
4757             if (isNaN(majorVersion)) {
4758                 fullVersion = '' + parseFloat(navigator.appVersion);
4759                 majorVersion = parseInt(navigator.appVersion, 10);
4760             }
4761
4762             return {
4763                 fullVersion: fullVersion,
4764                 version: majorVersion,
4765                 name: browserName
4766             };
4767         }
4768
4769         var isMobile = {
4770             Android: function() {
4771                 return navigator.userAgent.match(/Android/i);
4772             },
4773             BlackBerry: function() {
4774                 return navigator.userAgent.match(/BlackBerry/i);
4775             },
4776             iOS: function() {
4777                 return navigator.userAgent.match(/iPhone|iPad|iPod/i);
4778             },
4779             Opera: function() {
4780                 return navigator.userAgent.match(/Opera Mini/i);
4781             },
4782             Windows: function() {
4783                 return navigator.userAgent.match(/IEMobile/i);
4784             },
4785             any: function() {
4786                 return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows());
4787             },
4788             getOsName: function() {
4789                 var osName = 'Unknown OS';
4790                 if (isMobile.Android()) {
4791                     osName = 'Android';
4792                 }
4793
4794                 if (isMobile.BlackBerry()) {
4795                     osName = 'BlackBerry';
4796                 }
4797
4798                 if (isMobile.iOS()) {
4799                     osName = 'iOS';
4800                 }
4801
4802                 if (isMobile.Opera()) {
4803                     osName = 'Opera Mini';
4804                 }
4805
4806                 if (isMobile.Windows()) {
4807                     osName = 'Windows';
4808                 }
4809
4810                 return osName;
4811             }
4812         };
4813
4814         var osName = 'Unknown OS';
4815
4816         if (isMobile.any()) {
4817             osName = isMobile.getOsName();
4818         } else {
4819             if (navigator.appVersion.indexOf('Win') !== -1) {
4820                 osName = 'Windows';
4821             }
4822
4823             if (navigator.appVersion.indexOf('Mac') !== -1) {
4824                 osName = 'MacOS';
4825             }
4826
4827             if (navigator.appVersion.indexOf('X11') !== -1) {
4828                 osName = 'UNIX';
4829             }
4830
4831             if (navigator.appVersion.indexOf('Linux') !== -1) {
4832                 osName = 'Linux';
4833             }
4834         }
4835
4836
4837         var isCanvasSupportsStreamCapturing = false;
4838         var isVideoSupportsStreamCapturing = false;
4839         ['captureStream', 'mozCaptureStream', 'webkitCaptureStream'].forEach(function(item) {
4840             // asdf
4841             if (item in document.createElement('canvas')) {
4842                 isCanvasSupportsStreamCapturing = true;
4843             }
4844
4845             if (item in document.createElement('video')) {
4846                 isVideoSupportsStreamCapturing = true;
4847             }
4848         });
4849
4850         // via: https://github.com/diafygi/webrtc-ips
4851         function DetectLocalIPAddress(callback) {
4852             getIPs(function(ip) {
4853                 //local IPs
4854                 if (ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)) {
4855                     callback('Local: ' + ip);
4856                 }
4857
4858                 //assume the rest are public IPs
4859                 else {
4860                     callback('Public: ' + ip);
4861                 }
4862             });
4863         }
4864
4865         //get the IP addresses associated with an account
4866         function getIPs(callback) {
4867             var ipDuplicates = {};
4868
4869             //compatibility for firefox and chrome
4870             var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
4871             var useWebKit = !!window.webkitRTCPeerConnection;
4872
4873             // bypass naive webrtc blocking using an iframe
4874             if (!RTCPeerConnection) {
4875                 var iframe = document.getElementById('iframe');
4876                 if (!iframe) {
4877                     //<iframe id="iframe" sandbox="allow-same-origin" style="display: none"></iframe>
4878                     throw 'NOTE: you need to have an iframe in the page right above the script tag.';
4879                 }
4880                 var win = iframe.contentWindow;
4881                 RTCPeerConnection = win.RTCPeerConnection || win.mozRTCPeerConnection || win.webkitRTCPeerConnection;
4882                 useWebKit = !!win.webkitRTCPeerConnection;
4883             }
4884
4885             //minimal requirements for data connection
4886             var mediaConstraints = {
4887                 optional: [{
4888                     RtpDataChannels: true
4889                 }]
4890             };
4891
4892             //firefox already has a default stun server in about:config
4893             //    media.peerconnection.default_iceservers =
4894             //    [{"url": "stun:stun.services.mozilla.com"}]
4895             var servers;
4896
4897             //add same stun server for chrome
4898             if (useWebKit) {
4899                 servers = {
4900                     iceServers: [{
4901                         urls: 'stun:stun.services.mozilla.com'
4902                     }]
4903                 };
4904
4905                 if (typeof DetectRTC !== 'undefined' && DetectRTC.browser.isFirefox && DetectRTC.browser.version <= 38) {
4906                     servers[0] = {
4907                         url: servers[0].urls
4908                     };
4909                 }
4910             }
4911
4912             //construct a new RTCPeerConnection
4913             var pc = new RTCPeerConnection(servers, mediaConstraints);
4914
4915             function handleCandidate(candidate) {
4916                 //match just the IP address
4917                 var ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3})/;
4918                 var ipAddress = ipRegex.exec(candidate)[1];
4919
4920                 //remove duplicates
4921                 if (ipDuplicates[ipAddress] === undefined) {
4922                     callback(ipAddress);
4923                 }
4924
4925                 ipDuplicates[ipAddress] = true;
4926             }
4927
4928             //listen for candidate events
4929             pc.onicecandidate = function(ice) {
4930                 //skip non-candidate events
4931                 if (ice.candidate) {
4932                     handleCandidate(ice.candidate.candidate);
4933                 }
4934             };
4935
4936             //create a bogus data channel
4937             pc.createDataChannel('');
4938
4939             //create an offer sdp
4940             pc.createOffer(function(result) {
4941
4942                 //trigger the stun server request
4943                 pc.setLocalDescription(result, function() {}, function() {});
4944
4945             }, function() {});
4946
4947             //wait for a while to let everything done
4948             setTimeout(function() {
4949                 //read candidate info from local description
4950                 var lines = pc.localDescription.sdp.split('\n');
4951
4952                 lines.forEach(function(line) {
4953                     if (line.indexOf('a=candidate:') === 0) {
4954                         handleCandidate(line);
4955                     }
4956                 });
4957             }, 1000);
4958         }
4959
4960         var MediaDevices = [];
4961
4962         // ---------- Media Devices detection
4963         var canEnumerate = false;
4964
4965         /*global MediaStreamTrack:true */
4966         if (typeof MediaStreamTrack !== 'undefined' && 'getSources' in MediaStreamTrack) {
4967             canEnumerate = true;
4968         } else if (navigator.mediaDevices && !!navigator.mediaDevices.enumerateDevices) {
4969             canEnumerate = true;
4970         }
4971
4972         var hasMicrophone = canEnumerate;
4973         var hasSpeakers = canEnumerate;
4974         var hasWebcam = canEnumerate;
4975
4976         // http://dev.w3.org/2011/webrtc/editor/getusermedia.html#mediadevices
4977         // todo: switch to enumerateDevices when landed in canary.
4978         function checkDeviceSupport(callback) {
4979             // This method is useful only for Chrome!
4980
4981             if (!navigator.enumerateDevices && window.MediaStreamTrack && window.MediaStreamTrack.getSources) {
4982                 navigator.enumerateDevices = window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack);
4983             }
4984
4985             if (!navigator.enumerateDevices && navigator.enumerateDevices) {
4986                 navigator.enumerateDevices = navigator.enumerateDevices.bind(navigator);
4987             }
4988
4989             if (!navigator.enumerateDevices) {
4990                 if (callback) {
4991                     callback();
4992                 }
4993                 return;
4994             }
4995
4996             MediaDevices = [];
4997             navigator.enumerateDevices(function(devices) {
4998                 devices.forEach(function(_device) {
4999                     var device = {};
5000                     for (var d in _device) {
5001                         device[d] = _device[d];
5002                     }
5003
5004                     var skip;
5005                     MediaDevices.forEach(function(d) {
5006                         if (d.id === device.id) {
5007                             skip = true;
5008                         }
5009                     });
5010
5011                     if (skip) {
5012                         return;
5013                     }
5014
5015                     // if it is MediaStreamTrack.getSources
5016                     if (device.kind === 'audio') {
5017                         device.kind = 'audioinput';
5018                     }
5019
5020                     if (device.kind === 'video') {
5021                         device.kind = 'videoinput';
5022                     }
5023
5024                     if (!device.deviceId) {
5025                         device.deviceId = device.id;
5026                     }
5027
5028                     if (!device.id) {
5029                         device.id = device.deviceId;
5030                     }
5031
5032                     if (!device.label) {
5033                         device.label = 'Please invoke getUserMedia once.';
5034                         if (!isHTTPs) {
5035                             device.label = 'HTTPs is required to get label of this ' + device.kind + ' device.';
5036                         }
5037                     }
5038
5039                     if (device.kind === 'audioinput' || device.kind === 'audio') {
5040                         hasMicrophone = true;
5041                     }
5042
5043                     if (device.kind === 'audiooutput') {
5044                         hasSpeakers = true;
5045                     }
5046
5047                     if (device.kind === 'videoinput' || device.kind === 'video') {
5048                         hasWebcam = true;
5049                     }
5050
5051                     // there is no 'videoouput' in the spec.
5052
5053                     MediaDevices.push(device);
5054                 });
5055
5056                 if (typeof DetectRTC !== 'undefined') {
5057                     DetectRTC.MediaDevices = MediaDevices;
5058                     DetectRTC.hasMicrophone = hasMicrophone;
5059                     DetectRTC.hasSpeakers = hasSpeakers;
5060                     DetectRTC.hasWebcam = hasWebcam;
5061                 }
5062
5063                 if (callback) {
5064                     callback();
5065                 }
5066             });
5067         }
5068
5069         // check for microphone/camera support!
5070         checkDeviceSupport();
5071
5072         var DetectRTC = {};
5073         
5074         // ----------
5075         // DetectRTC.browser.name || DetectRTC.browser.version || DetectRTC.browser.fullVersion
5076         DetectRTC.browser = getBrowserInfo();
5077
5078         // DetectRTC.isChrome || DetectRTC.isFirefox || DetectRTC.isEdge
5079         DetectRTC.browser['is' + DetectRTC.browser.name] = true;
5080
5081         var isHTTPs = location.protocol === 'https:';
5082         var isNodeWebkit = !!(window.process && (typeof window.process === 'object') && window.process.versions && window.process.versions['node-webkit']);
5083
5084         // --------- Detect if system supports WebRTC 1.0 or WebRTC 1.1.
5085         var isWebRTCSupported = false;
5086         ['webkitRTCPeerConnection', 'mozRTCPeerConnection', 'RTCIceGatherer'].forEach(function(item) {
5087             if (item in window) {
5088                 isWebRTCSupported = true;
5089             }
5090         });
5091         DetectRTC.isWebRTCSupported = isWebRTCSupported;
5092
5093         //-------
5094         DetectRTC.isORTCSupported = typeof RTCIceGatherer !== 'undefined';
5095
5096         // --------- Detect if system supports screen capturing API
5097         var isScreenCapturingSupported = false;
5098         if (DetectRTC.browser.isChrome && DetectRTC.browser.version >= 35) {
5099             isScreenCapturingSupported = true;
5100         } else if (DetectRTC.browser.isFirefox && DetectRTC.browser.version >= 34) {
5101             isScreenCapturingSupported = true;
5102         }
5103
5104         if (!isHTTPs) {
5105             isScreenCapturingSupported = false;
5106         }
5107         DetectRTC.isScreenCapturingSupported = isScreenCapturingSupported;
5108
5109         // --------- Detect if WebAudio API are supported
5110         var webAudio = {};
5111         ['AudioContext', 'webkitAudioContext', 'mozAudioContext', 'msAudioContext'].forEach(function(item) {
5112             if (webAudio.isSupported && webAudio.isCreateMediaStreamSourceSupported) {
5113                 return;
5114             }
5115             if (item in window) {
5116                 webAudio.isSupported = true;
5117
5118                 if ('createMediaStreamSource' in window[item].prototype) {
5119                     webAudio.isCreateMediaStreamSourceSupported = true;
5120                 }
5121             }
5122         });
5123         DetectRTC.isAudioContextSupported = webAudio.isSupported;
5124         DetectRTC.isCreateMediaStreamSourceSupported = webAudio.isCreateMediaStreamSourceSupported;
5125
5126         // ---------- Detect if SCTP/RTP channels are supported.
5127
5128         var isRtpDataChannelsSupported = false;
5129         if (DetectRTC.browser.isChrome && DetectRTC.browser.version > 31) {
5130             isRtpDataChannelsSupported = true;
5131         }
5132         DetectRTC.isRtpDataChannelsSupported = isRtpDataChannelsSupported;
5133
5134         var isSCTPSupportd = false;
5135         if (DetectRTC.browser.isFirefox && DetectRTC.browser.version > 28) {
5136             isSCTPSupportd = true;
5137         } else if (DetectRTC.browser.isChrome && DetectRTC.browser.version > 25) {
5138             isSCTPSupportd = true;
5139         } else if (DetectRTC.browser.isOpera && DetectRTC.browser.version >= 11) {
5140             isSCTPSupportd = true;
5141         }
5142         DetectRTC.isSctpDataChannelsSupported = isSCTPSupportd;
5143
5144         // ---------
5145
5146         DetectRTC.isMobileDevice = isMobileDevice; // "isMobileDevice" boolean is defined in "getBrowserInfo.js"
5147
5148         // ------
5149
5150         DetectRTC.isWebSocketsSupported = 'WebSocket' in window && 2 === window.WebSocket.CLOSING;
5151         DetectRTC.isWebSocketsBlocked = 'Checking';
5152
5153         if (DetectRTC.isWebSocketsSupported) {
5154                 /* CUSTOM CODE */
5155                 var href = window.location.href;
5156             var hostPatt = new RegExp(window.location.host +"/[^/]*");
5157             var res = hostPatt.exec(href);
5158             var protocol = window.location.protocol.replace("http","ws");
5159             
5160             var signalingServerPath = protocol + "//" + res + "/contact"; 
5161             var websocket = new WebSocket(signalingServerPath)
5162                 // var websocket = new WebSocket('wss://echo.websocket.org:443/');
5163             /* CUSTOM CODE */
5164             websocket.onopen = function() {
5165                 DetectRTC.isWebSocketsBlocked = false;
5166
5167                 if (DetectRTC.loadCallback) {
5168                     DetectRTC.loadCallback();
5169                 }
5170             };
5171             websocket.onerror = function() {
5172                 DetectRTC.isWebSocketsBlocked = true;
5173
5174                 if (DetectRTC.loadCallback) {
5175                     DetectRTC.loadCallback();
5176                 }
5177             };
5178         }
5179
5180         // ------
5181         var isGetUserMediaSupported = false;
5182         if (navigator.getUserMedia) {
5183             isGetUserMediaSupported = true;
5184         } else if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
5185             isGetUserMediaSupported = true;
5186         }
5187         if (DetectRTC.browser.isChrome && DetectRTC.browser.version >= 47 && !isHTTPs) {
5188             DetectRTC.isGetUserMediaSupported = 'Requires HTTPs';
5189         }
5190         DetectRTC.isGetUserMediaSupported = isGetUserMediaSupported;
5191
5192         // -----------
5193         DetectRTC.osName = osName; // "osName" is defined in "detectOSName.js"
5194
5195         // ----------
5196         DetectRTC.isCanvasSupportsStreamCapturing = isCanvasSupportsStreamCapturing;
5197         DetectRTC.isVideoSupportsStreamCapturing = isVideoSupportsStreamCapturing;
5198
5199         // ------
5200         DetectRTC.DetectLocalIPAddress = DetectLocalIPAddress;
5201
5202         // -------
5203         DetectRTC.load = function(callback) {
5204             this.loadCallback = callback;
5205
5206             checkDeviceSupport(callback);
5207         };
5208
5209         DetectRTC.MediaDevices = MediaDevices;
5210         DetectRTC.hasMicrophone = hasMicrophone;
5211         DetectRTC.hasSpeakers = hasSpeakers;
5212         DetectRTC.hasWebcam = hasWebcam;
5213
5214         // ------
5215         var isSetSinkIdSupported = false;
5216         if ('setSinkId' in document.createElement('video')) {
5217             isSetSinkIdSupported = true;
5218         }
5219         DetectRTC.isSetSinkIdSupported = isSetSinkIdSupported;
5220
5221         // -----
5222         var isRTPSenderReplaceTracksSupported = false;
5223         if (DetectRTC.browser.isFirefox /*&& DetectRTC.browser.version > 39*/ ) {
5224             /*global mozRTCPeerConnection:true */
5225             if ('getSenders' in mozRTCPeerConnection.prototype) {
5226                 isRTPSenderReplaceTracksSupported = true;
5227             }
5228         } else if (DetectRTC.browser.isChrome) {
5229             /*global webkitRTCPeerConnection:true */
5230             if ('getSenders' in webkitRTCPeerConnection.prototype) {
5231                 isRTPSenderReplaceTracksSupported = true;
5232             }
5233         }
5234         DetectRTC.isRTPSenderReplaceTracksSupported = isRTPSenderReplaceTracksSupported;
5235
5236         //------
5237         var isRemoteStreamProcessingSupported = false;
5238         if (DetectRTC.browser.isFirefox && DetectRTC.browser.version > 38) {
5239             isRemoteStreamProcessingSupported = true;
5240         }
5241         DetectRTC.isRemoteStreamProcessingSupported = isRemoteStreamProcessingSupported;
5242
5243         //-------
5244         var isApplyConstraintsSupported = false;
5245
5246         /*global MediaStreamTrack:true */
5247         if (typeof MediaStreamTrack !== 'undefined' && 'applyConstraints' in MediaStreamTrack.prototype) {
5248             isApplyConstraintsSupported = true;
5249         }
5250         DetectRTC.isApplyConstraintsSupported = isApplyConstraintsSupported;
5251
5252         //-------
5253         var isMultiMonitorScreenCapturingSupported = false;
5254         if (DetectRTC.browser.isFirefox && DetectRTC.browser.version >= 43) {
5255             // version 43 merely supports platforms for multi-monitors
5256             // version 44 will support exact multi-monitor selection i.e. you can select any monitor for screen capturing.
5257             isMultiMonitorScreenCapturingSupported = true;
5258         }
5259         DetectRTC.isMultiMonitorScreenCapturingSupported = isMultiMonitorScreenCapturingSupported;
5260
5261         window.DetectRTC = DetectRTC;
5262
5263     })();
5264
5265     // DetectRTC extender
5266     var screenCallback;
5267
5268     DetectRTC.screen = {
5269         chromeMediaSource: 'screen',
5270         extensionid: ReservedExtensionID,
5271         getSourceId: function(callback) {
5272             if (!callback) throw '"callback" parameter is mandatory.';
5273
5274             // make sure that chrome extension is installed.
5275             if (!!DetectRTC.screen.status) {
5276                 onstatus(DetectRTC.screen.status);
5277             } else DetectRTC.screen.getChromeExtensionStatus(onstatus);
5278
5279             function onstatus(status) {
5280                 if (status == 'installed-enabled') {
5281                     screenCallback = callback;
5282                     window.postMessage('get-sourceId', '*');
5283                     return;
5284                 }
5285
5286                 DetectRTC.screen.chromeMediaSource = 'screen';
5287                 callback('No-Response'); // chrome extension isn't available
5288             }
5289         },
5290         onMessageCallback: function(data) {
5291             if (!(isString(data) || !!data.sourceId)) return;
5292
5293             log('chrome message', data);
5294
5295             // "cancel" button is clicked
5296             if (data == 'PermissionDeniedError') {
5297                 DetectRTC.screen.chromeMediaSource = 'PermissionDeniedError';
5298                 if (screenCallback) return screenCallback('PermissionDeniedError');
5299                 else throw new Error('PermissionDeniedError');
5300             }
5301
5302             // extension notified his presence
5303             if (data == 'rtcmulticonnection-extension-loaded') {
5304                 DetectRTC.screen.chromeMediaSource = 'desktop';
5305                 if (DetectRTC.screen.onScreenCapturingExtensionAvailable) {
5306                     DetectRTC.screen.onScreenCapturingExtensionAvailable();
5307
5308                     // make sure that this event isn't fired multiple times
5309                     DetectRTC.screen.onScreenCapturingExtensionAvailable = null;
5310                 }
5311             }
5312
5313             // extension shared temp sourceId
5314             if (data.sourceId) {
5315                 DetectRTC.screen.sourceId = data.sourceId;
5316                 if (screenCallback) screenCallback(DetectRTC.screen.sourceId);
5317             }
5318         },
5319         getChromeExtensionStatus: function(extensionid, callback) {
5320             function _callback(status) {
5321                 DetectRTC.screen.status = status;
5322                 callback(status);
5323             }
5324
5325             if (isFirefox) return _callback('not-chrome');
5326
5327             if (arguments.length != 2) {
5328                 callback = extensionid;
5329                 extensionid = this.extensionid;
5330             }
5331
5332             var image = document.createElement('img');
5333             image.src = 'chrome-extension://' + extensionid + '/icon.png';
5334             image.onload = function() {
5335                 DetectRTC.screen.chromeMediaSource = 'screen';
5336                 window.postMessage('are-you-there', '*');
5337                 setTimeout(function() {
5338                     if (DetectRTC.screen.chromeMediaSource == 'screen') {
5339                         _callback(
5340                             DetectRTC.screen.chromeMediaSource == 'desktop' ? 'installed-enabled' : 'installed-disabled' /* if chrome extension isn't permitted for current domain, then it will be installed-disabled all the time even if extension is enabled. */
5341                         );
5342                     } else _callback('installed-enabled');
5343                 }, 2000);
5344             };
5345             image.onerror = function() {
5346                 _callback('not-installed');
5347             };
5348         }
5349     };
5350
5351     // if IE
5352     if (!window.addEventListener) {
5353         window.addEventListener = function(el, eventName, eventHandler) {
5354             if (!el.attachEvent) return;
5355             el.attachEvent('on' + eventName, eventHandler);
5356         };
5357     }
5358
5359     function listenEventHandler(eventName, eventHandler) {
5360         window.removeEventListener(eventName, eventHandler);
5361         window.addEventListener(eventName, eventHandler, false);
5362     }
5363
5364     window.addEventListener('message', function(event) {
5365         if (event.origin != window.location.origin) {
5366             return;
5367         }
5368
5369         DetectRTC.screen.onMessageCallback(event.data);
5370     });
5371
5372     function setDefaults(connection) {
5373         var DetectRTC = window.DetectRTC || {};
5374
5375         // www.RTCMultiConnection.org/docs/userid/
5376         connection.userid = getRandomString();
5377
5378         // www.RTCMultiConnection.org/docs/session/
5379         connection.session = {
5380             audio: true,
5381             video: true
5382         };
5383
5384         // www.RTCMultiConnection.org/docs/maxParticipantsAllowed/
5385         connection.maxParticipantsAllowed = 256;
5386
5387         // www.RTCMultiConnection.org/docs/direction/
5388         // 'many-to-many' / 'one-to-many' / 'one-to-one' / 'one-way'
5389         connection.direction = 'many-to-many';
5390
5391         // www.RTCMultiConnection.org/docs/mediaConstraints/
5392         connection.mediaConstraints = {
5393             mandatory: {}, // kept for backward compatibility
5394             optional: [], // kept for backward compatibility
5395             audio: {
5396                 mandatory: {},
5397                 optional: []
5398             },
5399             video: {
5400                 mandatory: {},
5401                 optional: []
5402             }
5403         };
5404
5405         // www.RTCMultiConnection.org/docs/candidates/
5406         connection.candidates = {
5407             host: true,
5408             stun: true,
5409             turn: true
5410         };
5411
5412         connection.sdpConstraints = {};
5413
5414         // as @serhanters proposed in #225
5415         // it will auto fix "all" renegotiation scenarios
5416         connection.sdpConstraints.mandatory = {
5417             OfferToReceiveAudio: true,
5418             OfferToReceiveVideo: true
5419         };
5420
5421         connection.privileges = {
5422             canStopRemoteStream: false, // user can stop remote streams
5423             canMuteRemoteStream: false // user can mute remote streams
5424         };
5425
5426         connection.iceProtocols = {
5427             tcp: true,
5428             udp: true
5429         };
5430
5431         // www.RTCMultiConnection.org/docs/preferSCTP/
5432         connection.preferSCTP = isFirefox || chromeVersion >= 32 ? true : false;
5433         connection.chunkInterval = isFirefox || chromeVersion >= 32 ? 100 : 500; // 500ms for RTP and 100ms for SCTP
5434         connection.chunkSize = isFirefox || chromeVersion >= 32 ? 13 * 1000 : 1000; // 1000 chars for RTP and 13000 chars for SCTP
5435
5436         // www.RTCMultiConnection.org/docs/fakeDataChannels/
5437         connection.fakeDataChannels = false;
5438
5439         connection.waitUntilRemoteStreamStartsFlowing = null; // NULL == true
5440
5441         // auto leave on page unload
5442         connection.leaveOnPageUnload = true;
5443
5444         // get ICE-servers from XirSys
5445         connection.getExternalIceServers = isChrome;
5446
5447         // www.RTCMultiConnection.org/docs/UA/
5448         connection.UA = {
5449             isFirefox: isFirefox,
5450             isChrome: isChrome,
5451             isMobileDevice: isMobileDevice,
5452             version: isChrome ? chromeVersion : firefoxVersion,
5453             isNodeWebkit: isNodeWebkit,
5454             isSafari: isSafari,
5455             isIE: isIE,
5456             isOpera: isOpera
5457         };
5458
5459         // file queue: to store previous file objects in memory;
5460         // and stream over newly connected peers
5461         // www.RTCMultiConnection.org/docs/fileQueue/
5462         connection.fileQueue = {};
5463
5464         // this array is aimed to store all renegotiated streams' session-types
5465         connection.renegotiatedSessions = {};
5466
5467         // www.RTCMultiConnection.org/docs/channels/
5468         connection.channels = {};
5469
5470         // www.RTCMultiConnection.org/docs/extra/
5471         connection.extra = {};
5472
5473         // www.RTCMultiConnection.org/docs/bandwidth/
5474         connection.bandwidth = {
5475             screen: 300 // 300kbps (dirty workaround)
5476         };
5477
5478         // www.RTCMultiConnection.org/docs/caniuse/
5479         connection.caniuse = {
5480             RTCPeerConnection: DetectRTC.isWebRTCSupported,
5481             getUserMedia: !!navigator.webkitGetUserMedia || !!navigator.mozGetUserMedia,
5482             AudioContext: DetectRTC.isAudioContextSupported,
5483
5484             // there is no way to check whether "getUserMedia" flag is enabled or not!
5485             ScreenSharing: DetectRTC.isScreenCapturingSupported,
5486             RtpDataChannels: DetectRTC.isRtpDataChannelsSupported,
5487             SctpDataChannels: DetectRTC.isSctpDataChannelsSupported
5488         };
5489
5490         // www.RTCMultiConnection.org/docs/snapshots/
5491         connection.snapshots = {};
5492
5493         // www.WebRTC-Experiment.com/demos/MediaStreamTrack.getSources.html
5494         connection._mediaSources = {};
5495
5496         // www.RTCMultiConnection.org/docs/devices/
5497         connection.devices = {};
5498
5499         // www.RTCMultiConnection.org/docs/language/ (to see list of all supported languages)
5500         connection.language = 'en';
5501
5502         // www.RTCMultiConnection.org/docs/autoTranslateText/
5503         connection.autoTranslateText = false;
5504
5505         // please use your own Google Translate API key
5506         // Google Translate is a paid service.
5507         connection.googKey = 'AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE';
5508
5509         connection.localStreamids = [];
5510         connection.localStreams = {};
5511
5512         // this object stores pre-recorded media streaming uids
5513         // multiple pre-recorded media files can be streamed concurrently.
5514         connection.preRecordedMedias = {};
5515
5516         // www.RTCMultiConnection.org/docs/attachStreams/
5517         connection.attachStreams = [];
5518
5519         // www.RTCMultiConnection.org/docs/detachStreams/
5520         connection.detachStreams = [];
5521
5522         connection.optionalArgument = {
5523             optional: [{
5524                 DtlsSrtpKeyAgreement: true
5525             }, {
5526                 googImprovedWifiBwe: true
5527             }, {
5528                 googScreencastMinBitrate: 300
5529             }],
5530             mandatory: {}
5531         };
5532
5533         connection.dataChannelDict = {};
5534
5535         // www.RTCMultiConnection.org/docs/dontAttachStream/
5536         connection.dontAttachStream = false;
5537
5538         // www.RTCMultiConnection.org/docs/dontCaptureUserMedia/
5539         connection.dontCaptureUserMedia = false;
5540
5541         // this feature added to keep users privacy and 
5542         // make sure HTTPs pages NEVER auto capture users media
5543         // isChrome && location.protocol == 'https:'
5544         connection.preventSSLAutoAllowed = false;
5545
5546         connection.autoReDialOnFailure = true;
5547         connection.isInitiator = false;
5548
5549         // access DetectRTC.js features directly!
5550         connection.DetectRTC = DetectRTC;
5551
5552         // you can falsify it to merge all ICE in SDP and share only SDP!
5553         // such mechanism is useful for SIP/XMPP and XMLHttpRequest signaling
5554         // bug: renegotiation fails if "trickleIce" is false
5555         connection.trickleIce = true;
5556
5557         // this object stores list of all sessions in current channel
5558         connection.sessionDescriptions = {};
5559
5560         // this object stores current user's session-description
5561         // it is set only for initiator
5562         // it is set as soon as "open" method is invoked.
5563         connection.sessionDescription = null;
5564
5565         // resources used in RTCMultiConnection
5566         connection.resources = {
5567             // CUSTOM CODE //   
5568                 /*      Commenting this block as we do not wasnt external dependencies
5569                  * 
5570             RecordRTC: 'https://cdn.webrtc-experiment.com/RecordRTC.js',
5571             PreRecordedMediaStreamer: 'https://cdn.webrtc-experiment.com/PreRecordedMediaStreamer.js',
5572             customGetUserMediaBar: 'https://cdn.webrtc-experiment.com/navigator.customGetUserMediaBar.js',
5573             html2canvas: 'https://cdn.webrtc-experiment.com/screenshot.js',
5574             hark: 'https://cdn.webrtc-experiment.com/hark.js',
5575             firebase: 'https://cdn.webrtc-experiment.com/firebase.js',
5576             firebaseio: 'https://webrtc-signaling.firebaseio.com/',
5577             
5578            
5579            
5580             getConnectionStats: 'https://cdn.webrtc-experiment.com/getConnectionStats.js',
5581             FileBufferReader: 'https://cdn.webrtc-experiment.com/FileBufferReader.js'
5582             */
5583                 
5584             // CUSTOM CODE //   
5585                         
5586                         
5587         };
5588
5589         // www.RTCMultiConnection.org/docs/body/
5590         connection.body = document.body || document.documentElement;
5591         connection.screenbody = null;// document.body || document.documentElement;
5592         connection.videobody = null;//document.body || document.documentElement;
5593
5594         // www.RTCMultiConnection.org/docs/peers/
5595         connection.peers = {};
5596
5597         // www.RTCMultiConnection.org/docs/firebase/
5598         connection.firebase = 'chat';
5599
5600         connection.numberOfSessions = 0;
5601         connection.numberOfConnectedUsers = 0;
5602
5603         // by default, data-connections will always be getting
5604         // FileBufferReader.js if absent.
5605         connection.enableFileSharing = true;
5606
5607         // www.RTCMultiConnection.org/docs/autoSaveToDisk/
5608         // to make sure file-saver dialog is not invoked.
5609         connection.autoSaveToDisk = false;
5610
5611         connection.processSdp = function(sdp) {
5612             // process sdp here
5613             return sdp;
5614         };
5615
5616         // www.RTCMultiConnection.org/docs/onmessage/
5617         connection.onmessage = function(e) {
5618             log('onmessage', toStr(e));
5619         };
5620
5621         // www.RTCMultiConnection.org/docs/onopen/
5622         connection.onopen = function(e) {
5623             log('Data connection is opened between you and', e.userid);
5624         };
5625
5626         // www.RTCMultiConnection.org/docs/onerror/
5627         connection.onerror = function(e) {
5628             error(onerror, toStr(e));
5629         };
5630
5631         // www.RTCMultiConnection.org/docs/onclose/
5632         connection.onclose = function(e) {
5633             warn('onclose', toStr(e));
5634
5635             // todo: should we use "stop" or "remove"?
5636             // BTW, it is remote user!
5637             connection.streams.remove({
5638                 userid: e.userid
5639             });
5640         };
5641
5642         var progressHelper = {};
5643
5644         // www.RTCMultiConnection.org/docs/onFileStart/
5645         connection.onFileStart = function(file) {
5646             var div = document.createElement('div');
5647             div.title = file.name;
5648             div.innerHTML = '<label>0%</label> <progress></progress>';
5649             connection.body.insertBefore(div, connection.body.firstChild);
5650             progressHelper[file.uuid] = {
5651                 div: div,
5652                 progress: div.querySelector('progress'),
5653                 label: div.querySelector('label')
5654             };
5655             progressHelper[file.uuid].progress.max = file.maxChunks;
5656         };
5657
5658         // www.RTCMultiConnection.org/docs/onFileProgress/
5659         connection.onFileProgress = function(chunk) {
5660             var helper = progressHelper[chunk.uuid];
5661             if (!helper) return;
5662             helper.progress.value = chunk.currentPosition || chunk.maxChunks || helper.progress.max;
5663             updateLabel(helper.progress, helper.label);
5664         };
5665
5666         // www.RTCMultiConnection.org/docs/onFileEnd/
5667         connection.onFileEnd = function(file) {
5668             if (progressHelper[file.uuid]) progressHelper[file.uuid].div.innerHTML = '<a href="' + file.url + '" target="_blank" download="' + file.name + '">' + file.name + '</a>';
5669
5670             // for backward compatibility
5671             if (connection.onFileSent || connection.onFileReceived) {
5672                 if (connection.onFileSent) connection.onFileSent(file, file.uuid);
5673                 if (connection.onFileReceived) connection.onFileReceived(file.name, file);
5674             }
5675         };
5676
5677         function updateLabel(progress, label) {
5678             if (progress.position == -1) return;
5679             var position = +progress.position.toFixed(2).split('.')[1] || 100;
5680             label.innerHTML = position + '%';
5681         }
5682
5683         // www.RTCMultiConnection.org/docs/onstream/
5684         connection.onstream = function(e) {
5685             //  CUSTOM CODE  //        
5686         
5687                 if(e.isVideo || e.isAudio)
5688                         {
5689                             var videoTag = e.mediaElement;
5690                             var videoType = e.type;
5691                             var parentDiv = connection.videobody;
5692                             
5693                             if(videoType == "local")
5694                                 {
5695                                         videoTag.style.top = "auto";
5696                                         videoTag.style.position = "absolute";
5697                                         //videoTag.style.left = (parentDiv.offsetWidth - 160) + "px";
5698                                         videoTag.style.height = "150px";
5699                                         videoTag.style.width = "150px";
5700                                         videoTag.style.zIndex = 1;
5701                                         
5702                                 }
5703                             else if(videoType == "remote")
5704                                 {
5705                                         videoTag.style.top = "auto";
5706                                         videoTag.style.position = "absolute";
5707                                         videoTag.style.zIndex = -1;
5708                                 }
5709                                         
5710                             parentDiv.appendChild(videoTag);
5711                         }
5712                 else if(e.isScreen)
5713                {
5714                                 var screenTag = e.mediaElement;
5715                             var screenType = e.type;
5716                             var parentDiv = connection.screenbody;
5717                             
5718                             if(screenType == "local")
5719                                 {
5720                                         // no need to display this because the person sharing his/her screen doesn't have to see whats being shared 
5721                                         // enabled for demo purposes
5722                                         parentDiv.appendChild(screenTag);
5723                                 }
5724                             else if(screenType == "remote")
5725                                 {
5726                                         parentDiv.appendChild(screenTag);
5727                                 }
5728                             
5729                                 
5730                }
5731                
5732             else            
5733               connection.body.insertBefore(e.mediaElement, connection.body.firstChild);
5734               
5735             //  CUSTOM CODE  //    
5736         };
5737
5738         // www.RTCMultiConnection.org/docs/onStreamEndedHandler/
5739         connection.onstreamended = function(e) {
5740             log('onStreamEndedHandler:', e);
5741
5742             if (!e.mediaElement) {
5743                 return warn('Event.mediaElement is undefined', e);
5744             }
5745             if (!e.mediaElement.parentNode) {
5746                 e.mediaElement = document.getElementById(e.streamid);
5747
5748                 if (!e.mediaElement) {
5749                     return warn('Event.mediaElement is undefined', e);
5750                 }
5751
5752                 if (!e.mediaElement.parentNode) {
5753                     return warn('Event.mediElement.parentNode is null.', e);
5754                 }
5755             }
5756
5757             e.mediaElement.parentNode.removeChild(e.mediaElement);
5758         };
5759
5760         // todo: need to write documentation link
5761         connection.onSessionClosed = function(session) {
5762             if (session.isEjected) {
5763                 warn(session.userid, 'ejected you.');
5764             } else warn('Session has been closed.', session);
5765         };
5766
5767         // www.RTCMultiConnection.org/docs/onmute/
5768         connection.onmute = function(e) {
5769             if (e.isVideo && e.mediaElement) {
5770                 e.mediaElement.pause();
5771                 e.mediaElement.setAttribute('poster', e.snapshot || connection.resources.muted);
5772             }
5773             if (e.isAudio && e.mediaElement) {
5774                 e.mediaElement.muted = true;
5775             }
5776         };
5777
5778         // www.RTCMultiConnection.org/docs/onunmute/
5779         connection.onunmute = function(e) {
5780             if (e.isVideo && e.mediaElement) {
5781                 e.mediaElement.play();
5782                 e.mediaElement.removeAttribute('poster');
5783             }
5784             if (e.isAudio && e.mediaElement) {
5785                 e.mediaElement.muted = false;
5786             }
5787         };
5788
5789         // www.RTCMultiConnection.org/docs/onleave/
5790         connection.onleave = function(e) {
5791             log('onleave', toStr(e));
5792         };
5793
5794         connection.token = getRandomString;
5795
5796         connection.peers[connection.userid] = {
5797             drop: function() {
5798                 connection.drop();
5799             },
5800             renegotiate: function() {},
5801             addStream: function() {},
5802             hold: function() {},
5803             unhold: function() {},
5804             changeBandwidth: function() {},
5805             sharePartOfScreen: function() {}
5806         };
5807
5808         connection._skip = ['stop', 'mute', 'unmute', '_private', '_selectStreams', 'selectFirst', 'selectAll', 'remove'];
5809
5810         // www.RTCMultiConnection.org/docs/streams/
5811         connection.streams = {
5812             mute: function(session) {
5813                 this._private(session, true);
5814             },
5815             unmute: function(session) {
5816                 this._private(session, false);
5817             },
5818             _private: function(session, enabled) {
5819                 if (session && !isString(session)) {
5820                     for (var stream in this) {
5821                         if (connection._skip.indexOf(stream) == -1) {
5822                             _muteOrUnMute(this[stream], session, enabled);
5823                         }
5824                     }
5825
5826                     function _muteOrUnMute(stream, session, isMute) {
5827                         if (session.local && stream.type != 'local') return;
5828                         if (session.remote && stream.type != 'remote') return;
5829
5830                         if (session.isScreen && !stream.isScreen) return;
5831                         if (session.isAudio && !stream.isAudio) return;
5832                         if (session.isVideo && !stream.isVideo) return;
5833
5834                         if (isMute) stream.mute(session);
5835                         else stream.unmute(session);
5836                     }
5837                     return;
5838                 }
5839
5840                 // implementation from #68
5841                 for (var stream in this) {
5842                     if (connection._skip.indexOf(stream) == -1) {
5843                         this[stream]._private(session, enabled);
5844                     }
5845                 }
5846             },
5847             stop: function(type) {
5848                 var _stream;
5849                 for (var stream in this) {
5850                     if (connection._skip.indexOf(stream) == -1) {
5851                         _stream = this[stream];
5852
5853                         if (!type) _stream.stop();
5854
5855                         else if (isString(type)) {
5856                             // connection.streams.stop('screen');
5857                             var config = {};
5858                             config[type] = true;
5859                             _stopStream(_stream, config);
5860                         } else _stopStream(_stream, type);
5861                     }
5862                 }
5863
5864                 function _stopStream(_stream, config) {
5865                     // connection.streams.stop({ remote: true, userid: 'remote-userid' });
5866                     if (config.userid && _stream.userid != config.userid) return;
5867
5868                     if (config.local && _stream.type != 'local') return;
5869                     if (config.remote && _stream.type != 'remote') return;
5870
5871                     if (config.screen && !!_stream.isScreen) {
5872                         _stream.stop();
5873                     }
5874
5875                     if (config.audio && !!_stream.isAudio) {
5876                         _stream.stop();
5877                     }
5878
5879                     if (config.video && !!_stream.isVideo) {
5880                         _stream.stop();
5881                     }
5882
5883                     // connection.streams.stop('local');
5884                     if (!config.audio && !config.video && !config.screen) {
5885                         _stream.stop();
5886                     }
5887                 }
5888             },
5889             remove: function(type) {
5890                 var _stream;
5891                 for (var stream in this) {
5892                     if (connection._skip.indexOf(stream) == -1) {
5893                         _stream = this[stream];
5894
5895                         if (!type) _stopAndRemoveStream(_stream, {
5896                             local: true,
5897                             remote: true
5898                         });
5899
5900                         else if (isString(type)) {
5901                             // connection.streams.stop('screen');
5902                             var config = {};
5903                             config[type] = true;
5904                             _stopAndRemoveStream(_stream, config);
5905                         } else _stopAndRemoveStream(_stream, type);
5906                     }
5907                 }
5908
5909                 function _stopAndRemoveStream(_stream, config) {
5910                     // connection.streams.remove({ remote: true, userid: 'remote-userid' });
5911                     if (config.userid && _stream.userid != config.userid) return;
5912
5913                     if (config.local && _stream.type != 'local') return;
5914                     if (config.remote && _stream.type != 'remote') return;
5915
5916                     if (config.screen && !!_stream.isScreen) {
5917                         endStream(_stream);
5918                     }
5919
5920                     if (config.audio && !!_stream.isAudio) {
5921                         endStream(_stream);
5922                     }
5923
5924                     if (config.video && !!_stream.isVideo) {
5925                         endStream(_stream);
5926                     }
5927
5928                     // connection.streams.remove('local');
5929                     if (!config.audio && !config.video && !config.screen) {
5930                         endStream(_stream);
5931                     }
5932                 }
5933
5934                 function endStream(_stream) {
5935                     onStreamEndedHandler(_stream, connection);
5936                     delete connection.streams[_stream.streamid];
5937                 }
5938             },
5939             selectFirst: function(args) {
5940                 return this._selectStreams(args, false);
5941             },
5942             selectAll: function(args) {
5943                 return this._selectStreams(args, true);
5944             },
5945             _selectStreams: function(args, all) {
5946                 if (!args || isString(args) || isEmpty(args)) throw 'Invalid arguments.';
5947
5948                 // if userid is used then both local/remote shouldn't be auto-set
5949                 if (isNull(args.local) && isNull(args.remote) && isNull(args.userid)) {
5950                     args.local = args.remote = true;
5951                 }
5952
5953                 if (!args.isAudio && !args.isVideo && !args.isScreen) {
5954                     args.isAudio = args.isVideo = args.isScreen = true;
5955                 }
5956
5957                 var selectedStreams = [];
5958                 for (var stream in this) {
5959                     if (connection._skip.indexOf(stream) == -1 && (stream = this[stream]) && ((args.local && stream.type == 'local') || (args.remote && stream.type == 'remote') || (args.userid && stream.userid == args.userid))) {
5960                         if (args.isVideo && stream.isVideo) {
5961                             selectedStreams.push(stream);
5962                         }
5963
5964                         if (args.isAudio && stream.isAudio) {
5965                             selectedStreams.push(stream);
5966                         }
5967
5968                         if (args.isScreen && stream.isScreen) {
5969                             selectedStreams.push(stream);
5970                         }
5971                     }
5972                 }
5973
5974                 return !!all ? selectedStreams : selectedStreams[0];
5975             }
5976         };
5977
5978         var iceServers = [];
5979
5980         //  CUSTOM CODE  // 
5981         // these servers are provided by research
5982         iceServers.push({
5983             url: '' /*TODO To test this WebRTC with some open stun and turn test servers*/
5984         });
5985
5986         iceServers.push({
5987             url: 'turn:207.140.168.120:3478',
5988             credential: 'xxx',
5989             username: 'xxx'
5990         });
5991
5992         iceServers.push({
5993             url: 'turn:207.140.168.120:443?transport=tcp',
5994             credential: 'harmfulmustard',
5995             username: 'ambient'
5996         });
5997         
5998         /* CHANGED: Fusion: These are servers for testing purposes
5999
6000                  iceServers.push({
6001             url: 'stun:stun.l.google.com:19302'
6002         });
6003
6004         iceServers.push({
6005             url: 'stun:stun.anyfirewall.com:3478'
6006         });
6007
6008         iceServers.push({
6009             url: 'turn:turn.bistri.com:80',
6010             credential: 'homeo',
6011             username: 'homeo'
6012         });
6013
6014         iceServers.push({
6015             url: 'turn:turn.anyfirewall.com:443?transport=tcp',
6016             credential: 'webrtc',
6017             username: 'webrtc'
6018         });
6019
6020                 */
6021          //  CUSTOM CODE  // 
6022         connection.iceServers = iceServers;
6023
6024         connection.rtcConfiguration = {
6025             iceServers: null,
6026             iceTransports: 'all', // none || relay || all - ref: http://goo.gl/40I39K
6027             peerIdentity: false
6028         };
6029
6030         // www.RTCMultiConnection.org/docs/media/
6031         connection.media = {
6032             min: function(width, height) {
6033                 if (!connection.mediaConstraints.video) return;
6034
6035                 if (!connection.mediaConstraints.video.mandatory) {
6036                     connection.mediaConstraints.video.mandatory = {};
6037                 }
6038                 connection.mediaConstraints.video.mandatory.minWidth = width;
6039                 connection.mediaConstraints.video.mandatory.minHeight = height;
6040             },
6041             max: function(width, height) {
6042                 if (!connection.mediaConstraints.video) return;
6043
6044                 if (!connection.mediaConstraints.video.mandatory) {
6045                     connection.mediaConstraints.video.mandatory = {};
6046                 }
6047
6048                 connection.mediaConstraints.video.mandatory.maxWidth = width;
6049                 connection.mediaConstraints.video.mandatory.maxHeight = height;
6050             }
6051         };
6052
6053         connection._getStream = function(event) {
6054             var resultingObject = merge({
6055                 sockets: event.socket ? [event.socket] : []
6056             }, event);
6057
6058             resultingObject.stop = function() {
6059                 var self = this;
6060
6061                 self.sockets.forEach(function(socket) {
6062                     if (self.type == 'local') {
6063                         socket.send({
6064                             streamid: self.streamid,
6065                             stopped: true
6066                         });
6067                     }
6068
6069                     if (self.type == 'remote') {
6070                         socket.send({
6071                             promptStreamStop: true,
6072                             streamid: self.streamid
6073                         });
6074                     }
6075                 });
6076
6077                 if (self.type == 'remote') return;
6078
6079                 var stream = self.stream;
6080                 if (stream) self.rtcMultiConnection.stopMediaStream(stream);
6081             };
6082
6083             resultingObject.mute = function(session) {
6084                 this.muted = true;
6085                 this._private(session, true);
6086             };
6087
6088             resultingObject.unmute = function(session) {
6089                 this.muted = false;
6090                 this._private(session, false);
6091             };
6092
6093             function muteOrUnmuteLocally(session, isPause, mediaElement) {
6094                 if (!mediaElement) return;
6095                 var lastPauseState = mediaElement.onpause;
6096                 var lastPlayState = mediaElement.onplay;
6097                 mediaElement.onpause = mediaElement.onplay = function() {};
6098
6099                 if (isPause) mediaElement.pause();
6100                 else mediaElement.play();
6101
6102                 mediaElement.onpause = lastPauseState;
6103                 mediaElement.onplay = lastPlayState;
6104             }
6105
6106             resultingObject._private = function(session, enabled) {
6107                 if (session && !isNull(session.sync) && session.sync == false) {
6108                     muteOrUnmuteLocally(session, enabled, this.mediaElement);
6109                     return;
6110                 }
6111
6112                 muteOrUnmute({
6113                     root: this,
6114                     session: session,
6115                     enabled: enabled,
6116                     stream: this.stream
6117                 });
6118             };
6119
6120             resultingObject.startRecording = function(session) {
6121                 var self = this;
6122
6123                 if (!session) {
6124                     session = {
6125                         audio: true,
6126                         video: true
6127                     };
6128                 }
6129
6130                 if (isString(session)) {
6131                     session = {
6132                         audio: session == 'audio',
6133                         video: session == 'video'
6134                     };
6135                 }
6136
6137                 if (!window.RecordRTC) {
6138                     return loadScript(self.rtcMultiConnection.resources.RecordRTC, function() {
6139                         self.startRecording(session);
6140                     });
6141                 }
6142
6143                 log('started recording session', session);
6144
6145                 self.videoRecorder = self.audioRecorder = null;
6146
6147                 if (isFirefox) {
6148                     // firefox supports both audio/video recording in single webm file
6149                     if (session.video) {
6150                         self.videoRecorder = RecordRTC(self.stream, {
6151                             type: 'video'
6152                         });
6153                     } else if (session.audio) {
6154                         self.audioRecorder = RecordRTC(self.stream, {
6155                             type: 'audio'
6156                         });
6157                     }
6158                 } else if (isChrome) {
6159                     // chrome supports recording in two separate files: WAV and WebM
6160                     if (session.video) {
6161                         self.videoRecorder = RecordRTC(self.stream, {
6162                             type: 'video'
6163                         });
6164                     }
6165
6166                     if (session.audio) {
6167                         self.audioRecorder = RecordRTC(self.stream, {
6168                             type: 'audio'
6169                         });
6170                     }
6171                 }
6172
6173                 if (self.audioRecorder) {
6174                     self.audioRecorder.startRecording();
6175                 }
6176
6177                 if (self.videoRecorder) self.videoRecorder.startRecording();
6178             };
6179
6180             resultingObject.stopRecording = function(callback, session) {
6181                 if (!session) {
6182                     session = {
6183                         audio: true,
6184                         video: true
6185                     };
6186                 }
6187
6188                 if (isString(session)) {
6189                     session = {
6190                         audio: session == 'audio',
6191                         video: session == 'video'
6192                     };
6193                 }
6194
6195                 log('stopped recording session', session);
6196
6197                 var self = this;
6198
6199                 if (session.audio && self.audioRecorder) {
6200                     self.audioRecorder.stopRecording(function() {
6201                         if (session.video && self.videoRecorder) {
6202                             self.videoRecorder.stopRecording(function() {
6203                                 callback({
6204                                     audio: self.audioRecorder.getBlob(),
6205                                     video: self.videoRecorder.getBlob()
6206                                 });
6207                             });
6208                         } else callback({
6209                             audio: self.audioRecorder.getBlob()
6210                         });
6211                     });
6212                 } else if (session.video && self.videoRecorder) {
6213                     self.videoRecorder.stopRecording(function() {
6214                         callback({
6215                             video: self.videoRecorder.getBlob()
6216                         });
6217                     });
6218                 }
6219             };
6220
6221             resultingObject.takeSnapshot = function(callback) {
6222                 takeSnapshot({
6223                     mediaElement: this.mediaElement,
6224                     userid: this.userid,
6225                     connection: connection,
6226                     callback: callback
6227                 });
6228             };
6229
6230             // redundant: kept only for backward compatibility
6231             resultingObject.streamObject = resultingObject;
6232
6233             return resultingObject;
6234         };
6235
6236         // new RTCMultiConnection().set({properties}).connect()
6237         connection.set = function(properties) {
6238             for (var property in properties) {
6239                 this[property] = properties[property];
6240             }
6241             return this;
6242         };
6243
6244         // www.RTCMultiConnection.org/docs/onMediaError/
6245         connection.onMediaError = function(event) {
6246             error('name', event.name);
6247             error('constraintName', toStr(event.constraintName));
6248             error('message', event.message);
6249             error('original session', event.session);
6250         };
6251
6252         // www.RTCMultiConnection.org/docs/takeSnapshot/
6253         connection.takeSnapshot = function(userid, callback) {
6254             takeSnapshot({
6255                 userid: userid,
6256                 connection: connection,
6257                 callback: callback
6258             });
6259         };
6260
6261         connection.saveToDisk = function(blob, fileName) {
6262             if (blob.size && blob.type) FileSaver.SaveToDisk(URL.createObjectURL(blob), fileName || blob.name || blob.type.replace('/', '-') + blob.type.split('/')[1]);
6263             else FileSaver.SaveToDisk(blob, fileName);
6264         };
6265
6266         // www.RTCMultiConnection.org/docs/selectDevices/
6267         connection.selectDevices = function(device1, device2) {
6268             if (device1) select(this.devices[device1]);
6269             if (device2) select(this.devices[device2]);
6270
6271             function select(device) {
6272                 if (!device) return;
6273                 connection._mediaSources[device.kind] = device.id;
6274             }
6275         };
6276
6277         // www.RTCMultiConnection.org/docs/getDevices/
6278         connection.getDevices = function(callback) {
6279             // if, not yet fetched.
6280             if (!DetectRTC.MediaDevices.length) {
6281                 return setTimeout(function() {
6282                     connection.getDevices(callback);
6283                 }, 1000);
6284             }
6285
6286             // loop over all audio/video input/output devices
6287             DetectRTC.MediaDevices.forEach(function(device) {
6288                 connection.devices[device.deviceId] = device;
6289             });
6290
6291             if (callback) callback(connection.devices);
6292         };
6293
6294         connection.getMediaDevices = connection.enumerateDevices = function(callback) {
6295             if (!callback) throw 'callback is mandatory.';
6296             connection.getDevices(function() {
6297                 callback(connection.DetectRTC.MediaDevices);
6298             });
6299         };
6300
6301         // www.RTCMultiConnection.org/docs/onCustomMessage/
6302         connection.onCustomMessage = function(message) {
6303             log('Custom message', message);
6304         };
6305
6306         // www.RTCMultiConnection.org/docs/ondrop/
6307         connection.ondrop = function(droppedBy) {
6308             log('Media connection is dropped by ' + droppedBy);
6309         };
6310
6311         // www.RTCMultiConnection.org/docs/drop/
6312         connection.drop = function(config) {
6313             config = config || {};
6314             connection.attachStreams = [];
6315
6316             // "drop" should detach all local streams
6317             for (var stream in connection.streams) {
6318                 if (connection._skip.indexOf(stream) == -1) {
6319                     stream = connection.streams[stream];
6320                     if (stream.type == 'local') {
6321                         connection.detachStreams.push(stream.streamid);
6322                         onStreamEndedHandler(stream, connection);
6323                     } else onStreamEndedHandler(stream, connection);
6324                 }
6325             }
6326
6327             // www.RTCMultiConnection.org/docs/sendCustomMessage/
6328             connection.sendCustomMessage({
6329                 drop: true,
6330                 dontRenegotiate: isNull(config.renegotiate) ? true : config.renegotiate
6331             });
6332         };
6333
6334         // www.RTCMultiConnection.org/docs/Translator/
6335         connection.Translator = {
6336             TranslateText: function(text, callback) {
6337                 // if(location.protocol === 'https:') return callback(text);
6338
6339                 var newScript = document.createElement('script');
6340                 newScript.type = 'text/javascript';
6341
6342                 var sourceText = encodeURIComponent(text); // escape
6343
6344                 var randomNumber = 'method' + connection.token();
6345                 window[randomNumber] = function(response) {
6346                     if (response.data && response.data.translations[0] && callback) {
6347                         callback(response.data.translations[0].translatedText);
6348                     }
6349
6350                     if (response.error && response.error.message == 'Daily Limit Exceeded') {
6351                         warn('Text translation failed. Error message: "Daily Limit Exceeded."');
6352
6353                         // returning original text
6354                         callback(text);
6355                     }
6356                 };
6357
6358                 var source = 'https://www.googleapis.com/language/translate/v2?key=' + connection.googKey + '&target=' + (connection.language || 'en-US') + '&callback=window.' + randomNumber + '&q=' + sourceText;
6359                 newScript.src = source;
6360                 document.getElementsByTagName('head')[0].appendChild(newScript);
6361             }
6362         };
6363
6364         // you can easily override it by setting it NULL!
6365         connection.setDefaultEventsForMediaElement = function(mediaElement, streamid) {
6366             mediaElement.onpause = function() {
6367                 if (connection.streams[streamid] && !connection.streams[streamid].muted) {
6368                     connection.streams[streamid].mute();
6369                 }
6370             };
6371
6372             // todo: need to make sure that "onplay" EVENT doesn't play self-voice!
6373             mediaElement.onplay = function() {
6374                 if (connection.streams[streamid] && connection.streams[streamid].muted) {
6375                     connection.streams[streamid].unmute();
6376                 }
6377             };
6378
6379             var volumeChangeEventFired = false;
6380             mediaElement.onvolumechange = function() {
6381                 if (!volumeChangeEventFired) {
6382                     volumeChangeEventFired = true;
6383                     connection.streams[streamid] && setTimeout(function() {
6384                         var root = connection.streams[streamid];
6385                         connection.streams[streamid].sockets.forEach(function(socket) {
6386                             socket.send({
6387                                 streamid: root.streamid,
6388                                 isVolumeChanged: true,
6389                                 volume: mediaElement.volume
6390                             });
6391                         });
6392                         volumeChangeEventFired = false;
6393                     }, 2000);
6394                 }
6395             };
6396         };
6397
6398         // www.RTCMultiConnection.org/docs/onMediaFile/
6399         connection.onMediaFile = function(e) {
6400             log('onMediaFile', e);
6401             connection.body.appendChild(e.mediaElement);
6402         };
6403
6404         // www.RTCMultiConnection.org/docs/shareMediaFile/
6405         // this method handles pre-recorded media streaming
6406         connection.shareMediaFile = function(file, video, streamerid) {
6407             streamerid = streamerid || connection.token();
6408
6409             if (!PreRecordedMediaStreamer) {
6410                 loadScript(connection.resources.PreRecordedMediaStreamer, function() {
6411                     connection.shareMediaFile(file, video, streamerid);
6412                 });
6413                 return streamerid;
6414             }
6415
6416             return PreRecordedMediaStreamer.shareMediaFile({
6417                 file: file,
6418                 video: video,
6419                 streamerid: streamerid,
6420                 connection: connection
6421             });
6422         };
6423
6424         // www.RTCMultiConnection.org/docs/onpartofscreen/
6425         connection.onpartofscreen = function(e) {
6426             var image = document.createElement('img');
6427             image.src = e.screenshot;
6428             connection.body.appendChild(image);
6429         };
6430
6431         connection.skipLogs = function() {
6432             log = error = warn = function() {};
6433         };
6434
6435         // www.RTCMultiConnection.org/docs/hold/
6436         connection.hold = function(mLine) {
6437             for (var peer in connection.peers) {
6438                 connection.peers[peer].hold(mLine);
6439             }
6440         };
6441
6442         // www.RTCMultiConnection.org/docs/onhold/
6443         connection.onhold = function(track) {
6444             log('onhold', track);
6445
6446             if (track.kind != 'audio') {
6447                 track.mediaElement.pause();
6448                 track.mediaElement.setAttribute('poster', track.screenshot || connection.resources.muted);
6449             }
6450             if (track.kind == 'audio') {
6451                 track.mediaElement.muted = true;
6452             }
6453         };
6454
6455         // www.RTCMultiConnection.org/docs/unhold/
6456         connection.unhold = function(mLine) {
6457             for (var peer in connection.peers) {
6458                 connection.peers[peer].unhold(mLine);
6459             }
6460         };
6461
6462         // www.RTCMultiConnection.org/docs/onunhold/
6463         connection.onunhold = function(track) {
6464             log('onunhold', track);
6465
6466             if (track.kind != 'audio') {
6467                 track.mediaElement.play();
6468                 track.mediaElement.removeAttribute('poster');
6469             }
6470             if (track.kind != 'audio') {
6471                 track.mediaElement.muted = false;
6472             }
6473         };
6474
6475         connection.sharePartOfScreen = function(args) {
6476             var lastScreenshot = '';
6477
6478             function partOfScreenCapturer() {
6479                 // if stopped
6480                 if (connection.partOfScreen && !connection.partOfScreen.sharing) {
6481                     return;
6482                 }
6483
6484                 capturePartOfScreen({
6485                     element: args.element,
6486                     connection: connection,
6487                     callback: function(screenshot) {
6488                         // don't share repeated content
6489                         if (screenshot != lastScreenshot) {
6490                             lastScreenshot = screenshot;
6491
6492                             for (var channel in connection.channels) {
6493                                 connection.channels[channel].send({
6494                                     screenshot: screenshot,
6495                                     isPartOfScreen: true
6496                                 });
6497                             }
6498                         }
6499
6500                         // "once" can be used to share single screenshot
6501                         !args.once && setTimeout(partOfScreenCapturer, args.interval || 200);
6502                     }
6503                 });
6504             }
6505
6506             partOfScreenCapturer();
6507
6508             connection.partOfScreen = merge({
6509                 sharing: true
6510             }, args);
6511         };
6512
6513         connection.pausePartOfScreenSharing = function() {
6514             for (var peer in connection.peers) {
6515                 connection.peers[peer].pausePartOfScreenSharing = true;
6516             }
6517
6518             if (connection.partOfScreen) {
6519                 connection.partOfScreen.sharing = false;
6520             }
6521         };
6522
6523         connection.resumePartOfScreenSharing = function() {
6524             for (var peer in connection.peers) {
6525                 connection.peers[peer].pausePartOfScreenSharing = false;
6526             }
6527
6528             if (connection.partOfScreen) {
6529                 connection.partOfScreen.sharing = true;
6530             }
6531         };
6532
6533         connection.stopPartOfScreenSharing = function() {
6534             for (var peer in connection.peers) {
6535                 connection.peers[peer].stopPartOfScreenSharing = true;
6536             }
6537
6538             if (connection.partOfScreen) {
6539                 connection.partOfScreen.sharing = false;
6540             }
6541         };
6542
6543         connection.takeScreenshot = function(element, callback) {
6544             if (!element || !callback) throw 'Invalid number of arguments.';
6545
6546             if (!window.html2canvas) {
6547                 return loadScript(connection.resources.html2canvas, function() {
6548                     connection.takeScreenshot(element);
6549                 });
6550             }
6551
6552             if (isString(element)) {
6553                 element = document.querySelector(element);
6554                 if (!element) element = document.getElementById(element);
6555             }
6556             if (!element) throw 'HTML Element is inaccessible!';
6557
6558             // html2canvas.js is used to take screenshots
6559             html2canvas(element, {
6560                 onrendered: function(canvas) {
6561                     callback(canvas.toDataURL());
6562                 }
6563             });
6564         };
6565
6566         // this event is fired when RTCMultiConnection detects that chrome extension
6567         // for screen capturing is installed and available
6568         connection.onScreenCapturingExtensionAvailable = function() {
6569             log('It seems that screen capturing extension is installed and available on your system!');
6570         };
6571
6572         if (!isPluginRTC && DetectRTC.screen.onScreenCapturingExtensionAvailable) {
6573             DetectRTC.screen.onScreenCapturingExtensionAvailable = function() {
6574                 connection.onScreenCapturingExtensionAvailable();
6575             };
6576         }
6577
6578         connection.changeBandwidth = function(bandwidth) {
6579             for (var peer in connection.peers) {
6580                 connection.peers[peer].changeBandwidth(bandwidth);
6581             }
6582         };
6583
6584         connection.convertToAudioStream = function(mediaStream) {
6585             convertToAudioStream(mediaStream);
6586         };
6587
6588         connection.onstatechange = function(state) {
6589             log('on:state:change (' + state.userid + '):', state.name + ':', state.reason || '');
6590         };
6591
6592         connection.onfailed = function(event) {
6593             if (!event.peer.numOfRetries) event.peer.numOfRetries = 0;
6594             event.peer.numOfRetries++;
6595
6596             error('ICE connectivity check is failed. Renegotiating peer connection.');
6597             event.peer.numOfRetries < 2 && event.peer.renegotiate();
6598
6599             if (event.peer.numOfRetries >= 2) event.peer.numOfRetries = 0;
6600         };
6601
6602         connection.onconnected = function(event) {
6603             // event.peer.addStream || event.peer.getConnectionStats
6604             log('Peer connection has been established between you and', event.userid);
6605         };
6606
6607         connection.ondisconnected = function(event) {
6608             error('Peer connection seems has been disconnected between you and', event.userid);
6609
6610             if (isEmpty(connection.channels)) return;
6611             if (!connection.channels[event.userid]) return;
6612
6613             // use WebRTC data channels to detect user's presence
6614             connection.channels[event.userid].send({
6615                 checkingPresence: true
6616             });
6617
6618             // wait 5 seconds, if target peer didn't response, simply disconnect
6619             setTimeout(function() {
6620                 // iceConnectionState == 'disconnected' occurred out of low-bandwidth
6621                 // or internet connectivity issues
6622                 if (connection.peers[event.userid].connected) {
6623                     delete connection.peers[event.userid].connected;
6624                     return;
6625                 }
6626
6627                 // to make sure this user's all remote streams are removed.
6628                 connection.streams.remove({
6629                     remote: true,
6630                     userid: event.userid
6631                 });
6632
6633                 connection.remove(event.userid);
6634             }, 3000);
6635         };
6636
6637         connection.onstreamid = function(event) {
6638             // event.isScreen || event.isVideo || event.isAudio
6639             log('got remote streamid', event.streamid, 'from', event.userid);
6640         };
6641
6642         connection.stopMediaStream = function(mediaStream) {
6643             if (!mediaStream) throw 'MediaStream argument is mandatory.';
6644
6645             if (connection.keepStreamsOpened) {
6646                 if (mediaStream.onended) mediaStream.onended();
6647                 return;
6648             }
6649
6650             // remove stream from "localStreams" object
6651             // when native-stop method invoked.
6652             if (connection.localStreams[mediaStream.streamid]) {
6653                 delete connection.localStreams[mediaStream.streamid];
6654             }
6655
6656             if (isFirefox) {
6657                 // Firefox don't yet support onended for any stream (remote/local)
6658                 if (mediaStream.onended) mediaStream.onended();
6659             }
6660
6661             // Latest firefox does support mediaStream.getAudioTrack but doesn't support stop on MediaStreamTrack
6662             var checkForMediaStreamTrackStop = Boolean(
6663                 (mediaStream.getAudioTracks || mediaStream.getVideoTracks) && (
6664                     (mediaStream.getAudioTracks()[0] && !mediaStream.getAudioTracks()[0].stop) ||
6665                     (mediaStream.getVideoTracks()[0] && !mediaStream.getVideoTracks()[0].stop)
6666                 )
6667             );
6668
6669             if (!mediaStream.getAudioTracks || checkForMediaStreamTrackStop) {
6670                 if (mediaStream.stop) {
6671                     mediaStream.stop();
6672                 }
6673                 return;
6674             }
6675
6676             if (mediaStream.getAudioTracks().length && mediaStream.getAudioTracks()[0].stop) {
6677                 mediaStream.getAudioTracks().forEach(function(track) {
6678                     track.stop();
6679                 });
6680             }
6681
6682             if (mediaStream.getVideoTracks().length && mediaStream.getVideoTracks()[0].stop) {
6683                 mediaStream.getVideoTracks().forEach(function(track) {
6684                     track.stop();
6685                 });
6686             }
6687         };
6688
6689         connection.changeBandwidth = function(bandwidth) {
6690             if (!bandwidth || isString(bandwidth) || isEmpty(bandwidth)) {
6691                 throw 'Invalid "bandwidth" arguments.';
6692             }
6693
6694             forEach(connection.peers, function(peer) {
6695                 peer.peer.bandwidth = bandwidth;
6696             });
6697
6698             connection.renegotiate();
6699         };
6700
6701         // www.RTCMultiConnection.org/docs/openSignalingChannel/
6702         // http://goo.gl/uvoIcZ
6703
6704         // CUSTOM CODE
6705         var href = window.location.href;
6706         var hostPatt = new RegExp(window.location.host +"/[^/]*");
6707         var res = hostPatt.exec(href);
6708         var protocol = window.location.protocol.replace("http","ws");
6709         
6710         var signalingServerPath = protocol + "//" + res + "/webrtc"; 
6711         var SIGNALING_SERVER = signalingServerPath; //"ws://localhost:80/quantum/webrtc"; //"wss://localhost:80/quantum/webrtc"; --> ws for http and wss for https
6712         
6713         connection.openSignalingChannel = function(config) {
6714            
6715             
6716             config.channel = config.channel || this.channel;
6717             var websocket = new WebSocket(SIGNALING_SERVER);
6718             websocket.channel = config.channel;
6719             websocket.onopen = function() {
6720                 websocket.push(JSON.stringify({
6721                     open: true,
6722                     channel: config.channel
6723                 }));
6724                 if (config.callback)
6725                     config.callback(websocket);
6726             };
6727             websocket.onmessage = function(event) {
6728                 config.onmessage(JSON.parse(event.data));
6729             };
6730             websocket.push = websocket.send;
6731             websocket.send = function(data) {
6732                 websocket.push(JSON.stringify({
6733                     data: data,
6734                     channel: config.channel
6735                 }));
6736             };
6737             
6738             /*
6739             // make sure firebase.js is loaded
6740             if (!window.Firebase) {
6741                 return loadScript(connection.resources.firebase, function() {
6742                     connection.openSignalingChannel(config);
6743                 });
6744             }
6745
6746             var channel = config.channel || connection.channel;
6747
6748             if (connection.firebase) {
6749                 // for custom firebase instances
6750                 connection.resources.firebaseio = connection.resources.firebaseio.replace('//chat.', '//' + connection.firebase + '.');
6751             }
6752
6753             var firebase = new Firebase(connection.resources.firebaseio + channel);
6754             firebase.channel = channel;
6755             firebase.on('child_added', function(data) {
6756                 config.onmessage(data.val());
6757             });
6758
6759             firebase.send = function(data) {
6760                 // a quick dirty workaround to make sure firebase
6761                 // shouldn't fail for NULL values.
6762                 for (var prop in data) {
6763                     if (isNull(data[prop]) || typeof data[prop] == 'function') {
6764                         data[prop] = false;
6765                     }
6766                 }
6767
6768                 this.push(data);
6769             };
6770
6771             if (!connection.socket)
6772                 connection.socket = firebase;
6773
6774             firebase.onDisconnect().remove();
6775
6776             setTimeout(function() {
6777                 config.callback(firebase);
6778             }, 1);
6779             
6780             */
6781             // CUSTOM CODE //
6782             
6783         };
6784
6785         connection.Plugin = Plugin;
6786     }
6787
6788 })();