forked from butorenjin/easyrtc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patheasyrtc.js
5701 lines (5412 loc) · 221 KB
/
easyrtc.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//
// the below code is a copy of the standard polyfill adapter.js
//
var getUserMedia = null;
var attachMediaStream = null;
var reattachMediaStream = null;
var webrtcDetectedBrowser = null;
var webrtcDetectedVersion = null;
if (navigator.mozGetUserMedia) {
// console.log("This appears to be Firefox");
webrtcDetectedBrowser = "firefox";
//
// better version detection for gecko based browsers provided by
// Kévin Poulet.
//
var matches = navigator.userAgent.match(/\srv:([0-9]+)\./);
if (matches !== null && matches.length > 1) {
webrtcDetectedVersion = parseInt(matches[1]);
}
// The RTCPeerConnection object.
window.RTCPeerConnection = mozRTCPeerConnection;
// The RTCSessionDescription object.
window.RTCSessionDescription = mozRTCSessionDescription;
// The RTCIceCandidate object.
window.RTCIceCandidate = mozRTCIceCandidate;
// Get UserMedia (only difference is the prefix).
// Code from Adam Barth.
window.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
// Creates iceServer from the url for FF.
window.createIceServer = function(url, username, password) {
var iceServer = null;
var url_parts = url.split(':');
var turn_url_parts;
if (url_parts[0].indexOf('stun') === 0) {
// Create iceServer with stun url.
iceServer = {'url': url};
} else if (url_parts[0].indexOf('turn') === 0 &&
(url.indexOf('transport=udp') !== -1 ||
url.indexOf('?transport') === -1)) {
// Create iceServer with turn url.
// Ignore the transport parameter from TURN url.
turn_url_parts = url.split("?");
iceServer = {'url': turn_url_parts[0],
'credential': password,
'username': username};
}
return iceServer;
};
// Attach a media stream to an element.
attachMediaStream = function(element, stream) {
// console.log("Attaching media stream");
element.mozSrcObject = stream;
element.play();
};
reattachMediaStream = function(to, from) {
// console.log("Reattaching media stream");
to.mozSrcObject = from.mozSrcObject;
to.play();
};
if (webrtcDetectedVersion < 23) {
// Fake get{Video,Audio}Tracks
MediaStream.prototype.getVideoTracks = function() {
return [];
};
MediaStream.prototype.getAudioTracks = function() {
return [];
};
}
} else if (navigator.webkitGetUserMedia) {
// console.log("This appears to be Chrome");
webrtcDetectedBrowser = "chrome";
webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]);
// Creates iceServer from the url for Chrome.
window.createIceServer = function(url, username, password) {
var iceServer = null;
var url_turn_parts;
var url_parts = url.split(':');
if (url_parts[0].indexOf('stun') === 0) {
// Create iceServer with stun url.
iceServer = {'url': url};
} else if (url_parts[0].indexOf('turn') === 0) {
if (webrtcDetectedVersion < 28) {
// For pre-M28 chrome versions use old TURN format.
url_turn_parts = url.split("turn:");
iceServer = {'url': 'turn:' + username + '@' + url_turn_parts[1],
'credential': password};
} else {
// For Chrome M28 & above use new TURN format.
iceServer = {'url': url,
'credential': password,
'username': username};
}
}
return iceServer;
};
// The RTCPeerConnection object.
window.RTCPeerConnection = webkitRTCPeerConnection;
// Get UserMedia (only difference is the prefix).
// Code from Adam Barth.
window.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
// Attach a media stream to an element.
attachMediaStream = function(element, stream) {
if (typeof element.srcObject !== 'undefined') {
element.srcObject = stream;
} else if (typeof element.mozSrcObject !== 'undefined') {
element.mozSrcObject = stream;
} else if (typeof element.src !== 'undefined') {
element.src = URL.createObjectURL(stream);
} else {
console.log('Error attaching stream to element.');
}
};
reattachMediaStream = function(to, from) {
to.src = from.src;
};
// The representation of tracks in a stream is changed in M26.
// Unify them for earlier Chrome versions in the coexisting period.
if (!webkitMediaStream.prototype.getVideoTracks) {
webkitMediaStream.prototype.getVideoTracks = function() {
return this.videoTracks;
};
webkitMediaStream.prototype.getAudioTracks = function() {
return this.audioTracks;
};
}
// New syntax of getXXXStreams method in M26.
if (!webkitRTCPeerConnection.prototype.getLocalStreams) {
webkitRTCPeerConnection.prototype.getLocalStreams = function() {
return this.localStreams;
};
webkitRTCPeerConnection.prototype.getRemoteStreams = function() {
return this.remoteStreams;
};
}
//} else if( window.ActiveXObject ){ // appears to IE so check for the wrapper.
// var head = document.getElementsByTagName('head')[0];
// var i;
// var adapterAddress;
// var wrapperPresent = false;
//
// //
// // we look for the adapter as well as the wrapper because if we don't find the
// // wrapper, we'll look for it in the same directory as the adapter was found.
// //
// for( i = 0; i < head.childNodes.length; i++) {
// var child = head.childNodes[i];
// if( /\/adapter.js$/.test(child.src)) {
// adapterAddress = child.src;
// }
// else if( /\/rtcplugin.js$/.test(child.src)) {
// wrapperPresent = true;
// }
// }
//
//
// if( wrapperPresent) {
// addIEDeclarations();
// }
// else if( adapterAddress) {
// var script = document.createElement('script');
// script.type = 'text/javascript';
// script.src = adapterAddress.replace(/\/adapter.js$/, "/rtcplugin.js");
// src.onload = addIEDeclarations;
// src.onerror = function () {
// alert("Developer error: this page requires the Priologic IE Webrtc plugin wrapper (rtcplugin.js) to run when using Internet Explorer, which the developer has not supplied.");
// throw new Error("No rtcplugin.js found. It should be in the same folder as your adapter.js or you can include it yourself before the adapter.js");
// }
// head.appendChild(script);
// }
} else {
console.log("Browser does not appear to be WebRTC-capable");
}
if (!window.createIceServer) {
window.createIceServer = function(url, username, credential) {
return {'url': url, 'credential': credential, 'username': username};
};
}/** @class
*@version 1.0.15
*<p>
* Provides client side support for the EasyRTC framework.
* Please see the easyrtc_client_api.md and easyrtc_client_tutorial.md
* for more details.</p>
*
*<p>
*copyright Copyright (c) 2015, Priologic Software Inc.
*All rights reserved.</p>
*
*<p>
*Redistribution and use in source and binary forms, with or without
*modification, are permitted provided that the following conditions are met:
*</p>
* <ul>
* <li> Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. </li>
* <li> Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution. </li>
*</ul>
*<p>
*THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
*AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
*IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
*ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
*LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
*CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
*SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
*INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
*CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
*ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
*POSSIBILITY OF SUCH DAMAGE.
*</p>
*/
var Easyrtc = function() {
var self = this;
var isFirefox = (webrtcDetectedBrowser === "firefox");
var autoInitUserMedia = true;
var sdpLocalFilter = null,
sdpRemoteFilter = null;
var iceCandidateFilter = null;
var connectionOptions = {
'connect timeout': 10000,
'force new connection': true
};
//
// this function replaces the deprecated MediaStream.stop method
//
function stopStream(stream) {
var i;
var tracks;
tracks = stream.getAudioTracks();
for( i = 0; i < tracks.length; i++ ) {
try {
tracks[i].stop();
} catch(err){}
}
tracks = stream.getVideoTracks();
for( i = 0; i < tracks.length; i++ ) {
try {
tracks[i].stop();
} catch(err){}
}
}
/**
* Sets functions which filter sdp records before calling setLocalDescription or setRemoteDescription.
* This is advanced functionality which can break things, easily. See the easyrtc_rates.js file for a
* filter builder.
* @param {Function} localFilter a function that takes an sdp string and returns an sdp string.
* @param {Function} remoteFilter a function that takes an sdp string and returns an sdp string.
*/
this.setSdpFilters = function(localFilter, remoteFilter) {
sdpLocalFilter = localFilter;
sdpRemoteFilter = remoteFilter;
};
/**
* Sets a function to warn about the peer connection closing.
* @param {Function} handler: a function that gets an easyrtcid as an argument.
*/
this.setPeerClosedListener = function( handler ) {
this.onPeerClosed = handler;
};
/**
* Sets a function to receive warnings about the peer connection
* failing. The peer connection may recover by itself.
* @param {Function} failingHandler: a function that gets an easyrtcid as an argument.
* @param {Function} recoveredHandler: a function that gets an easyrtcid as an argument.
*/
this.setPeerFailingListener = function( failingHandler, recoveredHandler ) {
this.onPeerFailing = failingHandler;
this.onPeerRecovered = recoveredHandler;
};
/**
* Sets a function which filters IceCandidate records being sent or received.
*
* Candidate records can be received while they are being generated locally (before being
* sent to a peer), and after they are received by the peer. The filter receives two arguments, the candidate record and a boolean
* flag that is true for a candidate being received from another peer,
* and false for a candidate that was generated locally. The candidate record has the form:
* {type: 'candidate', label: sdpMLineIndex, id: sdpMid, candidate: candidateString}
* The function should return one of the following: the input candidate record, a modified candidate record, or null (indicating that the
* candidate should be discarded).
* @param {Function} filter
*/
this.setIceCandidateFilter = function(filter) {
iceCandidateFilter = filter;
};
/**
* Controls whether a default local media stream should be acquired automatically during calls and accepts
* if a list of streamNames is not supplied. The default is true, which mimics the behaviour of earlier releases
* that didn't support multiple streams. This function should be called before easyrtc.call or before entering an
* accept callback.
* @param {Boolean} flag true to allocate a default local media stream.
*/
this.setAutoInitUserMedia = function(flag) {
autoInitUserMedia = !!flag;
};
/**
* This function performs a printf like formatting. It actually takes an unlimited
* number of arguments, the declared arguments arg1, arg2, arg3 are present just for
* documentation purposes.
* @param {String} format A string like "abcd{1}efg{2}hij{1}."
* @param {String} arg1 The value that replaces {1}
* @param {String} arg2 The value that replaces {2}
* @param {String} arg3 The value that replaces {3}
* @returns {String} the formatted string.
*/
this.format = function(format, arg1, arg2, arg3) {
var formatted = arguments[0];
for (var i = 1; i < arguments.length; i++) {
var regexp = new RegExp('\\{' + (i - 1) + '\\}', 'gi');
formatted = formatted.replace(regexp, arguments[i]);
}
return formatted;
};
/**
* This function checks if a socket is actually connected.
* @param {Object} socket a socket.io socket.
* @return true if the socket exists and is connected, false otherwise.
*/
function isSocketConnected(socket) {
return (socket &&
( ( socket.socket && socket.socket.connected)
|| socket.connected ));
}
/** @private */
var haveAudioVideo = {audio: false, video: false};
//
// Maps a key to a language specific string using the easyrtc_constantStrings map.
// Defaults to the key if the key can not be found, but outputs a warning in that case.
// This function is only used internally by easyrtc.js
//
/**
* @private
* @param {String} key
*/
this.getConstantString = function(key) {
if (easyrtc_constantStrings[key]) {
return easyrtc_constantStrings[key];
}
else {
console.warn("Could not find key='" + key + "' in easyrtc_constantStrings");
return key;
}
};
//
// this is a list of the events supported by the generalized event listener.
//
var allowedEvents = {
roomOccupant: true, // this receives the list of everybody in any room you belong to
roomOccupants: true // this receives a {roomName:..., occupants:...} value for a specific room
};
//
// A map of eventListeners. The key is the event type.
var eventListeners = {};
/** This function checks if an attempt was made to add an event listener or
* or emit an unlisted event, since such is typically a typo.
* @private
* @param {String} eventName
* @param {String} callingFunction the name of the calling function.
*/
function event(eventName, callingFunction) {
if (typeof eventName !== 'string') {
self.showError(self.errCodes.DEVELOPER_ERR, src + " called without a string as the first argument");
throw "developer error";
}
if (!allowedEvents[eventName]) {
self.showError(self.errCodes.DEVELOPER_ERR, src + " called with a bad event name = " + eventName);
throw "developer error";
}
}
/**
* Adds an event listener for a particular type of event.
* Currently the only eventName supported is "roomOccupant".
* @param {String} eventName the type of the event
* @param {Function} eventListener the function that expects the event.
* The eventListener gets called with the eventName as it's first argument, and the event
* data as it's second argument.
* @returns {void}
*/
this.addEventListener = function(eventName, eventListener) {
event(eventName, "addEventListener");
if (typeof eventListener !== 'function') {
self.showError(self.errCodes.DEVELOPER_ERR, "addEventListener called with a non-function for second argument");
throw "developer error";
}
//
// remove the event listener if it's already present so we don't end up with two copies
//
self.removeEventListener(eventName, eventListener);
if (!eventListeners[eventName]) {
eventListeners[eventName] = [];
}
eventListeners[eventName][eventListeners[eventName].length] = eventListener;
};
/**
* Removes an event listener.
* @param {String} eventName
* @param {Function} eventListener
*/
this.removeEventListener = function(eventName, eventListener) {
event(eventName, "removeEventListener");
var listeners = eventListeners[eventName];
var i = 0;
if (listeners) {
for (i = 0; i < listeners.length; i++) {
if (listeners[i] === eventListener) {
if (i < listeners.length - 1) {
listeners[i] = listeners[listeners.length - 1];
}
listeners.length = listeners.length - 1;
}
}
}
};
/**
* Emits an event, or in other words, calls all the eventListeners for a
* particular event.
* @param {String} eventName
* @param {Object} eventData
*/
this.emitEvent = function(eventName, eventData) {
event(eventName, "emitEvent");
var listeners = eventListeners[eventName];
var i = 0;
if (listeners) {
for (i = 0; i < listeners.length; i++) {
listeners[i](eventName, eventData);
}
}
};
/** Error codes that the EasyRTC will use in the errorCode field of error object passed
* to error handler set by easyrtc.setOnError. The error codes are short printable strings.
* @type Object
*/
this.errCodes = {
BAD_NAME: "BAD_NAME", // a user name wasn't of the desired form
CALL_ERR: "CALL_ERR", // something went wrong creating the peer connection
DEVELOPER_ERR: "DEVELOPER_ERR", // the developer using the EasyRTC library made a mistake
SYSTEM_ERR: "SYSTEM_ERR", // probably an error related to the network
CONNECT_ERR: "CONNECT_ERR", // error occurred when trying to create a connection
MEDIA_ERR: "MEDIA_ERR", // unable to get the local media
MEDIA_WARNING: "MEDIA_WARNING", // didn't get the desired resolution
INTERNAL_ERR: "INTERNAL_ERR",
PEER_GONE: "PEER_GONE", // peer doesn't exist
ALREADY_CONNECTED: "ALREADY_CONNECTED",
BAD_CREDENTIAL: "BAD_CREDENTIAL",
ICECANDIDATE_ERR: "ICECANDIDATE_ERROR"
};
this.apiVersion = "1.0.15";
/** Most basic message acknowledgment object */
this.ackMessage = {msgType: "ack"};
/** Regular expression pattern for user ids. This will need modification to support non US character sets */
this.usernameRegExp = /^(.){1,64}$/;
/** Default cookieId name */
this.cookieId = "easyrtcsid";
/** @private */
var username = null;
/** Flag to indicate that user is currently logging out */
this.loggingOut = false;
/** @private */
this.disconnecting = false;
//
// A map of ids to local media streams.
//
var namedLocalMediaStreams = {};
var sessionFields = [];
var receivedMediaConstraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true
}
};
/**
* Control whether the client requests audio from a peer during a call.
* Must be called before the call to have an effect.
* @param value - true to receive audio, false otherwise. The default is true.
*/
this.enableAudioReceive = function(value) {
receivedMediaConstraints.mandatory.OfferToReceiveAudio = value;
};
/**
* Control whether the client requests video from a peer during a call.
* Must be called before the call to have an effect.
* @param value - true to receive video, false otherwise. The default is true.
*/
this.enableVideoReceive = function(value) {
receivedMediaConstraints.mandatory.OfferToReceiveVideo = value;
};
function getSourceList(callback, sourceType) {
if (MediaStreamTrack.getSources) {
MediaStreamTrack.getSources(function(sources) {
var results = [];
for (var i = 0; i < sources.length; i++) {
var source = sources[i];
if (source.kind == sourceType) {
results.push(source);
}
}
callback(results);
});
}
else {
callback([]);
}
}
/**
* Gets a list of the available audio sources (ie, cameras)
* @param {Function} callback receives list of {label:String, id:String, kind:"audio"}
* Note: the label string always seems to be the empty string if you aren't using https.
* Note: not supported by Firefox.
* @example easyrtc.getAudioSourceList( function(list) {
* var i;
* for( i = 0; i < list.length; i++ ) {
* console.log("label=" + list[i].label + ", id= " + list[i].id);
* }
* });
*/
this.getAudioSourceList = function(callback){
getSourceList(callback, "audio");
};
/**
* Gets a list of the available video sources (ie, cameras)
* @param {Function} callback receives list of {facing:String, label:String, id:String, kind:"video"}
* Note: the label string always seems to be the empty string if you aren't using https.
* Note: not supported by Firefox.
* @example easyrtc.getVideoSourceList( function(list) {
* var i;
* for( i = 0; i < list.length; i++ ) {
* console.log("label=" + list[i].label + ", id= " + list[i].id);
* }
* });
*/
this.getVideoSourceList = function(callback) {
getSourceList(callback, "video");
};
/** @private */
self.audioEnabled = true;
/** @private */
self.videoEnabled = true;
/** @private */
var dataChannelName = "dc";
/** @private */
this.debugPrinter = null;
/** Your easyrtcid */
this.myEasyrtcid = "";
/** @private */
var oldConfig = {};
/** @private */
var offersPending = {};
/** The height of the local media stream video in pixels. This field is set an indeterminate period
* of time after easyrtc.initMediaSource succeeds. Note: in actuality, the dimensions of a video stream
* change dynamically in response to external factors, you should check the videoWidth and videoHeight attributes
* of your video objects before you use them for pixel specific operations.
*/
this.nativeVideoHeight = 0;
/** This constant determines how long (in bytes) a message can be before being split in chunks of that size.
* This is because there is a limitation of the length of the message you can send on the
* data channel between browsers.
*/
this.maxP2PMessageLength = 1000;
/** The width of the local media stream video in pixels. This field is set an indeterminate period
* of time after easyrtc.initMediaSource succeeds. Note: in actuality, the dimensions of a video stream
* change dynamically in response to external factors, you should check the videoWidth and videoHeight attributes
* of your video objects before you use them for pixel specific operations.
*/
this.nativeVideoWidth = 0;
/** @private */
var credential = null;
/** The rooms the user is in. This only applies to room oriented applications and is set at the same
* time a token is received.
*/
this.roomJoin = {};
/** Checks if the supplied string is a valid user name (standard identifier rules)
* @param {String} name
* @return {Boolean} true for a valid user name
* @example
* var name = document.getElementById('nameField').value;
* if( !easyrtc.isNameValid(name)){
* console.error("Bad user name");
* }
*/
this.isNameValid = function(name) {
return self.usernameRegExp.test(name);
};
/**
* This function sets the name of the cookie that client side library will look for
* and transmit back to the server as it's easyrtcsid in the first message.
* @param {String} cookieId
*/
this.setCookieId = function(cookieId) {
self.cookieId = cookieId;
};
/**
* This method allows you to join a single room. It may be called multiple times to be in
* multiple rooms simultaneously. It may be called before or after connecting to the server.
* Note: the successCB and failureDB will only be called if you are already connected to the server.
* @param {String} roomName the room to be joined.
* @param {String} roomParameters application specific parameters, can be null.
* @param {Function} successCB called once, with a roomName as it's argument, once the room is joined.
* @param {Function} failureCB called if the room can not be joined. The arguments of failureCB are errorCode, errorText, roomName.
*/
this.joinRoom = function(roomName, roomParameters, successCB, failureCB) {
if (self.roomJoin[roomName]) {
console.error("Developer error: attempt to join room " + roomName + " which you are already in.");
return;
}
var newRoomData = {roomName: roomName};
if (roomParameters) {
try {
JSON.stringify(roomParameters);
} catch (error) {
self.showError(self.errCodes.DEVELOPER_ERR, "non-jsonable parameter to easyrtc.joinRoom");
throw "Developer error, see application error messages";
}
var parameters = {};
for (var key in roomParameters) {
if (roomParameters.hasOwnProperty(key)) {
parameters[key] = roomParameters[key];
}
}
newRoomData.roomParameter = parameters;
}
var msgData = {
roomJoin: {}
};
var roomData;
var signallingSuccess, signallingFailure;
if (self.webSocket) {
msgData.roomJoin[roomName] = newRoomData;
signallingSuccess = function(msgType, msgData) {
roomData = msgData.roomData;
self.roomJoin[roomName] = newRoomData;
if (successCB) {
successCB(roomName);
}
processRoomData(roomData);
};
signallingFailure = function(errorCode, errorText) {
if (failureCB) {
failureCB(errorCode, errorText, roomName);
}
else {
self.showError(errorCode, self.format(self.getConstantString("unableToEnterRoom"), roomName, errorText));
}
};
sendSignalling(null, "roomJoin", msgData, signallingSuccess, signallingFailure);
}
else {
self.roomJoin[roomName] = newRoomData;
}
};
/**
* This function allows you to leave a single room. Note: the successCB and failureDB
* arguments are optional and will only be called if you are already connected to the server.
* @param {String} roomName
* @param {Function} successCallback - A function which expects a roomName.
* @param {Function} failureCallback - A function which expects the following arguments: errorCode, errorText, roomName.
* @example
* easyrtc.leaveRoom("freds_room");
* easyrtc.leaveRoom("freds_room", function(roomName){ console.log("left the room")},
* function(errorCode, errorText, roomName){ console.log("left the room")});
*/
this.leaveRoom = function(roomName, successCallback, failureCallback) {
var roomItem;
if (self.roomJoin[roomName]) {
if (!self.webSocket) {
delete self.roomJoin[roomName];
}
else {
roomItem = {};
roomItem[roomName] = {roomName: roomName};
sendSignalling(null, "roomLeave", {roomLeave: roomItem},
function(msgType, msgData) {
var roomData = msgData.roomData;
processRoomData(roomData);
if (successCallback) {
successCallback(roomName);
}
},
function(errorCode, errorText) {
if (failureCallback) {
failureCallback(errorCode, errorText, roomName);
}
});
}
}
};
/** @private */
this._desiredVideoProperties = {}; // default camera
/**
* Specify particular video source. Call this before you call easyrtc.initMediaSource().
* Note: this function isn't supported by Firefox.
* @param {String} videoSrcId is a id value from one of the entries fetched by getVideoSourceList. null for default.
* @example easyrtc.setVideoSrc( videoSrcId);
*/
this.setVideoSource = function(videoSrcId) {
self._desiredVideoProperties.videoSrcId = videoSrcId;
delete self._desiredVideoProperties.screenCapture;
};
/**
* Temporary alias for easyrtc.setVideoSource
*/
this.setVideoSrc = this.setVideoSource;
delete this._desiredVideoProperties.screenCapture;
/** This function is used to set the dimensions of the local camera, usually to get HD.
* If called, it must be called before calling easyrtc.initMediaSource (explicitly or implicitly).
* assuming it is supported. If you don't pass any parameters, it will default to 720p dimensions.
* @param {Number} width in pixels
* @param {Number} height in pixels
* @param {number} frameRate is optional
* @example
* easyrtc.setVideoDims(1280,720);
* @example
* easyrtc.setVideoDims();
*/
this.setVideoDims = function(width, height, frameRate) {
if (!width) {
width = 1280;
height = 720;
}
self._desiredVideoProperties.width = width;
self._desiredVideoProperties.height = height;
if (frameRate !== undefined) {
self._desiredVideoProperties.frameRate = frameRate;
}
};
/** This function requests that screen capturing be used to provide the local media source
* rather than a webcam. If you have multiple screens, they are composited side by side.
* Note: this functionality is not supported by Firefox, has to be called before calling initMediaSource (or easyApp), we don't currently supply a way to
* turn it off (once it's on), only works if the website is hosted SSL (https), and the image quality is rather
* poor going across a network because it tries to transmit so much data. In short, screen sharing
* through WebRTC isn't worth using at this point, but it is provided here so people can try it out.
* @example
* easyrtc.setScreenCapture();
* @deprecated: use easyrtc.initScreenCapture (same parameters as easyrtc.initMediaSource.
*/
this.setScreenCapture = function(enableScreenCapture) {
self._desiredVideoProperties.screenCapture = (enableScreenCapture !== false);
};
/**
* Builds the constraint object passed to getUserMedia.
* @returns {Object} mediaConstraints
*/
self.getUserMediaConstraints = function() {
var constraints = {};
//
// _presetMediaConstraints allow you to provide your own constraints to be used
// with initMediaSource.
//
if (self._presetMediaConstraints) {
constraints = self._presetMediaConstraints;
delete self._presetMediaConstraints;
return constraints;
}
else if (self._desiredVideoProperties.screenCapture) {
return {
video: {
mandatory: {
chromeMediaSource: 'screen',
maxWidth: screen.width,
maxHeight: screen.height,
minWidth: screen.width,
minHeight: screen.height,
minFrameRate: 1,
maxFrameRate: 5},
optional: []
},
audio: false
};
}
else if (!self.videoEnabled) {
constraints.video = false;
}
else {
constraints.video = {mandatory: {}, optional: []};
if (self._desiredVideoProperties.width) {
constraints.video.mandatory.maxWidth = self._desiredVideoProperties.width;
constraints.video.mandatory.minWidth = self._desiredVideoProperties.width;
}
if (self._desiredVideoProperties.width) {
constraints.video.mandatory.maxHeight = self._desiredVideoProperties.height;
constraints.video.mandatory.minHeight = self._desiredVideoProperties.height;
}
if (self._desiredVideoProperties.frameRate) {
constraints.video.mandatory.maxFrameRate = self._desiredVideoProperties.frameRate;
}
if (self._desiredVideoProperties.videoSrcId) {
constraints.video.optional.push({sourceId: self._desiredVideoProperties.videoSrcId});
}
// hack for opera
if (constraints.video.mandatory.length === 0 && constraints.video.optional.length === 0) {
constraints.video = true;
}
}
constraints.audio = self.audioEnabled;
return constraints;
};
/** Set the application name. Applications can only communicate with other applications
* that share the same API Key and application name. There is no predefined set of application
* names. Maximum length is
* @param {String} name
* @example
* easyrtc.setApplicationName('simpleAudioVideo');
*/
this.setApplicationName = function(name) {
self.applicationName = name;
};
/** Enable or disable logging to the console.
* Note: if you want to control the printing of debug messages, override the
* easyrtc.debugPrinter variable with a function that takes a message string as it's argument.
* This is exactly what easyrtc.enableDebug does when it's enable argument is true.
* @param {Boolean} enable - true to turn on debugging, false to turn off debugging. Default is false.
* @example
* easyrtc.enableDebug(true);
*/
this.enableDebug = function(enable) {
if (enable) {
self.debugPrinter = function(message) {
var stackString = new Error().stack;
var srcLine = "location unknown";
if (stackString) {
var stackFrameStrings = stackString.split('\n');
srcLine = "";
if (stackFrameStrings.length >= 3) {
srcLine = stackFrameStrings[2];
}
}
console.log("debug " + (new Date()).toISOString() + " : " + message + " [" + srcLine + "]");
};
}
else {
self.debugPrinter = null;
}
};
//
// this is a temporary version used until we connect to the server.
//
this.updatePresence = function(state, statusText) {
self.presenceShow = state;
self.presenceStatus = statusText;
};
/**
* Determines if the local browser supports WebRTC GetUserMedia (access to camera and microphone).
* @returns {Boolean} True getUserMedia is supported.
*/
this.supportsGetUserMedia = function() {
return !!getUserMedia;
};
/**
* Determines if the local browser supports WebRTC Peer connections to the extent of being able to do video chats.
* @returns {Boolean} True if Peer connections are supported.
*/
this.supportsPeerConnections = function() {
if (!self.supportsGetUserMedia()) {
return false;
}
if (!window.RTCPeerConnection) {
return false;
}
try {
self.createRTCPeerConnection({"iceServers": []}, null);
} catch (oops) {
return false;
}
return true;
};
/** @private
* @param pc_config ice configuration array
* @param optionalStuff peer constraints.
*/
/** @private
* @param pc_config ice configuration array
* @param optionalStuff peer constraints.
*/
this.createRTCPeerConnection = function(pc_config, optionalStuff) {
if (RTCPeerConnection) {
return new RTCPeerConnection(pc_config, optionalStuff);
}
else {
throw "Your browser doesn't support webRTC (RTCPeerConnection)";
}
};
//
// this should really be part of adapter.js
// Versions of chrome < 31 don't support reliable data channels transport.
// Firefox does.
//
this.getDatachannelConstraints = function() {
if (webrtcDetectedBrowser === "chrome" && webrtcDetectedVersion < 31) {
return {reliable: false};
}
else {
return {reliable: true};
}
};
/** @private */
haveAudioVideo = {
audio: false,
video: false
};
/** @private */
var dataEnabled = false;
/** @private */
var serverPath = null;
/** @private */
var roomOccupantListener = null;
/** @private */
var onDataChannelOpen = null;
/** @private */
var onDataChannelClose = null;
/** @private */
var lastLoggedInList = {};
/** @private */
var receivePeer = {msgTypes: {}};
/** @private */
var receiveServerCB = null;
/** @private */
var updateConfigurationInfo = function() {
}; // dummy placeholder for when we aren't connected
//
//
// peerConns is a map from caller names to the below object structure
// { startedAV: boolean, -- true if we have traded audio/video streams
// dataChannelS: RTPDataChannel for outgoing messages if present
// dataChannelR: RTPDataChannel for incoming messages if present
// dataChannelReady: true if the data channel can be used for sending yet
// connectTime: timestamp when the connection was started
// sharingAudio: true if audio is being shared
// sharingVideo: true if video is being shared
// cancelled: temporarily true if a connection was cancelled by the peer asking to initiate it
// candidatesToSend: SDP candidates temporarily queued
// streamsAddedAcks: ack callbacks waiting for stream received messages
// pc: RTCPeerConnection
// mediaStream: mediaStream
// function callSuccessCB(string) - see the easyrtc.call documentation.
// function callFailureCB(errorCode, string) - see the easyrtc.call documentation.
// function wasAcceptedCB(boolean,string) - see the easyrtc.call documentation.
// }
//
/** @private */
var peerConns = {};
//
// a map keeping track of whom we've requested a call with so we don't try to
// call them a second time before they've responded.
//
/** @private */
var acceptancePending = {};
/**
* Disconnect from the EasyRTC server.
* @example
* easyrtc.disconnect();
*/
this.disconnect = function() {
};
/** @private
* @param caller
* @param helper
*/
this.acceptCheck = function(caller, helper) {
helper(true);
};
/** @private
* @param easyrtcid
* @param stream
*/
this.streamAcceptor = function(easyrtcid, stream) {
};
/** @private
* @param easyrtcid