PageSourceSearch

https://lodki-volga.ru/bitrix/js/main/core/core_webrtc.js

js lodki-volga.ru collected 2026-09-26 11:48:33 UTC 24,229 bytes, 882 lines download raw bytes

1/**
2 * Class for unification and work with WebRTC commands
3 * @param params
4 * @constructor
5 */
6
7;(function (window)
8{
9	if (window.BX.webrtc) return;
10
11	var BX = window.BX;
12
13	// this is constructor, you must inherit
14	BX.webrtc = function ()
15	{
16		this.debug = false;
17		this.audioMuted = false;
18		this.videoMuted = false;
19		this.enabled = false;
20		this.detectedBrowser = 'none';
21		this.pcConfig = {};
22		this.oneway = false;
23		this.lastUserMediaParams = {};
24		this.pcConstraints = {};
25		this.sdpConstraints = {'mandatory': { 'OfferToReceiveAudio':true, 'OfferToReceiveVideo':true }};
26		this.defaultMicrophone = null;
27		this.defaultCamera = null;
28		this.defaultSpeaker = null;
29		this.enableMicAutoParameters = true;
30		this.useFallbackConstraints = false;
31
32		this.configVideo = {
33			aspectRatio: 1.7777777778
34		};
35		this.configVideoGroup = this.configVideo;
36		this.configVideoMobile = this.configVideo;
37		this.configVideoAfterError = { };
38
39		this.callStreamSelf = null;
40		this.callStreamMain = null;
41		this.callStreamUsers = {};
42
43		this.pc = {};
44		this.pcStart = {};
45		this.connected = {};
46
47		this.iceCandidates = {};
48
49		this.initiator = false;
50		this.callUserId = 0;
51		this.callChatId = 0;
52		this.callToGroup = false;
53		this.callGroupUsers = [];
54		this.callInit = false;
55		this.callInitUserId = 0;
56		this.callActive = false;
57		this.callVideo = false;
58		this.callRequestUserMedia = {};
59		this.needPeerConnection = true;
60
61		this.createAnswerTimeout = {};
62		this.initPeerConnectionTimeout = {};
63		this.iceCandidateTimeout = {};
64		this.pcConnectTimeout = {};
65
66		this.init();
67		this.setTurnServer();
68	};
69
70	// this function needed to execute, after constructor
71	BX.inheritWebrtc = function(child)
72	{
73		child.prototype = Object.create(BX.webrtc.prototype);
74		child.prototype.constructor = child;
75		child.prototype.parent = BX.webrtc.prototype;
76	};
77
78	BX.webrtc.prototype.init = function()
79	{
80		if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia)
81			this.enabled = true;
82		else
83			return;
84
85		if (navigator.userAgent.substr(navigator.userAgent.indexOf('Firefox/')+8, 2) >= 27)
86			this.detectedBrowser = 'firefox';
87		else if (navigator.appVersion.substr(navigator.appVersion.indexOf('Chrome/')+7, 2) >= 29)
88			this.detectedBrowser = 'chrome';
89	};
90
91	BX.webrtc.prototype.attachMediaStream = function(element, stream)
92	{
93		element.srcObject = stream;
94		if(BX.type.isNotEmptyString(this.defaultSpeaker) && element.setSinkId)
95		{
96			this.log('Trying to set output device: ' + this.defaultSpeaker);
97			element.setSinkId(this.defaultSpeaker);
98		}
99	};
100
101	// this is public function, don't overwrite
102	BX.webrtc.prototype.ready = function ()
103	{
104		return this.enabled;
105	};
106
107	// this is public function, don't overwrite
108	BX.webrtc.prototype.setTurnServer = function (params)
109	{
110		if (!this.ready()) return false;
111
112		params = params || {};
113
114		this.turnServer = params.turnServer || 'turn.calls.bitrix24.com';
115		this.turnServerFirefox = params.turnServerFirefox || '54.217.240.163';
116		this.turnServerLogin = params.turnServerLogin || 'bitrix';
117		this.turnServerPassword = params.turnServerPassword || 'bitrix';
118
119		if (this.detectedBrowser == 'firefox')
120		{
121			this.pcConfig = { "iceServers": [ { url:"stun:"+this.turnServerFirefox}, { url:"turn:"+this.turnServerFirefox, credential:this.turnServerPassword, username: this.turnServerLogin} ] };
122			this.pcConstraints = {"optional": [{"DtlsSrtpKeyAgreement": true}]};
123		}
124		else if (this.detectedBrowser == 'chrome')
125		{
126			this.pcConfig = { "iceServers": [ { url:"stun:"+this.turnServer}, { url:"turn:"+this.turnServerLogin+"@"+this.turnServer, username: this.turnServerLogin, credential:this.turnServerPassword} ] };
127			this.pcConstraints = {"optional": [{"DtlsSrtpKeyAgreement": true}]};
128		}
129
130		return true;
131	};
132
133	// this is public function, don't overwrite
134	BX.webrtc.prototype.toggleAudio = function(changeVariable)
135	{
136		if (!this.ready()) return false;
137
138		changeVariable = typeof(changeVariable) != 'undefined'? changeVariable: true;
139		if (changeVariable)
140			this.audioMuted = this.audioMuted? false: true;
141
142		if (this.callStreamSelf && this.callStreamSelf.getAudioTracks() && this.callStreamSelf.getAudioTracks().length>0)
143		{
144			for (var i = 0; i < this.callStreamSelf.getAudioTracks().length; i++)
145			{
146				this.callStreamSelf.getAudioTracks()[i].enabled = !this.audioMuted;
147			}
148		}
149
150		return true;
151	};
152
153	// this is public function, don't overwrite
154	BX.webrtc.prototype.toggleVideo = function(changeVariable)
155	{
156		if (!this.ready()) return false;
157
158		changeVariable = typeof(changeVariable) != 'undefined'? changeVariable: true;
159		if (changeVariable)
160			this.videoMuted = this.videoMuted? false: true;
161
162		if (this.callStreamSelf && this.callStreamSelf.getVideoTracks() && this.callStreamSelf.getVideoTracks().length>0)
163		{
164			for (var i = 0; i < this.callStreamSelf.getVideoTracks().length; i++)
165			{
166				this.callStreamSelf.getVideoTracks()[i].enabled = !this.videoMuted;
167			}
168		}
169
170		return true;
171	};
172
173	// this is public function, don't overwrite
174	BX.webrtc.prototype.signalingReady = function()
175	{
176		return (this.ready() && BX.PULL && BX.PULL.getPullServerStatus());
177	};
178
179	// this is public function, don't overwrite
180	BX.webrtc.prototype.log = function()
181	{
182		var text = '';
183		if (BX.desktop && BX.desktop.ready())
184		{
185			for (var i = 0; i < arguments.length; i++)
186			{
187				try
188				{
189					text = text+' | '+(typeof(arguments[i]) == 'object'? JSON.stringify(arguments[i]): arguments[i]);
190				}
191				catch (e)
192				{
193					text = text+' | (circular structure)';
194				}
195
196			}
197			BX.desktop.log(BX.message('USER_ID')+'.video.log', text.substr(3));
198		}
199		if (this.debug)
200		{
201			if (console) console.log('WebRTC Log', arguments);
202		}
203	};
204
205	/* UserMedia API */
206
207	// this is public function, you can inherit
208	BX.webrtc.prototype.startGetUserMedia = function(video, audio)
209	{
210		if (this.oneway && !this.initiator)
211		{
212			this.onUserMediaSuccess();
213			return true;
214		}
215
216		this.lastUserMediaParams = {video: video, audio: audio};
217
218		if (this.callRequestUserMedia[this.callVideo? 'video': 'audio'])
219			return false;
220
221		if(typeof(video) != 'undefined' && video !== true)
222		{
223			//nop
224		}
225		else
226		{
227			if(this.useFallbackConstraints)
228				video = this.configVideoAfterError;
229			else if(this.callToGroup)
230				video = this.configVideoGroup;
231			else
232				video = this.configVideo;
233		}
234
235		if(video && this.defaultCamera)
236		{
237			video.deviceId = {ideal: this.defaultCamera};
238		}
239
240		audio = typeof(audio) != 'undefined' && audio !== true? audio: {};
241		if(audio && this.defaultMicrophone)
242		{
243			audio.deviceId = {ideal: this.defaultMicrophone};
244		}
245
246		/* for unknown reason, these constraints do not work in chrome, when paired with video resolution constraints */
247		if(false && this.enableMicAutoParameters === false)
248		{
249			audio.optional = [
250				{echoCancellation:false},
251				{googEchoCancellation:false},
252				{googEchoCancellation2:false},
253				{googDAEchoCancellation:false},
254				{googAutoGainControl: false},
255				{googAutoGainControl2: false},
256				{mozAutoGainControl: false},
257				{googNoiseSuppression: false},
258				{googNoiseSuppression2: false},
259				{googHighpassFilter: false},
260				{googTypingNoiseDetection: false},
261				{googAudioMirroring: false}
262			];
263		}
264
265		var constraints = {
266			"audio": audio,
267			"video": video
268		};
269
270		this.log("Requested access to local media with constraints:  \"" + JSON.stringify(constraints) + "\"");
271
272		try {
273			this.callRequestUserMedia[this.callVideo? 'video': 'audio'] = true;
274			navigator.mediaDevices.getUserMedia(constraints).then(BX.delegate(this.onUserMediaSuccess, this), BX.delegate(this.onUserMediaError, this));
275		} catch (e) {
276			this.debug = true;
277			this.log("Method getUserMedia failed with exception: " + e.message);
278		}
279
280		return true;
281	}
282
283	// this is protected function, you must inherit
284	BX.webrtc.prototype.onUserMediaSuccess = function(stream)
285	{
286		this.log('Media stream received');
287		if(stream && stream.getTracks)
288		{
289			stream.getTracks().forEach(function(track)
290			{
291				this.log(BX.webrtc.mediaStreamTrackToString(track));
292			}.bind(this));
293		}
294
295		if (!this.oneway || this.initiator)
296		{
297			this.callRequestUserMedia[this.callVideo? 'video': 'audio'] = false;
298			if (this.callStreamSelf)
299				return false;
300
301			if (!this.callActive && stream)
302			{
303				BX.webrtc.stopMediaStream(stream);
304				return false;
305			}
306
307			this.log("User has granted access to local media.");
308
309			this.callStreamSelf = stream;
310			this.toggleAudio(false);
311		}
312
313		if (!this.needPeerConnection)
314			return true;
315
316		if (this.initiator)
317		{
318			if (this.callToGroup)
319			{
320				for (var i = 0; i < this.callGroupUsers.length; i++)
321				{
322					var userId = this.callGroupUsers[i];
323					if (userId != BX.message('USER_ID'))
324					{
325						clearTimeout(this.initPeerConnectionTimeout[userId]);
326						this.initPeerConnection(userId);
327					}
328				}
329			}
330			else
331			{
332				clearTimeout(this.initPeerConnectionTimeout[this.callUserId]);
333				this.initPeerConnection(this.callUserId);
334			}
335		}
336		else
337		{
338			if (this.callToGroup)
339			{
340				for (var i = 0; i < this.callGroupUsers.length; i++)
341				{
342					var userId = this.callGroupUsers[i];
343					if (userId != BX.message('USER_ID') && userId != this.callInitUserId && !this.callStreamUsers[userId])
344					{
345						clearTimeout(this.initPeerConnectionTimeout[userId]);
346						this.initPeerConnection(userId, true);
347					}
348				}
349			}
350		}
351		return true;
352	}
353
354	// this is protected function, you can inherit
355	BX.webrtc.prototype.onUserMediaError = function(error)
356	{
357		this.debug = true;
358		this.callRequestUserMedia[this.callVideo? 'video': 'audio'] = false;
359		if (!this.callActive)
360			return false;
361
362		this.log("Failed to get access to local media. Error code was " + JSON.stringify(error));
363
364		return true;
365	}
366
367	/* PeerConnection API */
368
369	// this is public function, don't overwrite or inherit
370	BX.webrtc.prototype.initPeerConnection = function(userId, initiator)
371	{
372		if (!this.callActive)
373			return false;
374
375		initiator = initiator === true;
376		if (this.debug) console.log(userId, 'wait initPeerConnection')
377		if (!this.pcStart[userId] && (!this.oneway && this.callStreamSelf || this.oneway && this.initiator && this.callStreamSelf || this.oneway && !this.initiator))
378		{
379			if (this.connected[userId])
380			{
381				this.log("Creating PeerConnection.", userId);
382				this.createPeerConnection(userId);
383				if (!this.pc[userId])
384				{
385					clearTimeout(this.initPeerConnectionTimeout[userId]);
386					this.initPeerConnectionTimeout[userId] = setTimeout(BX.delegate(function(){
387						this.initPeerConnection(userId, initiator);
388					}, this), 2000);
389					return false;
390				}
391
392				this.log("Adding local stream.", userId, this.pc[userId]);
393
394				this.pcStart[userId] = true;
395
396				if (this.initiator || initiator)
397					this.sendOfferToPeer(userId);
398			}
399			else
400			{
401				clearTimeout(this.initPeerConnectionTimeout[userId]);
402				this.initPeerConnectionTimeout[userId] = setTimeout(BX.delegate(function(){
403					this.initPeerConnection(userId, initiator);
404				}, this), 2000);
405			}
406		}
407		else if (!this.connected[userId])
408		{
409			clearTimeout(this.initPeerConnectionTimeout[userId]);
410			this.initPeerConnectionTimeout[userId] = setTimeout(BX.delegate(function(){
411				this.initPeerConnection(userId, initiator);
412			}, this), 2000);
413		}
414
415		return true;
416	}
417
418	// this protected function, you must inherit
419	BX.webrtc.prototype.setLocalAndSend = function(userId, desc)
420	{
421		if (this.pc[userId] == null || !this.callActive)
422			return false;
423
424		this.log('set local description and send', userId, JSON.stringify(desc));
425		this.pc[userId].setLocalDescription(desc, BX.delegate(function(a){this.log('setLocalDescription', a)}, this), BX.delegate(function(a){this.log('setLocalDescription', a)}, this));
426
427		// need to send local description to "userId" peer
428
429		return true;
430	}
431
432	// this is blank function, you must overwrite
433	BX.webrtc.prototype.onRemoteStreamAdded = function(userId, event, setMainVideo)
434	{
435	}
436
437	// this is blank function, you can overwrite
438	BX.webrtc.prototype.onRemoteStreamRemoved = function(userId, event)
439	{
440	}
441
442	// this is blank function, you must overwrite
443	BX.webrtc.prototype.onIceCandidate = function (userId, candidates)
444	{
445		// need to send IceCandidate to "userId" peer
446	}
447
448	// this is blank function, you can overwrite
449	BX.webrtc.prototype.onIceConnectionStateChange = function(userId, event)
450	{
451	}
452
453	// this is blank function, you can overwrite
454	BX.webrtc.prototype.onSignalingStateChange = function(userId, event)
455	{
456	}
457
458	// this is blank function, you can overwrite
459	BX.webrtc.prototype.peerConnectionError = function (userId, event)
460	{
461	}
462
463	// this is public function, you must inherit
464	BX.webrtc.prototype.peerConnectionReconnect = function (userId)
465	{
466		if (!this.pc[userId])
467			return false;
468
469		if ((this.pc[userId].iceConnectionState == 'connected' || this.pc[userId].iceConnectionState == 'completed') && this.pc[userId].signalingState == 'stable')
470			return false;
471
472		this.log('peerConnectionReconnect', this.pc[userId].iceConnectionState, this.pc[userId].signalingState)
473		this.pc[userId].close();
474
475		delete this.pc[userId];
476		delete this.pcStart[userId];
477
478		if (this.callStreamMain == this.callStreamUsers[userId])
479			this.callStreamMain = null;
480		this.callStreamUsers[userId] = null;
481
482		clearTimeout(this.initPeerConnectionTimeout[userId]);
483
484		// need to send reconnect command to "userId" peer in your class
485		return true;
486	}
487
488	// this is public function, you can inherit
489	BX.webrtc.prototype.deleteEvents = function()
490	{
491		this.initiator = false;
492		this.callUserId = 0;
493		this.callChatId = 0;
494		this.callToGroup = false;
495		this.callGroupUsers = [];
496		this.callInit = false;
497		this.callInitUserId = 0;
498		this.callActive = false;
499		this.callVideo = false;
500		this.callRequestUserMedia = {};
501
502		this.audioMuted = false;
503		this.videoMuted = false;
504		this.needPeerConnection = true;
505
506		var i = 0;
507		for (i in this.pc)
508		{
509			if(this.pc.hasOwnProperty(i))
510			{
511				this.pc[i].close();
512				delete this.pc[i];
513			}
514		}
515
516		this.pc = {};
517		this.pcStart = {};
518		this.connected = {};
519		this.iceCandidates = {};
520
521		for (i in this.iceCandidateTimeout)
522		{
523			if (this.iceCandidateTimeout.hasOwnProperty(i))
524			{
525				clearTimeout(this.iceCandidateTimeout[i]);
526			}
527		}
528		this.iceCandidateTimeout = {};
529
530		for (i in this.pcConnectTimeout)
531		{
532			if (this.pcConnectTimeout.hasOwnProperty(i))
533			{
534				clearTimeout(this.pcConnectTimeout[i]);
535			}
536		}
537		this.pcConnectTimeout = {};
538
539		for (i in this.createAnswerTimeout)
540		{
541			if (this.createAnswerTimeout.hasOwnProperty(i))
542			{
543				clearTimeout(this.createAnswerTimeout[i]);
544			}
545		}
546		this.createAnswerTimeout = {};
547
548		for (i in this.initPeerConnectionTimeout)
549		{
550			if (this.initPeerConnectionTimeout.hasOwnProperty(i))
551			{
552				clearTimeout(this.initPeerConnectionTimeout[i]);
553			}
554		}
555		this.initPeerConnectionTimeout = {};
556
557		if (this.callStreamSelf)
558		{
559			BX.webrtc.stopMediaStream(this.callStreamSelf);
560			this.callStreamSelf = null;
561		}
562		if (this.callStreamMain)
563		{
564			BX.webrtc.stopMediaStream(this.callStreamMain);
565			this.callStreamMain = null;
566		}
567		for (i in this.callStreamUsers)
568		{
569			if (this.callStreamUsers.hasOwnProperty(i))
570			{
571				if (this.callStreamUsers[i])
572					BX.webrtc.stopMediaStream(this.callStreamUsers[i]);
573				delete this.callStreamUsers[i];
574			}
575		}
576	}
577
578	/* Private function */
579
580	// this is private function, don't use or overwrite or inherit
581	BX.webrtc.prototype.mergeConstraints = function (cons1, cons2)
582	{
583		if (!this.ready()) return false;
584
585		var merged = cons1;
586		for (var name in cons2.mandatory)
587		{
588			if (cons2.mandatory.hasOwnProperty(name))
589			{
590				merged.mandatory[name] = cons2.mandatory[name];
591			}
592		}
593		merged.optional.concat(cons2.optional);
594		return merged;
595	};
596
597	// this is private function, don't use or overwrite or inherit
598	BX.webrtc.prototype.sendOfferToPeer = function(userId)
599	{
600		if (!this.pc[userId])
601			return false;
602
603		var constraints = {"optional": [], "mandatory": {"MozDontOfferDataChannel": true}};
604
605		if (this.detectedBrowser === "chrome")
606		{
607			for (var prop in constraints.mandatory)
608			{
609				if (constraints.mandatory.hasOwnProperty(prop))
610				{
611					if (prop.indexOf("Moz") != -1)
612						delete constraints.mandatory[prop];
613				}
614			}
615		}
616		this.log('Constraints', constraints);
617		constraints = this.mergeConstraints(constraints, this.sdpConstraints);
618
619		this.log("Sending offer to peer, with constraints:", userId,  "\"" + JSON.stringify(constraints) + "\".");
620		this.pc[userId].createOffer(BX.delegate(function(desc){this.setLocalAndSend(userId, desc)}, this), BX.delegate(function(a){this.log('createOffer failure', a)}, this), constraints);
621
622		return true;
623	}
624
625	// this is private function, don't use or overwrite or inherit
626	BX.webrtc.prototype.createPeerConnection = function(userId)
627	{
628		try
629		{
630			this.pc[userId] = new RTCPeerConnection(this.pcConfig, this.pcConstraints);
631			this.pc[userId].onicecandidate = BX.delegate(function(event) { this.onIceCandidateEvent(userId, event) }, this);
632			this.pc[userId].onaddstream = BX.delegate(function(event) { this.onRemoteStreamAddedEvent(userId, event) }, this);
633			this.pc[userId].onremovestream = BX.delegate(function(event) { this.onRemoteStreamRemovedEvent(userId, event)}, this);
634			this.pc[userId].oniceconnectionstatechange = BX.delegate(function(event) { this.onIceConnectionStateChangeEvent(userId, event)}, this);
635			this.pc[userId].onsignalingstatechange = BX.delegate(function(event) { this.onSignalingStateChangeEvent(userId, event)}, this);
636			if (!this.oneway || this.initiator)
637			{
638				this.pc[userId].addStream(this.callStreamSelf);
639			}
640
641			this.log("Created RTCPeerConnnection for "+userId+" with:\n" +
642			"  config: \"" + JSON.stringify(this.pcConfig) + "\";\n" +
643			"  constraints: \"" + JSON.stringify(this.pcConstraints) + "\".");
644		}
645		catch (e)
646		{
647			if (this.callToGroup && this.callStreamUsers[userId])
648				return false;
649
650			this.log('PeerConnection: ', JSON.stringify(this.pcConfig), JSON.stringify(this.pcConstraints));
651			this.log("Failed to create PeerConnection, exception: " + e.message);
652
653			this.peerConnectionError(userId, e);
654		}
655		return true;
656	}
657
658	// this is private function, don't use or overwrite or inherit
659	BX.webrtc.prototype.signalingPeerData = function(userId, peerData)
660	{
661		var signal = JSON.parse(peerData);
662		if (signal.type === 'offer')
663		{
664			if (!this.pcStart[userId])
665				this.initPeerConnection(userId);
666
667			this.createAnswer(userId, signal);
668		}
669		else if (signal.type === 'answer' && this.pcStart[userId])
670		{
671			if (this.pc[userId] == null)
672				return false;
673
674			this.pc[userId].setRemoteDescription(new RTCSessionDescription(signal), function(){}, function(){});
675		}
676		else if (signal.type === 'candidate' && this.pcStart[userId])
677		{
678			if (!this.pc[userId] || this.pc[userId] == null)
679				return false;
680
681			for (var i = 0; i < signal.candidates.length; i++)
682			{
683				var candidate = new RTCIceCandidate({sdpMLineIndex:signal.candidates[i].label, candidate:signal.candidates[i].candidate});
684				try{
685					this.pc[userId].addIceCandidate(candidate);
686				}
687				catch(e)
688				{
689					this.log('Error addIceCandidate', JSON.stringify(e));
690					this.peerConnectionReconnect(userId);
691					return false;
692				}
693			}
694		}
695		else
696		{
697			this.log('Signaling command "'+(signal && signal.type? signal.type: 'undefined')+'" from user "'+userId+'" skip');
698		}
699
700		return true;
701	}
702
703	// this is private function, don't use or overwrite or inherit
704	BX.webrtc.prototype.onRemoteStreamAddedEvent = function (userId, event)
705	{
706		if (!this.pc[userId]) return false;
707
708		this.log('Remote stream added', userId, JSON.stringify(event));
709
710		var setMainVideo = false;
711		if (!this.callStreamMain)
712		{
713			this.callStreamMain = event.stream;
714			setMainVideo = true;
715		}
716		this.callStreamUsers[userId] = event.stream;
717		this.onRemoteStreamAdded(userId, event, setMainVideo)
718	}
719
720	// this is private function, don't use or overwrite or inherit
721	BX.webrtc.prototype.onRemoteStreamRemovedEvent = function(userId, event)
722	{
723		if (!this.pc[userId]) return false;
724
725		this.callStreamUsers[userId] = null;
726		this.onRemoteStreamRemoved(userId, event);
727	}
728
729	// this is private function, don't use or overwrite or inherit
730	BX.webrtc.prototype.onIceCandidateEvent = function (userId, event)
731	{
732		if (!this.pc[userId]) return false;
733
734		if (!this.iceCandidates[userId])
735			this.iceCandidates[userId] = [];
736
737		if (event.candidate)
738		{
739			this.log("New ICE candidate: ", event.candidate);
740			this.iceCandidates[userId].push({type: 'candidate', label: event.candidate.sdpMLineIndex, id: event.candidate.sdpMid, candidate: event.candidate.candidate});
741
742			clearTimeout(this.iceCandidateTimeout[userId]);
743			this.iceCandidateTimeout[userId] = setTimeout(BX.delegate(function(){
744				if (this.iceCandidates[userId].length === 0)
745					return false;
746
747				this.onIceCandidate(userId, {'type': 'candidate', 'candidates': this.iceCandidates[userId]});
748				this.iceCandidates[userId] = [];
749			}, this), 250);
750		}
751		else
752		{
753			this.log("End of candidates of "+userId);
754		}
755	}
756
757	// this is private function, don't use or overwrite or inherit
758	BX.webrtc.prototype.onIceConnectionStateChangeEvent = function(userId, event)
759	{
760		clearTimeout(this.pcConnectTimeout[userId]);
761		if (!this.pc[userId] || this.pc[userId].iceConnectionState == "close")
762			return false;
763
764		this.log('iceConnectionStateChange', userId, this.pc[userId].iceConnectionState, this.pc[userId].signalingState);
765		this.pcConnectTimeout[userId] = setTimeout(BX.delegate(function(){
766			this.peerConnectionReconnect(userId);
767		}, this), 15000);
768
769		this.onIceConnectionStateChange(userId, event);
770
771		return true;
772	}
773
774	// this is private function, don't use or overwrite or inherit
775	BX.webrtc.prototype.onSignalingStateChangeEvent = function(userId, event)
776	{
777		clearTimeout(this.pcConnectTimeout[userId]);
778		if (!this.pc[userId] || this.pc[userId].signalingState == "close")
779			return false;
780
781		this.log('signalingStateChange', userId, this.pc[userId].iceConnectionState, this.pc[userId].signalingState);
782		this.pcConnectTimeout[userId] = setTimeout(BX.delegate(function(){
783			this.peerConnectionReconnect(userId);
784		}, this), 15000);
785
786		this.onSignalingStateChange(userId, event)
787	}
788
789	// this is private function, don't use or overwrite or inherit
790	BX.webrtc.prototype.createAnswer = function(userId, signal)
791	{
792		if (!this.callActive)
793			return false;
794
795		if (!this.pcStart[userId])
796		{
797			clearTimeout(this.createAnswerTimeout[userId]);
798			this.createAnswerTimeout[userId] = setTimeout(BX.delegate(function(){
799				this.createAnswer(userId, signal);
800			}, this), 2000);
801
802			return false;
803		}
804		this.pc[userId].setRemoteDescription(new RTCSessionDescription(signal), BX.delegate(function()
805		{
806			if (!this.pc[userId]) return false;
807			this.pc[userId].createAnswer(BX.delegate(function(desc){this.setLocalAndSend(userId, desc)}, this), BX.delegate(function(a){this.log('createAnswer failure', a)}, this), this.sdpConstraints);
808		}, this), function(){});
809
810		return true;
811	}
812
813	BX.webrtc.prototype.setDefaultCamera = function(defaultCamera)
814	{
815		this.defaultCamera = defaultCamera;
816	};
817
818	BX.webrtc.prototype.setDefaultMicrophone = function(defaultMicrophone)
819	{
820		this.defaultMicrophone = defaultMicrophone;
821	};
822
823	BX.webrtc.prototype.logDevices = function()
824	{
825		var self = this;
826		if(navigator.mediaDevices && navigator.mediaDevices.enumerateDevices)
827		{
828			self.log('Enumerating media devices');
829			navigator.mediaDevices.enumerateDevices().then(function(devices)
830			{
831				devices.forEach(function(device)
832				{
833					try
834					{
835						self.log(JSON.stringify(device));
836					}
837					catch (e)
838					{
839						self.log(device);
840					}
841				});
842			})
843		}
844		else
845		{
846			self.log('Could not enumerate devices, api is not supported');
847		}
848	};
849
850	BX.webrtc.mediaStreamTrackToString = function(track)
851	{
852		var result = '';
853		for(key in track)
854		{
855			if(!BX.type.isFunction(track[key]))
856			{
857				result = result + ' ' + key + ': ' + track[key] + ';';
858			}
859		}
860		return result;
861	};
862
863	BX.webrtc.stopMediaStream = function(mediaStream)
864	{
865		if(!(mediaStream instanceof MediaStream))
866			return;
867
868		if (typeof mediaStream.getTracks === 'undefined')
869		{
870			// Support for legacy browsers
871			mediaStream.stop();
872		}
873		else
874		{
875			mediaStream.getTracks().forEach(function(track)
876			{
877				track.stop();
878			});
879		}
880	};
881
882})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.