EasyRTC Documentation

Documentation

Source: easyrtc_app.js

1
/* global define, module, require, console */
2
/*!
3
Script: easyrtc_app.js
4
5
Provides support file and data transfer support to easyrtc.
6
7
About: License
8
9
Copyright (c) 2016, Priologic Software Inc.
10
All rights reserved.
11
12
Redistribution and use in source and binary forms, with or without
13
modification, are permitted provided that the following conditions are met:
14
15
* Redistributions of source code must retain the above copyright notice,
16
this list of conditions and the following disclaimer.
17
* Redistributions in binary form must reproduce the above copyright
18
notice, this list of conditions and the following disclaimer in the
19
documentation and/or other materials provided with the distribution.
20
21
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
POSSIBILITY OF SUCH DAMAGE.
32
*/
33
34
(function (root, factory) {
35
if (typeof define === 'function' && define.amd) {
36
//RequireJS (AMD) build system
37
define(['easyrtc'], factory);
38
} else if (typeof module === 'object' && module.exports) {
39
//CommonJS build system
40
module.exports = factory(require('easyrtc'));
41
} else {
42
//Vanilla JS, ensure dependencies are loaded correctly
43
if (typeof window.easyrtc !== 'object' || !window.easyrtc) {
44
throw new Error("easyrtc_app requires easyrtc");
45
}
46
root.easyrtc = factory(window.easyrtc);
47
}
48
}(this, function (easyrtc, undefined) {
49
50
"use strict";
51
52
/**
53
* This file adds additional methods to Easyrtc for simplifying the 
54
* management of video-mediastream assignment.
55
* @class Easyrtc_App
56
*/
57
58
/** @private */
59
var autoAddCloseButtons = true;
60
61
/** By default, the easyApp routine sticks a "close" button on top of each caller
62
* video object that it manages. Call this function(before calling easyApp) to disable that particular feature.
63
* @function
64
* @memberOf Easyrtc_App
65
* @example
66
*    easyrtc.dontAddCloseButtons();
67
*/
68
easyrtc.dontAddCloseButtons = function() {
69
autoAddCloseButtons = false;
70
};
71
72
/**
73
* This is a helper function for the easyApp method. It manages the assignment of video streams
74
* to video objects. It assumes
75
* @param {String} monitorVideoId is the id of the mirror video tag.
76
* @param {Array} videoIds is an array of ids of the caller video tags.
77
* @private
78
*/
79
function easyAppBody(monitorVideoId, videoIds) {
80
81
var videoIdsP = videoIds || [],
82
numPEOPLE = videoIds.length,
83
videoIdToCallerMap = {},
84
onCall = null, 
85
onHangup = null;
86
87
/**
88
* Validates that the video ids correspond to dom objects.
89
* @param {String} monitorVideoId
90
* @param {Array} videoIds
91
* @returns {Boolean}
92
* @private
93
*/
94
function validateVideoIds(monitorVideoId, videoIds) {
95
var i;
96
// verify that video ids were not typos.
97
if (monitorVideoId && !document.getElementById(monitorVideoId)) {
98
easyrtc.showError(easyrtc.errCodes.DEVELOPER_ERR, "The monitor video id passed to easyApp was bad, saw " + monitorVideoId);
99
return false;
100
}
101
102
for (i in videoIds) {
103
if (!videoIds.hasOwnProperty(i)) {
104
continue;
105
}
106
var name = videoIds[i];
107
if (!document.getElementById(name)) {
108
easyrtc.showError(easyrtc.errCodes.DEVELOPER_ERR, "The caller video id '" + name + "' passed to easyApp was bad.");
109
return false;
110
}
111
}
112
return true;
113
}
114
115
116
function getCallerOfVideo(videoObject) {
117
return videoIdToCallerMap[videoObject.id];
118
}
119
120
function setCallerOfVideo(videoObject, callerEasyrtcId) {
121
videoIdToCallerMap[videoObject.id] = callerEasyrtcId;
122
}
123
124
function videoIsFree(obj) {
125
var caller = getCallerOfVideo(obj);
126
return (caller === "" || caller === null || caller === undefined);
127
}
128
129
function getIthVideo(i) {
130
if (videoIdsP[i]) {
131
return document.getElementById(videoIdsP[i]);
132
}
133
else {
134
return null;
135
}
136
}
137
138
function showVideo(video, stream) {
139
easyrtc.setVideoObjectSrc(video, stream);
140
if (video.style.visibility) {
141
video.style.visibility = 'visible';
142
}
143
}
144
145
function hideVideo(video) {
146
easyrtc.setVideoObjectSrc(video, "");
147
video.style.visibility = "hidden";
148
}
149
150
if (!validateVideoIds(monitorVideoId, videoIdsP)) {
151
throw "bad video element id";
152
}
153
154
if (monitorVideoId) {
155
document.getElementById(monitorVideoId).muted = "muted";
156
}
157
158
easyrtc.addEventListener("roomOccupants", 
159
function(eventName, eventData) {
160
var i;
161
for (i = 0; i < numPEOPLE; i++) {
162
var video = getIthVideo(i);
163
if (!videoIsFree(video)) {
164
if( !easyrtc.isPeerInAnyRoom(getCallerOfVideo(video))){
165
if( onHangup ) {
166
onHangup(getCallerOfVideo(video), i);
167
}
168
setCallerOfVideo(video, null);
169
}
170
}
171
}
172
}
173
);
174
175
/** Sets an event handler that gets called when an incoming MediaStream is assigned 
176
* to a video object. The name is poorly chosen and reflects a simpler era when you could
177
* only have one media stream per peer connection.
178
* @function
179
* @memberOf Easyrtc_App
180
* @param {Function} cb has the signature function(easyrtcid, slot){}
181
* @example
182
*   easyrtc.setOnCall( function(easyrtcid, slot){
183
*      console.log("call with " + easyrtcid + "established");
184
*   });
185
*/
186
easyrtc.setOnCall = function(cb) {
187
onCall = cb;
188
};
189
190
/** Sets an event handler that gets called when a call is ended.
191
* it's only purpose (so far) is to support transitions on video elements.
192
x     * this function is only defined after easyrtc.easyApp is called.
193
* The slot is parameter is the index into the array of video ids.
194
* Note: if you call easyrtc.getConnectionCount() from inside your callback
195
* it's count will reflect the number of connections before the hangup started.
196
* @function
197
* @memberOf Easyrtc_App
198
* @param {Function} cb has the signature function(easyrtcid, slot){}
199
* @example
200
*   easyrtc.setOnHangup( function(easyrtcid, slot){
201
*      console.log("call with " + easyrtcid + "ended");
202
*   });
203
*/
204
easyrtc.setOnHangup = function(cb) {
205
onHangup = cb;
206
};
207
208
/** 
209
* Get the easyrtcid of the ith caller, starting at 0.
210
* @function
211
* @memberOf Easyrtc_App
212
* @param {number} i
213
* @returns {String}
214
*/
215
easyrtc.getIthCaller = function(i) {
216
if (i < 0 || i >= videoIdsP.length) {
217
return null;
218
}
219
var vid = getIthVideo(i);
220
return getCallerOfVideo(vid);
221
};
222
223
/** 
224
* This is the complement of getIthCaller. Given an easyrtcid,
225
* it determines which slot the easyrtc is in.
226
* @function
227
* @memberOf Easyrtc_App
228
* @param {string} easyrtcid 
229
* @returns {number} or -1 if the easyrtcid is not a caller.
230
*/
231
easyrtc.getSlotOfCaller = function(easyrtcid) {
232
var i;
233
for (i = 0; i < numPEOPLE; i++) {
234
if (easyrtc.getIthCaller(i) === easyrtcid) {
235
return i;
236
}
237
}
238
return -1; // caller not connected
239
};
240
241
easyrtc.setOnStreamClosed(function(caller) {
242
var i;
243
for (i = 0; i < numPEOPLE; i++) {
244
var video = getIthVideo(i);
245
if (getCallerOfVideo(video) === caller) {
246
hideVideo(video);
247
setCallerOfVideo(video, "");
248
if (onHangup) {
249
onHangup(caller, i);
250
}
251
}
252
}
253
});
254
255
//
256
// Only accept incoming calls if we have a free video object to display
257
// them in.
258
//
259
easyrtc.setAcceptChecker(function(caller, helper) {
260
var i;
261
for (i = 0; i < numPEOPLE; i++) {
262
var video = getIthVideo(i);
263
if (videoIsFree(video)) {
264
helper(true);
265
return;
266
}
267
}
268
helper(false);
269
});
270
271
easyrtc.setStreamAcceptor(function(caller, stream) {
272
var i;
273
if (easyrtc.debugPrinter) {
274
easyrtc.debugPrinter("stream acceptor called");
275
}
276
277
var video;
278
279
for (i = 0; i < numPEOPLE; i++) {
280
video = getIthVideo(i);
281
if (getCallerOfVideo(video) === caller) {
282
showVideo(video, stream);
283
if (onCall) {
284
onCall(caller, i);
285
}
286
return;
287
}
288
}
289
290
for (i = 0; i < numPEOPLE; i++) {
291
video = getIthVideo(i);
292
if (videoIsFree(video)) {
293
setCallerOfVideo(video, caller);
294
if (onCall) {
295
onCall(caller, i);
296
}
297
showVideo(video, stream);
298
return;
299
}
300
}
301
//
302
// no empty slots, so drop whatever caller we have in the first slot and use that one.
303
//
304
video = getIthVideo(0);
305
if (video) {
306
easyrtc.hangup(getCallerOfVideo(video));
307
showVideo(video, stream);
308
if (onCall) {
309
onCall(caller, 0);
310
}
311
}
312
313
setCallerOfVideo(video, caller);
314
});
315
316
var addControls, parentDiv, closeButton, i;
317
if (autoAddCloseButtons) {
318
319
addControls = function(video) {
320
parentDiv = video.parentNode;
321
setCallerOfVideo(video, "");
322
closeButton = document.createElement("div");
323
closeButton.className = "easyrtc_closeButton";
324
closeButton.onclick = function() {
325
if (getCallerOfVideo(video)) {
326
easyrtc.hangup(getCallerOfVideo(video));
327
hideVideo(video);
328
setCallerOfVideo(video, "");
329
}
330
};
331
parentDiv.appendChild(closeButton);
332
};
333
334
for (i = 0; i < numPEOPLE; i++) {
335
addControls(getIthVideo(i));
336
}
337
}
338
339
var monitorVideo = null;
340
if (easyrtc.videoEnabled && monitorVideoId !== null) {
341
monitorVideo = document.getElementById(monitorVideoId);
342
if (!monitorVideo) {
343
console.error("Programmer error: no object called " + monitorVideoId);
344
return;
345
}
346
monitorVideo.muted = "muted";
347
monitorVideo.defaultMuted = true;
348
}
349
}
350
351
/**
352
* Provides a layer on top of the easyrtc.initMediaSource and easyrtc.connect, assign the local media stream to
353
* the video object identified by monitorVideoId, assign remote video streams to
354
* the video objects identified by videoIds, and then call onReady. One of it's
355
* side effects is to add hangup buttons to the remote video objects, buttons
356
* that only appear when you hover over them with the mouse cursor. This method will also add the
357
* easyrtcMirror class to the monitor video object so that it behaves like a mirror.
358
* @function
359
* @memberOf Easyrtc_App
360
*  @param {String} applicationName - name of the application.
361
*  @param {String} monitorVideoId - the id of the video object used for monitoring the local stream.
362
*  @param {Array} videoIds - an array of video object ids (strings)
363
*  @param {Function} onReady - a callback function used on success. It is called with the easyrtcId this peer is known to the server as.
364
*  @param {Function} onFailure - a callback function used on failure (failed to get local media or a connection of the signaling server).
365
*  @example
366
*     easyrtc.easyApp('multiChat', 'selfVideo', ['remote1', 'remote2', 'remote3'],
367
*              function(easyrtcId){
368
*                  console.log("successfully connected, I am " + easyrtcId);
369
*              },
370
*              function(errorCode, errorText){
371
*                  console.log(errorText);
372
*              });
373
*/
374
easyrtc.easyApp = function(applicationName, monitorVideoId, videoIds, onReady, onFailure) {
375
376
var gotMediaCallback = null,
377
gotConnectionCallback = null;
378
379
easyAppBody(monitorVideoId, videoIds);
380
381
easyrtc.setGotMedia = function(gotMediaCB) {
382
gotMediaCallback = gotMediaCB;
383
};
384
385
//
386
// try to restablish broken connections that weren't caused by a hangup
387
//
388
easyrtc.setPeerClosedListener( function(easyrtcid) {
389
setTimeout( function() {
390
if( easyrtc.getSlotOfCaller(easyrtcid)  >= 0 && easyrtc.isPeerInAnyRoom(easyrtcid)) {
391
easyrtc.call(easyrtcid, function(){}, function() {}, function(){});
392
}
393
}, 1000);
394
});
395
396
/** Sets an event handler that gets called when a connection to the signaling
397
* server has or has not been made. Can only be called after calling easyrtc.easyApp.
398
* @function
399
* @memberOf Easyrtc_App
400
* @param {Function} gotConnectionCB has the signature (gotConnection, errorText)
401
* @example
402
*    easyrtc.setGotConnection( function(gotConnection, errorText){
403
*        if( gotConnection ){
404
*            console.log("Successfully connected to signaling server");
405
*        }
406
*        else{
407
*            console.log("Failed to connect to signaling server because: " + errorText);
408
*        }
409
*    });
410
*/
411
easyrtc.setGotConnection = function(gotConnectionCB) {
412
gotConnectionCallback = gotConnectionCB;
413
};
414
415
function nextInitializationStep(/* token */) {
416
if (gotConnectionCallback) {
417
gotConnectionCallback(true, "");
418
}
419
onReady(easyrtc.myEasyrtcid);
420
}
421
422
function postGetUserMedia() {
423
if (gotMediaCallback) {
424
gotMediaCallback(true, null);
425
}
426
if (monitorVideoId !== null) {
427
easyrtc.setVideoObjectSrc(document.getElementById(monitorVideoId), easyrtc.getLocalStream());
428
}
429
function connectError(errorCode, errorText) {
430
if (gotConnectionCallback) {
431
gotConnectionCallback(false, errorText);
432
}
433
else if (onFailure) {
434
onFailure(easyrtc.errCodes.CONNECT_ERR, errorText);
435
}
436
else {
437
easyrtc.showError(easyrtc.errCodes.CONNECT_ERR, errorText);
438
}
439
}
440
441
easyrtc.connect(applicationName, nextInitializationStep, connectError);
442
}
443
444
var stream = easyrtc.getLocalStream(null);
445
if (stream) {
446
postGetUserMedia();
447
}
448
else {
449
easyrtc.initMediaSource(
450
postGetUserMedia,
451
function(errorCode, errorText) {
452
if (gotMediaCallback) {
453
gotMediaCallback(false, errorText);
454
}
455
else if (onFailure) {
456
onFailure(easyrtc.errCodes.MEDIA_ERR, errorText);
457
}
458
else {
459
easyrtc.showError(easyrtc.errCodes.MEDIA_ERR, errorText);
460
}
461
},
462
null // default stream
463
);
464
}
465
};
466
467
return easyrtc;
468
469
})); // end of module wrapper