/** @class
*@version 0.9.0
*<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) 2013, 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 = {};
/** Error codes that the easyRTC will use in the errCode field of error object passed
* to error handler set by easyRTC.setOnError. The error codes are short printable strings.
* @type Dictionary.
*/
easyRTC.errCodes = {
BAD_NAME: "BAD_NAME", // a user name wasn't of the desired form
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 occured 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",
SIP_ERR: "SIP_ERR" //something went wrong with a sip session
};
easyRTC.apiVersion = "0.8.0";
/** Regular expression pattern for user ids. This will need modification to support non US character sets */
easyRTC.userNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,30}[a-zA-Z0-9]$/;
/** @private */
easyRTC.userName = null;
/** @private */
easyRTC.loggingOut = false;
/** @private */
easyRTC.disconnecting = false;
/** @private */
easyRTC.localStream = null;
/** @private */
easyRTC.videoFeatures = true; // default video
/** @private */
easyRTC.isMozilla = navigator.mozGetUserMedia ? true : false;
/** @private */
easyRTC.audioEnabled = true;
/** @private */
easyRTC.videoEnabled = true;
/** @private */
easyRTC.datachannelName = "dc";
/** @private */
easyRTC.debugPrinter = null;
/** @private */
easyRTC.myEasyrtcid = "";
/** @private */
easyRTC.oldConfig = {};
/** @private */
easyRTC.offersPending = {};
/** private */
easyRTC.cookieId = "easyrtcsid";
/** @private */
easyRTC.filterOutTurn = false;
easyRTC.sipUA = null; // JSSIP user agent if SIP is supported.
/** The height of the local media stream video in pixels. This field is set an indeterminate period
* of time after easyRTC.initMediaSource succeeds.
*/
easyRTC.nativeVideoHeight = 0;
/** The width of the local media stream video in pixels. This field is set an indeterminate period
* of time after easyRTC.initMediaSource succeeds.
*/
easyRTC.nativeVideoWidth = 0;
/** @private */
easyRTC.apiKey = "cmhslu5vo57rlocg"; // default key for now
/** The name user is in. This only applies to room oriented applications and is set at the same
* time a token is received.
*/
easyRTC.room = null;
/** 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)) {
* alert("Bad user name");
* }
*/
easyRTC.isNameValid = function(name) {
return easyRTC.userNamePattern.test(name);
};
/** This function allows you to select whether turn servers are filtered out of the
* peer connection configuration. It's primary purpose to is to allow you to easily determine
* whether you need a turn server to form a connection.
* @param {boolean} filterOut is true to filter out turn servers, false to include them.
* @returns {undefined}
*/
easyRTC.setFilterOutTurn = function(filterOutTurn) {
easyRTC.filterOutTurn = !!filterOutTurn;
}
/**
* 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 {type} cookieId
*/
easyRTC.setCookieId = function(cookieId) {
easyRTC.cookieId = cookieId;
};
/** 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
* @example
* easyRTC.setVideoDims(1280,720);
* @example
* easyRTC.setVideoDims();
*/
easyRTC.setVideoDims = function(width, height) {
if (!width) {
width = 1280;
height = 720;
}
easyRTC.videoFeatures = {
mandatory: {
minWidth: width,
minHeight: height,
maxWidth: width,
maxHeight: height
},
optional: []
};
};
/** 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.
* @example
* easyRTC.setScreenCapture();
*/
easyRTC.setScreenCapture = function() {
easyRTC.videoFeatures = {
mandatory: {
chromeMediaSource: "screen"
},
optional: []
};
};
/** Set the API Key. The API key identifies the owner of the application.
* The API key has no meaning for the Open Source server.
* @param {String} key
* @example
* easyRTC.setApiKey('cmhslu5vo57rlocg');
*/
easyRTC.setApiKey = function(key) {
easyRTC.apiKey = key;
};
/** Set the application name. Applications can only communicate with other applications
* that share the sname API Key and application name. There is no predefined set of application
* names. Maximum length is
* @param {String} name
* @example
* easyRTC.setApplicationName('simpleAudioVideo');
*/
easyRTC.setApplicationName = function(name) {
easyRTC.applicationName = name;
};
/** Setup the JsSIP User agent.
*
* @param {type} connectConfig is a dictionary that contains the following fields: { ws_servers, uri, password }
* @returns {undefined}
*/
easyRTC.setSipConfig = function(connectConfig)
{
easyRTC.sipConfig = connectConfig ? JSON.parse(JSON.stringify(connectConfig)) : null;
};
easyRTC.getSipConnectionCount = function() {
return 0;
}
/** 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);
*/
easyRTC.enableDebug = function(enable) {
if (enable) {
easyRTC.debugPrinter = function(message) {
var stackString = new Error().stack;
var srcLine = "location unknown";
if (stackString) {
var stackFrameStrings = new Error().stack.split('\n');
srcLine = "";
if (stackFrameStrings.length >= 3) {
srcLine = stackFrameStrings[2];
}
}
console.log("debug " + (new Date()).toISOString() + " : " + message + " [" + srcLine + "]");
};
}
else {
easyRTC.debugPrinter = null;
}
};
/**
* Determines if the local browser supports WebRTC GetUserMedia (access to camera and microphone).
* @returns {Boolean} True getUserMedia is supported.
*/
easyRTC.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.
*/
easyRTC.supportsPeerConnection = function() {
if (!easyRTC.supportsGetUserMedia()) {
return false;
}
if (!window.RTCPeerConnection) {
return false;
}
try {
easyRTC.createRTCPeerConnection({"iceServers": []}, null);
} catch (oops) {
return false;
}
return true;
};
/** @private
* @param pc_config ice configuration array
* @param optionalStuff peer constraints.
*/
easyRTC.createRTCPeerConnection = function(pc_config, optionalStuff) {
if (RTCPeerConnection) {
return new RTCPeerConnection(pc_config, optionalStuff);
}
else {
throw "Your browser doesn't support webRTC (RTCPeerConnection)";
}
};
//
//
//
if (easyRTC.isMozilla) {
easyRTC.datachannelConstraints = {};
}
else {
easyRTC.datachannelConstraints = {
reliable: false
};
}
/** @private */
easyRTC.haveAudioVideo = {
audio: false,
video: false
};
/** @private */
easyRTC.dataEnabled = false;
/** @private */
easyRTC.serverPath = null;
/** @private */
easyRTC.loggedInListener = null;
/** @private */
easyRTC.onDataChannelOpen = null;
/** @private */
easyRTC.onDataChannelClose = null;
/** @private */
easyRTC.lastLoggedInList = {};
/** @private */
easyRTC.receivePeerCB = null;
/** @private */
easyRTC.receiveServerCB = null;
/** @private */
easyRTC.appDefinedFields = {};
/** @private */
easyRTC.updateConfigurationInfo = function() {
}; // dummy placeholder for when we aren't connected
//
//
// easyRTC.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
// pc: RTCPeerConnection
// mediaStream: mediaStream
// function callSuccessCB(string) - see the easyRTC.call documentation.
// function callFailureCB(string) - see the easyRTC.call documentation.
// function wasAcceptedCB(boolean,string) - see the easyRTC.call documentation.
// }
//
/** @private */
easyRTC.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 */
easyRTC.acceptancePending = {};
/*
* the maximum length of the appDefinedFields. This is defined on the
* server side as well, so changing it here alone is insufficient.
*/
/** @private */
var maxAppDefinedFieldsLength = 128;
/**
* Disconnect from the easyRTC server.
*/
easyRTC.disconnect = function() {
};
/** @private
* @param caller
* @param helper
*/
easyRTC.acceptCheck = function(caller, helper) {
helper(true);
};
/** @private
* @param easyrtcid
* @param stream
*/
easyRTC.streamAcceptor = function(easyrtcid, stream) {
};
/** @private
* @param easyrtcid
*/
easyRTC.onStreamClosed = function(easyrtcid) {
};
/** @private
* @param easyrtcid
*/
easyRTC.callCancelled = function(easyrtcid) {
};
/** Provide a set of application defined fields that will be part of this instances
* configuration information. This data will get sent to other peers via the websocket
* path.
* @param {type} fields just be JSON serializable to a length of not more than 128 bytes.
* @example
* easyRTC.setAppDefinedFields( { favorite_alien:'Mr Spock'});
* easyRTC.setLoggedInListener( function(list) {
* for( var i in list ) {
* console.log("easyrtcid=" + i + " favorite alien is " + list[i].appDefinedFields.favorite_alien);
* }
* });
*/
easyRTC.setAppDefinedFields = function(fields) {
var fieldAsString = JSON.stringify(fields);
if (JSON.stringify(fieldAsString).length <= 128) {
easyRTC.appDefinedFields = JSON.parse(fieldAsString);
easyRTC.updateConfigurationInfo();
}
else {
alert("Developer error: your appDefinedFields were too big");
}
};
/** Default error reporting function. The default implementation displays error messages
* in a programatically created div with the id easyRTCErrorDialog. The div has title
* component with a classname of easyRTCErrorDialog_title. The error messages get added to a
* container with the id easyRTCErrorDialog_body. Each error message is a text node inside a div
* with a class of easyRTCErrorDialog_element. There is an "okay" button with the className of easyRTCErrorDialog_okayButton.
* @param {String} messageCode An error message code
* @param {String} message the error message text without any markup.
* @example
* easyRTC.showError("BAD_NAME", "Invalid username");
*/
easyRTC.showError = function(messageCode, message) {
easyRTC.onError({errCode: messageCode, errText: message});
};
/** @private
* @param errorObject
*/
easyRTC.onError = function(errorObject) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("saw error " + errorObject.errText);
}
var errorDiv = document.getElementById('easyRTCErrorDialog');
var errorBody;
if (!errorDiv) {
errorDiv = document.createElement("div");
errorDiv.id = 'easyRTCErrorDialog';
var title = document.createElement("div");
title.innerHTML = "Error messages";
title.className = "easyRTCErrorDialog_title";
errorDiv.appendChild(title);
errorBody = document.createElement("div");
errorBody.id = "easyRTCErrorDialog_body";
errorDiv.appendChild(errorBody);
var clearButton = document.createElement("button");
clearButton.appendChild(document.createTextNode("Okay"));
clearButton.className = "easyRTCErrorDialog_okayButton";
clearButton.onclick = function() {
errorBody.innerHTML = ""; // remove all inner nodes
errorDiv.style.display = "none";
};
errorDiv.appendChild(clearButton);
document.body.appendChild(errorDiv);
}
;
errorBody = document.getElementById("easyRTCErrorDialog_body");
var messageNode = document.createElement("div");
messageNode.className = 'easyRTCErrorDialog_element';
messageNode.appendChild(document.createTextNode(errorObject.errText));
errorBody.appendChild(messageNode);
errorDiv.style.display = "block";
};
//
// add the style sheet to the head of the document. That way, developers
// can overide it.
//
(function() {
//
// check to see if we already have an easyrtc.css file loaded
// if we do, we can exit immediately.
//
var links = document.getElementsByTagName("link");
for (var cssindex in links) {
var css = links[cssindex];
if (css.href && (css.href.match("\/easyrtc.css") || css.href.match("\/easyrtc.css\?"))) {
return;
}
}
//
// add the easyrtc.css file since it isn't present
//
var easySheet = document.createElement("link");
easySheet.setAttribute("rel", "stylesheet");
easySheet.setAttribute("type", "text/css");
easySheet.setAttribute("href", "/easyrtc/easyrtc.css");
var headSection = document.getElementsByTagName("head")[0];
var firstHead = headSection.childNodes[0];
headSection.insertBefore(easySheet, firstHead);
})();
/** @private */
easyRTC.videoBandwidthString = "b=AS:50"; // default video band width is 50kbps
//
// easyRTC.createObjectURL builds a URL from a media stream.
// Arguments:
// mediaStream - a media stream object.
// The video object in Chrome expects a URL.
//
/** @private
* @param mediaStream */
easyRTC.createObjectURL = function(mediaStream) {
if (window.URL && window.URL.createObjectURL) {
return window.URL.createObjectURL(mediaStream);
}
else if (window.webkitURL && window.webkitURL.createObjectURL) {
return window.webkit.createObjectURL(mediaStream);
}
else {
var errMessage = "Your browsers does not support URL.createObjectURL.";
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("saw exception " + errMessage);
}
throw errMessage;
}
};
/**
* A convenience function to ensure that a string doesn't have symbols that will be interpreted by HTML.
* @param {String} idString
* @return {String}
* @example
* console.log( easyRTC.cleanId('&hello'));
*/
easyRTC.cleanId = function(idString) {
var MAP = {
'&': '&',
'<': '<',
'>': '>'
};
return idString.replace(/[&<>]/g, function(c) {
return MAP[c];
});
};
/** Set the callback that will be invoked when the list of people logged in changes.
* The callback expects to receive a map whose ideas are easyrtcids and whose values are in turn maps
* supplying user specific information. The inner maps have the following keys:
* userName, applicationName, browserFamily, browserMajor, osFamily, osMajor, deviceFamily.
* The callback also receives a boolean that indicates whether the owner is the primary owner of a room.
* @param {Function} listener
* @example
* easyRTC.setLoggedInListener( function(list, isPrimaryOwner) {
* for( var i in list ) {
* ("easyrtcid=" + i + " belongs to user " + list[i].userName);
* }
* });
*/
easyRTC.setLoggedInListener = function(listener) {
easyRTC.loggedInListener = listener;
};
/**
* Sets a callback that is called when a data channel is open and ready to send data.
* The callback will be called with an easyrtcid as it's sole argument.
* @param {Function} listener
* @example
* easyRTC.setDataChannelOpenListener( function(easyrtcid) {
* easyRTC.sendDataP2P(easyrtcid, "hello");
* });
*/
easyRTC.setDataChannelOpenListener = function(listener) {
easyRTC.onDataChannelOpen = listener;
};
/** Sets a callback that is called when a previously open data channel closes.
* The callback will be called with an easyrtcid as it's sole argument.
* @param {Function} listener
* @example
* easyRTC.setDataChannelCloseListener( function(easyrtcid) {
* ("No longer connected to " + easyRTC.idToName(easyrtcid));
* });
*/
easyRTC.setDataChannelCloseListener = function(listener) {
easyRTC.onDataChannelClose = listener;
};
/** Returns the number of live peer connections the client has.
* @return {Number}
* @example
* ("You have " + easyRTC.getConnectionCount() + " peer connections");
*/
easyRTC.getConnectionCount = function() {
var count = 0;
for (var i in easyRTC.peerConns) {
if (easyRTC.peerConns[i].startedAV) {
count++;
}
}
return count + easyRTC.getSipConnectionCount();
};
/** Sets whether audio is transmitted by the local user in any subsequent calls.
* @param {Boolean} enabled true to include audio, false to exclude audio. The default is true.
* @example
* easyRTC.enableAudio(false);
*/
easyRTC.enableAudio = function(enabled) {
easyRTC.audioEnabled = enabled;
};
/**
*Sets whether video is transmitted by the local user in any subsequent calls.
* @param {Boolean} enabled - true to include video, false to exclude video. The default is true.
* @example
* easyRTC.enableVideo(false);
*/
easyRTC.enableVideo = function(enabled) {
easyRTC.videoEnabled = enabled;
};
/**
* Sets whether webrtc data channels are used to send inter-client messages.
* This is only the messages that applications explicitly send to other applications, not the webrtc signalling messages.
* @param {Boolean} enabled true to use data channels, false otherwise. The default is false.
* @example
* easyRTC.enableDataChannels(true);
*/
easyRTC.enableDataChannels = function(enabled) {
easyRTC.dataEnabled = enabled;
};
/**
* Returns a URL for your local camera and microphone.
* It can be called only after easyRTC.initMediaSource has succeeded.
* It returns a url that can be used as a source by the chrome video element or the <canvas> element.
* @return {URL}
* @example
* document.getElementById("myVideo").src = easyRTC.getLocalStreamAsUrl();
*/
easyRTC.getLocalStreamAsUrl = function() {
if (easyRTC.localStream === null) {
alert("Developer error: attempt to get a mediastream without invoking easyRTC.initMediaSource successfully");
}
return easyRTC.createObjectURL(easyRTC.localStream);
};
/**
* Returns a media stream for your local camera and microphone.
* It can be called only after easyRTC.initMediaSource has succeeded.
* It returns a stream that can be used as an argument to easyRTC.setVideoObjectSrc.
* @return {MediaStream}
* @example
* easyRTC.setVideoObjectSrc( document.getElementById("myVideo"), easyRTC.getLocalStream());
*/
easyRTC.getLocalStream = function() {
return easyRTC.localStream;
};
/**
* Sends a easyrtcCmd command to the server, rather than another client.
* @param {string} instruction
* @param {object} data
* @returns {undefined}
*/
easyRTC.sendEasyrtcCmd = function(instruction, data) {
if (!easyRTC.webSocketConnected) {
throw "Attempt to send message without a valid connection to the server."
}
else {
var dataToShip = {
msgType: instruction,
msgData: data
};
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("sending server message " + JSON.stringify(dataToShip));
}
easyRTC.webSocket.json.emit("easyrtcCmd", dataToShip);
}
};
easyRTC.sendServerMessage = function(instruction, data) {
if (!easyRTC.webSocketConnected) {
throw "Attempt to send message without a valid connection to the server."
}
else {
var dataToShip = {
msgType: instruction,
msgData: data
};
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("sending server message " + JSON.stringify(dataToShip));
}
easyRTC.webSocket.json.emit("message", dataToShip);
}
};
/** Clears the media stream on a video object.
*
* @param {type} element the video object.
* @returns {undefined}
*/
easyRTC.clearMediaStream = function(element) {
if (typeof element.srcObject !== 'undefined') {
element.srcObject = null;
} else if (typeof element.mozSrcObject !== 'undefined') {
element.mozSrcObject = null;
} else if (typeof element.src !== 'undefined') {
element.src = null;
} else {
}
};
/**
* Sets a video or audio object from a media stream.
* Chrome uses the src attribute and expects a URL, while firefox
* uses the mozSrcObject and expects a stream. This procedure hides
* that from you.
* If the media stream is from a local webcam, you may want to add the
* easyRTCMirror class to the video object so it looks like a proper mirror.
* The easyRTCMirror class is defined in easyrtc.css, which is automatically added
* when you add the easyrtc.js file to an HTML file.
* @param {DOMObject} videoObject an HTML5 video object
* @param {MediaStream} stream a media stream as returned by easyRTC.getLocalStream or your stream acceptor.
* @example
* easyRTC.setVideoObjectSrc( document.getElementById("myVideo"), easyRTC.getLocalStream());
*
*/
easyRTC.setVideoObjectSrc = function(videoObject, stream) {
if (stream && stream != "") {
videoObject.autoplay = true;
attachMediaStream(videoObject, stream);
videoObject.play();
}
else {
easyRTC.clearMediaStream(videoObject);
}
};
/** @private
* @param {String} x */
easyRTC.formatError = function(x) {
if (x === null || typeof x === 'undefined') {
message = "null";
}
if (typeof x === 'string') {
return x;
}
else if (x.type && x.description) {
return x.type + " : " + x.description;
}
else if (typeof x === 'object') {
try {
return JSON.stringify(x);
}
catch (oops) {
var result = "{";
for (var name in x) {
if (typeof x[name] === 'string') {
result = result + name + "='" + x[name] + "' ";
}
}
result = result + "}";
return result;
}
}
else {
return "Strange case";
}
};
/** Initializes your access to a local camera and microphone.
* Failure could be caused a browser that didn't support webrtc, or by the user
* not granting permission.
* If you are going to call easyRTC.enableAudio or easyRTC.enableVideo, you need to do it before
* calling easyRTC.initMediaSource.
* @param {Function} successCallback - will be called when the media source is ready.
* @param {Function} errorCallback - is called with a message string if the attempt to get media failed.
* @example
* easyRTC.initMediaSource(
* function() {
* easyRTC.setVideoObjectSrc( document.getElementById("mirrorVideo"), easyRTC.getLocalStream());
* },
* function() {
* easyRTC.showError("no-media", "Unable to get local media");
* });
*
*/
easyRTC.initMediaSource = function(successCallback, errorCallback) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("about to request local media");
}
if (!window.getUserMedia) {
errorCallback("Your browser doesn't appear to support WebRTC.");
}
if (errorCallback === null) {
errorCallback = function(x) {
var message = "easyRTC.initMediaSource: " + easyRTC.formatError(x);
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter(message);
}
easyRTC.showError("no-media", message);
};
}
if (!successCallback) {
alert("easyRTC.initMediaSource not supplied a successCallback");
return;
}
var mode = {'audio': (easyRTC.audioEnabled ? true : false),
'video': ((easyRTC.videoEnabled) ? (easyRTC.videoFeatures) : false)};
/** @private
* @param {Stream} stream
* */
var onUserMediaSuccess = function(stream) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("getUserMedia success callback entered");
}
// console.log("getusermedia succeeded");
// the below commented out checks were for Chrome. However, Mozilla nightly started
// implementing the getAudioTracks method as well, except their implementation appears to
// be asychronous, the audioTracks.length are zero initially and filled later.
//
// if (easyRTC.audioEnabled && stream.getAudioTracks) {
// var audioTracks = stream.getAudioTracks();
// if (!audioTracks || audioTracks.length === 0) {
// errorCallback("The application requested audio but the system didn't supply it");
// return;
// }
// }
// if (easyRTC.videoEnabled && stream.getVideoTracks) {
// var videoTracks = stream.getVideoTracks();
// if (!videoTracks || videoTracks.length === 0) {
// errorCallback("The application requested video but the system didn't supply it");
// return;
// }
// }
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("successfully got local media");
}
easyRTC.localStream = stream;
if (easyRTC.haveAudioVideo.video) {
var videoObj = document.createElement('video');
videoObj.muted = true;
var triesLeft = 30;
var tryToGetSize = function() {
if (videoObj.videoWidth > 0 || triesLeft < 0) {
easyRTC.nativeVideoWidth = videoObj.videoWidth;
easyRTC.nativeVideoHeight = videoObj.videoHeight;
if (easyRTC.videoFeatures.mandatory &&
easyRTC.videoFeatures.mandatory.minHeight &&
(easyRTC.nativeVideoHeight != easyRTC.videoFeatures.mandatory.minHeight ||
easyRTC.nativeVideoWidth != easyRTC.videoFeatures.mandatory.minWidth)) {
easyRTC.showError(easyRTC.errCodes.MEDIA_WARNING,
"requested video size of " + easyRTC.videoFeatures.mandatory.minWidth + "x" + easyRTC.videoFeatures.mandatory.minHeight +
" but got size of " + easyRTC.nativeVideoWidth + "x" + easyRTC.nativeVideoHeight);
}
easyRTC.setVideoObjectSrc(videoObj, "");
if (videoObj.removeNode) {
videoObj.removeNode(true);
}
else {
var ele = document.createElement('div');
ele.appendChild(videoObj);
ele.removeChild(videoObj);
}
// easyRTC.updateConfigurationInfo();
if (successCallback) {
successCallback();
}
}
else {
triesLeft -= 1;
setTimeout(tryToGetSize, 100);
}
};
easyRTC.setVideoObjectSrc(videoObj, stream);
tryToGetSize();
}
else {
easyRTC.updateConfigurationInfo();
if (successCallback) {
successCallback();
}
}
};
/** @private
* @param {String} error
*/
var onUserMediaError = function(error) {
console.log("getusermedia failed");
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("failed to get local media");
}
if (errorCallback) {
errorCallback("Failed to get access to local media. Error code was " + error.code + ".");
}
easyRTC.localStream = null;
easyRTC.haveAudioVideo = {
audio: false,
video: false
};
easyRTC.updateConfigurationInfo();
};
if (!easyRTC.audioEnabled && !easyRTC.videoEnabled) {
onUserMediaError("At least one of audio and video must be provided");
return;
}
/** @private */
easyRTC.haveAudioVideo = {
audio: easyRTC.audioEnabled,
video: easyRTC.videoEnabled
};
if (easyRTC.videoEnabled || easyRTC.audioEnabled) {
//
// getUserMedia usually fails the first time I call it. I suspect it's a page loading
// issue. So I'm going to try adding a 1 second delay to allow things to settle down first.
//
setTimeout(function() {
try {
getUserMedia(mode, onUserMediaSuccess, onUserMediaError);
} catch (e) {
errorCallback("getUserMedia failed with exception: " + e.message);
}
}, 1000);
}
else {
onUserMediaSuccess(null);
}
};
/**
* easyRTC.setAcceptChecker sets the callback used to decide whether to accept or reject an incoming call.
* @param {Function} acceptCheck takes the arguments (callerEasyrtcid, function():boolean ) {}
* The acceptCheck callback is passed (as it's second argument) a function that should be called with either
* a true value (accept the call) or false value( reject the call).
* @example
* easyRTC.setAcceptChecker( function(easyrtcid, acceptor) {
* if( easyRTC.idToName(easyrtcid) === 'Fred' ) {
* acceptor(true);
* }
* else if( easyRTC.idToName(easyrtcid) === 'Barney' ) {
* setTimeout( function() { acceptor(true)}, 10000);
* }
* else {
* acceptor(false);
* }
* });
*/
easyRTC.setAcceptChecker = function(acceptCheck) {
easyRTC.acceptCheck = acceptCheck;
};
/**
* easyRTC.setStreamAcceptor sets a callback to receive media streams from other peers, independent
* of where the call was initiated (caller or callee).
* @param {Function} acceptor takes arguments (caller, mediaStream)
* @example
* easyRTC.setStreamAcceptor(function(easyrtcid, stream) {
* document.getElementById('callerName').innerHTML = easyRTC.idToName(easyrtcid);
* easyRTC.setVideoObjectSrc( document.getElementById("callerVideo"), stream);
* });
*/
easyRTC.setStreamAcceptor = function(acceptor) {
easyRTC.streamAcceptor = acceptor;
};
/** Sets the easyRTC.onError field to a user specified function.
* @param {Function} errListener takes an object of the form { errCode: String, errText: String}
* @example
* easyRTC.setOnError( function(errorObject) {
* document.getElementById("errMessageDiv").innerHTML += errorObject.errText;
* });
*/
easyRTC.setOnError = function(errListener) {
easyRTC.onError = errListener;
};
/**
* Sets the callCancelled callback. This will be called when a remote user
* initiates a call to you, but does a "hangup" before you have a chance to get his video stream.
* @param {Function} callCancelled takes an easyrtcid as an argument and a boolean that indicates whether
* the call was explicitly cancelled remotely (true), or actually accepted by the user attempting a call to
* the same party.
* @example
* easyRTC.setCallCancelled( function(easyrtcid, explicitlyCancelled) {
* if( explicitlyCancelled ) {
* console..log(easyrtc.idToName(easyrtcid) + " stopped trying to reach you");
* }
* else {
* console.log("Implicitly called " + easyrtc.idToName(easyrtcid));
* }
* });
*/
easyRTC.setCallCancelled = function(callCancelled) {
easyRTC.callCancelled = callCancelled;
};
/** Sets a callback to receive notification of a media stream closing. The usual
* use of this is to clear the source of your video object so you aren't left with
* the last frame of the video displayed on it.
* @param {Function} onStreamClosed takes an easyrtcid as it's first parameter.
* @example
* easyRTC.setOnStreamClosed( function(easyrtcid) {
* easyRTC.setVideoObjectSrc( document.getElementById("callerVideo"), "");
* ( easyRTC.idToName(easyrtcid) + " went away");
* });
*/
easyRTC.setOnStreamClosed = function(onStreamClosed) {
easyRTC.onStreamClosed = onStreamClosed;
};
/**
* Sets the bandwidth for sending video data.
* Setting the rate too low will cause connection attempts to fail. 40 is probably good lower limit.
* The default is 50. A value of zero will remove bandwidth limits.
* @param {Number} kbitsPerSecond is rate in kilobits per second.
* @example
* easyRTC.setVideoBandwidth( 40);
*/
easyRTC.setVideoBandwidth = function(kbitsPerSecond) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("video bandwidth set to " + kbitsPerSecond + " kbps");
}
if (kbitsPerSecond > 0) {
easyRTC.videoBandwidthString = "b=AS:" + kbitsPerSecond;
}
else {
easyRTC.videoBandwidthString = "";
}
};
/**
* Sets a listener for data sent from another client (either peer to peer or via websockets).
* @param {Function} listener has the signature (easyrtcid, data)
* @example
* easyRTC.setPeerListener( function(easyrtcid, data) {
* ("From " + easyRTC.idToName(easyrtcid) +
* " sent the follow data " + JSON.stringify(data));
* });
*
*
*/
easyRTC.setPeerListener = function(listener) {
easyRTC.receivePeerCB = listener;
};
/**
* Sets a listener for data sent from another client (either peer to peer or via websockets).
* @deprecated This is now a synonym for setPeerListener.
* @param {Function} listener has the signature (easyrtcid, data)
* @example
* easyRTC.setDataListener( function(easyrtcid, data) {
* ("From " + easyRTC.idToName(easyrtcid) +
* " sent the follow data " + JSON.stringify(data));
* });
*
*
*/
easyRTC.setDataListener = easyRTC.setPeerListener;
/**
* Sets a listener for messages from the server.
* @param {Function} listener has the signature (data)
* @example
* easyRTC.setPeerListener( function(data) {
* ("From Server sent the follow data " + JSON.stringify(data));
* });
*/
easyRTC.setServerListener = function(listener) {
easyRTC.receiveServerCB = listener;
};
/**
* Sets the url of the Socket server.
* The node.js server is great as a socket server, but it doesn't have
* all the hooks you'd like in a general web server, like PHP or Python
* plug-ins. By setting the serverPath your application can get it's regular
* pages from a regular webserver, but the easyRTC library can still reach the
* socket server.
* @param {DOMString} socketUrl
* @example
* easyRTC.setSocketUrl(":8080");
*/
easyRTC.setSocketUrl = function(socketUrl) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("webrtc signaling server URL set to " + socketUrl);
}
easyRTC.serverPath = socketUrl;
};
/**
* Sets the user name associated with the connection.
* @param {String} userName must obey standard identifier conventions.
* @returns {Boolean} true if the call succeeded, false if the username was invalid.
* @example
* if ( !easyRTC.setUserName("JohnSmith") ) {
* alert("bad user name);
*
*/
easyRTC.setUserName = function(userName) {
if (easyRTC.isNameValid(userName)) {
easyRTC.userName = userName;
return true;
}
else {
easyRTC.showError(easyRTC.errCodes.BAD_NAME, "Illegal username " + userName);
return false;
}
};
/**
* Sets the listener for socket disconnection by external (to the API) reasons.
* @param {Function} disconnectListener takes no arguments and is not called as a result of calling easyRTC.disconnect.
* @example
* easyRTC.setDisconnectListener(function() {
* easyRTC.showError("SYSTEM-ERROR", "Lost our connection to the socket server");
* });
*/
easyRTC.setDisconnectListener = function(disconnectListener) {
easyRTC.disconnectListener = disconnectListener;
};
/**
* Convert an easyrtcid to a user name. This is useful for labelling buttons and messages
* regarding peers.
* @param {String} easyrtcid
* @return {String}
* @example
* console.log(easyrtcid + " is actually " + easyRTC.idToName(easyrtcid));
*/
easyRTC.idToName = function(easyrtcid) {
if (easyRTC.lastLoggedInList) {
if (easyRTC.lastLoggedInList[easyrtcid]) {
if (easyRTC.lastLoggedInList[easyrtcid].userName) {
return easyRTC.lastLoggedInList[easyrtcid].userName;
}
else {
return easyrtcid;
}
}
}
return "--" + easyrtcid + "--";
};
easyRTC.haveTracks = function(easyrtcid, checkAudio) {
var stream;
if (easyrtcid) {
var peerConnObj = easyRTC.peerConns[easyrtcid];
if (!peerConnObj) {
return true;
}
var stream = peerConnObj.stream;
}
else {
stream = easyRTC.localStream;
}
if (!stream) {
return false;
}
var tracks;
try {
if (checkAudio) {
tracks = stream.getAudioTracks();
}
else {
tracks = stream.getVideoTracks();
}
} catch (oops) {
return true;
}
if (!tracks)
return false;
return tracks.length > 0;
}
/** Determines if a particular peer2peer connection has an audio track.
* @param easyrtcid - the id of the other caller in the connection. Null for local media stream.
* @return {boolean}
*/
easyRTC.haveAudioTrack = function(easyrtcid) {
return easyRTC.haveTracks(easyrtcid, true);
};
/** Determines if a particular peer2peer connection has a video track.
* @param easyrtcid - the id of the other caller in the connection. Null for local media stream.
* @return {string} "yes", "no", "unknown"
*/
easyRTC.haveVideoTrack = function(easyrtcid) {
return easyRTC.haveTracks(easyrtcid, false);
};
/* used in easyRTC.connect */
/** @private */
easyRTC.webSocket = null;
easyRTC.webSocketConnected = false;
/** @private */
easyRTC.pc_config = {};
/** @private */
easyRTC.closedChannel = null;
/**
* easyRTC.connect(args) performs the connection to the server. You must connect before trying to
* call other users.
* @param {String} applicationName is a string that identifies the application so that different applications can have different
* lists of users.
* @param {Function} successCallback (actualName, cookieOwner) - is called on successful connect. actualName is guaranteed to be unique.
* cookieOwner is true if the server sent back a isOwner:true field in response to a cookie.
* @param {Function} errorCallback (msgString) - is called on unsuccessful connect. if null, an alert is called instead.
*/
easyRTC.connect = function(applicationName, successCallback, errorCallback) {
easyRTC.webSocketConnected = false;
easyRTC.pc_config = {};
easyRTC.closedChannel = null;
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("attempt to connect to webrtc signalling server with application name=" + applicationName);
}
var mediaConstraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true
},
'optional': [{
RtpDataChannels: easyRTC.dataEnabled
}]
};
//
// easyRTC.disconnect performs a clean disconnection of the client from the server.
//
easyRTC.disconnectBody = function() {
easyRTC.loggingOut = true;
easyRTC.closedChannel = easyRTC.webSocket;
easyRTC.hangupAll();
if (easyRTC.loggedInListener) {
easyRTC.loggedInListener({}, false);
}
easyRTC.loggingOut = false;
easyRTC.oldConfig = {};
};
easyRTC.disconnect = function() {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("attempt to disconnect from webrtc signalling server");
}
easyRTC.hangupAll();
easyRTC.loggingOut = true;
//
// The hangupAll may try to send configuration information back to the server.
// Collecting that information is asynchronous, we don't actually close the
// connection until it's had a chance to be sent. We allocate 100ms for collecting
// the info, so 250ms should be sufficient for the disconnecting.
//
setTimeout(function() {
if (easyRTC.webSocketConnected) {
try {
easyRTC.webSocket.disconnect();
} catch (e) {
// we don't really care if this fails.
}
;
easyRTC.closedChannel = easyRTC.webSocket;
easyRTC.webSocketConnected = false;
}
easyRTC.loggingOut = false;
easyRTC.disconnecting = false;
if (easyRTC.loggedInListener) {
easyRTC.loggedInListener({}, false);
}
easyRTC.oldConfig = {};
}, 250);
};
if (errorCallback === null) {
errorCallback = function(x) {
alert("easyRTC.connect: " + x);
};
}
var sendMessage = function(destUser, instruction, data, successCallback, errorCallback) {
if (!easyRTC.webSocketConnected) {
throw "Attempt to send message without a valid connection to the server."
}
else {
var dataToShip = {
msgType: instruction,
senderId: easyRTC.myEasyrtcid,
targetId: destUser,
msgData: {
from: easyRTC.myEasyrtcid,
type: instruction,
actualData: data
}
};
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("sending socket message " + JSON.stringify(dataToShip));
}
easyRTC.webSocket.json.emit("easyrtcCmd", dataToShip);
}
};
/**
*Sends data to another user using previously established data channel. This method will
* fail if no data channel has been established yet. Unlike the easyRTC.sendWS method,
* you can't send a dictionary, convert dictionaries to strings using JSON.stringify first.
* What datatypes you can send, and how large a datatype depends on your browser.
* @param {String} destUser (an easyrtcid)
* @param {Object} data - an object which can be JSON'ed.
* @example
* easyRTC.sendDataP2P(someEasyrtcid, {room:499, bldgNum:'asd'});
*/
easyRTC.sendDataP2P = function(destUser, data) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("sending p2p message to " + destUser + " with data=" + JSON.stringify(data));
}
if (!easyRTC.peerConns[destUser]) {
easyRTC.showError(easyRTC.errCodes.DEVELOPER_ERR, "Attempt to send data peer to peer without a connection to " + destUser + ' first.');
}
else if (!easyRTC.peerConns[destUser].dataChannelS) {
easyRTC.showError(easyRTC.errCodes.DEVELOPER_ERR, "Attempt to send data peer to peer without establishing a data channel to " + destUser + ' first.');
}
else if (!easyRTC.peerConns[destUser].dataChannelReady) {
easyRTC.showError(easyRTC.errCodes.DEVELOPER_ERR, "Attempt to use data channel to " + destUser + " before it's ready to send.");
}
else {
var flattenedData = JSON.stringify(data);
easyRTC.peerConns[destUser].dataChannelS.send(flattenedData);
}
};
/** Sends data to another user using websockets.
* @param {String} destUser (an easyrtcid)
* @param {String} data - an object which can be JSON'ed.
* @example
* easyRTC.sendDataWS(someEasyrtcid, {room:499, bldgNum:'asd'});
*/
easyRTC.sendDataWS = function(destUser, data) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("sending client message via websockets to " + destUser + " with data=" + JSON.stringify(data));
}
if (easyRTC.webSocketConnected) {
easyRTC.webSocket.json.emit("message", {
senderId: easyRTC.myEasyrtcid,
targetId: destUser,
msgData: data
});
}
else {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("websocket failed because no connection to server");
}
throw "Attempt to send message without a valid connection to the server.";
}
};
/** Sends data to another user. This method uses datachannels if one has been set up, or websockets otherwise.
* @param {String} destUser (an easyrtcid)
* @param {String} data - an object which can be JSON'ed.
* @example
* easyRTC.sendData(someEasyrtcid, {room:499, bldgNum:'asd'});
*/
easyRTC.sendData = function(destUser, data) {
if (easyRTC.peerConns[destUser] && easyRTC.peerConns[destUser].dataChannelReady) {
easyRTC.sendDataP2P(destUser, data);
}
else {
easyRTC.sendDataWS(destUser, data);
}
};
/** Value returned by easyRTC.getConnectStatus if the other user isn't connected. */
easyRTC.NOT_CONNECTED = "not connected";
/** Value returned by easyRTC.getConnectStatus if the other user is in the process of getting connected */
easyRTC.BECOMING_CONNECTED = "connection in progress";
/** Value returned by easyRTC.getConnectStatus if the other user is connected. */
easyRTC.IS_CONNECTED = "is connected";
/**
* Return true if the client has a peer-2-peer connection to another user.
* The return values are text strings so you can use them in debugging output.
* @param {String} otherUser - the easyrtcid of the other user.
* @return {String} one of the following values: easyRTC.NOT_CONNECTED, easyRTC.BECOMING_CONNECTED, easyRTC.IS_CONNECTED
* @example
* if( easyRTC.getConnectStatus(otherEasyrtcid) == easyRTC.NOT_CONNECTED ) {
* easyRTC.connect(otherEasyrtcid,
* function() { console.log("success"); },
* function() { console.log("failure"); });
* }
*/
easyRTC.getConnectStatus = function(otherUser) {
if (typeof easyRTC.peerConns[otherUser] === 'undefined') {
return easyRTC.NOT_CONNECTED;
}
var peer = easyRTC.peerConns[otherUser];
if ((peer.sharingAudio || peer.sharingVideo) && !peer.startedAV) {
return easyRTC.BECOMING_CONNECTED;
}
else if (peer.sharingData && !peer.dataChannelReady) {
return easyRTC.BECOMING_CONNECTED;
}
else {
return easyRTC.IS_CONNECTED;
}
};
//
// Builds the constraints object for creating peer connections. This used to be
// inline but it's needed by the jssip connector as well.
//
/**
* @private
*/
easyRTC.buildPeerConstraints = function() {
var options = [];
options.push({'DtlsSrtpKeyAgreement': 'true'}); // for interoperability
if (easyRTC.dataEnabled) {
options.push({RtpDataChannels: true});
}
return {optional: options};
};
/**
* Initiates a call to another user. If it succeeds, the streamAcceptor callback will be called.
* @param {String} otherUser - the easyrtcid of the peer being called.
* @param {Function} callSuccessCB (otherCaller, mediaType) - is called when the datachannel is established or the mediastream is established. mediaType will have a value of "audiovideo" or "datachannel"
* @param {Function} callFailureCB (errMessage) - is called if there was a system error interfering with the call.
* @param {Function} wasAcceptedCB (wasAccepted:boolean,otherUser:string) - is called when a call is accepted or rejected by another party. It can be left null.
* @example
* easyRTC.call( otherEasyrtcid,
* function(easyrtcid, mediaType) {
* console.log("Got mediatype " + mediaType + " from " + easyRTC.idToName(easyrtcid);
* },
* function(errMessage) {
* console.log("call to " + easyRTC.idToName(otherEasyrtcid) + " failed:" + errMessage);
* },
* function(wasAccepted, easyrtcid) {
* if( wasAccepted ) {
* console.log("call accepted by " + easyRTC.idToName(easyrtcid));
* }
* else {
* console.log("call rejected" + easyRTC.idToName(easyrtcid));
* }
* });
*/
easyRTC.call = function(otherUser, callSuccessCB, callFailureCB, wasAcceptedCB) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("initiating peer to peer call to " + otherUser +
" audio=" + easyRTC.audioEnabled +
" video=" + easyRTC.videoEnabled +
" data=" + easyRTC.dataEnabled);
}
var i;
//
// If we are sharing audio/video and we haven't allocated the local media stream yet,
// we'll do so, recalling ourself on success.
//
if (easyRTC.localStream === null && (easyRTC.audioEnabled || easyRTC.videoEnabled)) {
easyRTC.initMediaSource(function() {
easyRTC.call(otherUser, callSuccessCB, callFailureCB, wasAcceptedCB);
}, callFailureCB);
return;
}
if (!easyRTC.webSocketConnected) {
var message = "Attempt to make a call prior to connecting to service";
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter(message);
}
throw message;
}
if (easyRTC.sipUA) {
//
// The standard sip address starts with "sip:".
//
for (i in easyRTC.sipProtocols) {
if (otherUser.indexOf(i) === 0) {
easyRTC.sipCall(otherUser, callSuccessCB, callFailureCB, wasAcceptedCB);
return;
}
}
}
//
// If B calls A, and then A calls B before accepting, then A should treat the attempt to
// call B as a positive offer to B's offer.
//
if (easyRTC.offersPending[otherUser]) {
wasAcceptedCB(true);
doAnswer(otherUser, easyRTC.offersPending[otherUser]);
delete easyRTC.offersPending[otherUser];
easyRTC.callCancelled(otherUser, false);
return;
}
// do we already have a pending call?
if (typeof easyRTC.acceptancePending[otherUser] !== 'undefined') {
message = "Call already pending acceptance";
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter(message);
}
callFailureCB(message);
return;
}
easyRTC.acceptancePending[otherUser] = true;
var pc = buildPeerConnection(otherUser, true, callFailureCB);
if (!pc) {
message = "buildPeerConnection failed, call not completed";
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter(message);
}
return;
}
easyRTC.peerConns[otherUser].callSuccessCB = callSuccessCB;
easyRTC.peerConns[otherUser].callFailureCB = callFailureCB;
easyRTC.peerConns[otherUser].wasAcceptedCB = wasAcceptedCB;
var peerConnObj = easyRTC.peerConns[otherUser];
var setLocalAndSendMessage = function(sessionDescription) {
if (peerConnObj.cancelled) {
return;
}
var sendOffer = function(successCB, errorCB) {
sendMessage(otherUser, "offer", sessionDescription, successCB, callFailureCB);
};
pc.setLocalDescription(sessionDescription, sendOffer, callFailureCB);
};
pc.createOffer(setLocalAndSendMessage, null, mediaConstraints);
};
function limitBandWidth(sd) {
if (easyRTC.videoBandwidthString !== "") {
var pieces = sd.sdp.split('\n');
for (var i = pieces.length - 1; i >= 0; i--) {
if (pieces[i].indexOf("m=video") === 0) {
for (var j = i; j < i + 10 && pieces[j].indexOf("a=") === -1 &&
pieces[j].indexOf("k=") === -1; j++) {
}
pieces.splice(j, 0, (easyRTC.videoBandwidthString + "\r"));
}
}
sd.sdp = pieces.join("\n");
}
}
function hangupBody(otherUser) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("Hanging up on " + otherUser);
}
clearQueuedMessages(otherUser);
if (easyRTC.peerConns[otherUser]) {
if (easyRTC.peerConns[otherUser].startedAV) {
try {
easyRTC.peerConns[otherUser].pc.close();
} catch (ignoredError) {
}
if (easyRTC.onStreamClosed) {
easyRTC.onStreamClosed(otherUser);
}
}
easyRTC.peerConns[otherUser].cancelled = true;
delete easyRTC.peerConns[otherUser];
if (easyRTC.webSocketConnected) {
sendMessage(otherUser, "hangup", {}, function() {
}, function(msg) {
if (easyRTC.debugPrinter) {
debugPrinter("hangup failed:" + msg);
}
});
}
if (easyRTC.acceptancePending[otherUser]) {
delete easyRTC.acceptancePending[otherUser];
}
}
}
/**
* Collects statistics on a particular connection.
* @param {string} otherUser - the easyrtcid the connection is attached to.
* @param {function} collector - a function that will be invoked with a map of statistics values
* @param {DOMObject} fieldsOfInterest - a map listing just the statistics to be reported. Can be null in which case all statics are reported.
* @return {boolean} true if statistics are collectable, false if not.
*/
easyRTC.getStats = function(otherUser, collector, fieldsOfInterest) {
if (!easyRTC.peerConns[otherUser] || !easyRTC.peerConns[otherUser].pc || !easyRTC.peerConns[otherUser].pc.getStats) {
console.log("no getstats");
return false;
}
else {
// console.log("invoking getstats");
easyRTC.peerConns[otherUser].pc.getStats(function(stats) {
var reports = stats.result();
var results = {};
var i;
for (i = 0; i < reports.length; i++) {
var report = reports[i];
var names = report.names();
for (var j = 0; j < names.length; j++) {
key = names[j];
console.log("saw stats key = " + key);
if (!fieldsOfInterest || fieldsOfInterest[key]) {
results[key] = report.stat(key);
}
}
}
// console.log("callback with results");
collector(results);
});
return true;
}
};
/**
* Hang up on a particular user or all users.
* @param {String} otherUser - the easyrtcid of the person to hang up on.
* @example
* easyRTC.hangup(someEasyrtcid);
*/
easyRTC.hangup = function(otherUser) {
hangupBody(otherUser);
easyRTC.updateConfigurationInfo();
};
/** @private */
easyRTC.hangupAllExternal = function() {
return false;
};
/**
* Hangs up on all current connections.
* @example
* easyRTC.hangupAll();
*/
easyRTC.hangupAll = function() {
var sawAConnection = false;
for (otherUser in easyRTC.peerConns) {
sawAConnection = true;
hangupBody(otherUser);
if (easyRTC.webSocketConnected) {
sendMessage(otherUser, "hangup", {}, function() {
}, function(msg) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("hangup failed:" + msg);
}
});
}
}
sawAConnection = sawAConnection || easyRTC.hangupAllExternal();
if (sawAConnection) {
easyRTC.updateConfigurationInfo();
}
};
/**
* This method adds a new stream to an existing connection.
* @param {string} otherUser - the easyrtcid of the other party in the connection.
* @param {MediaStream} stream - the other local media stream.
*/
easyRTC.addNewStream = function(otherUser, stream) {
if (!easyRTC.peerConns[otherUser]) {
alert("Programmer error: addNewStream: no connection to " + otherUser);
}
else if (!stream) {
alert("Programmer error: addNewStream: stream to add is null");
}
else {
var pc = easyRTC.peerConns[otherUser].pc;
pc.addStream(stream);
}
}
var buildPeerConnection = function(otherUser, isInitiator, failureCB) {
var pc;
var message;
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("building peer connection to " + otherUser);
}
try {
pc = easyRTC.createRTCPeerConnection(easyRTC.pc_config, easyRTC.buildPeerConstraints());
if (!pc) {
message = "Unable to create PeerConnection object, check your ice configuration(" +
JSON.stringify(easyRTC.pc_config) + ")";
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter(message);
}
throw(message);
}
//
// turn off data channel support if the browser doesn't support it.
//
if (easyRTC.dataEnabled && typeof pc.createDataChannel === 'undefined') {
easyRTC.dataEnabled = false;
}
pc.onconnection = function() {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("onconnection called prematurely");
}
};
var newPeerConn = {
pc: pc,
candidatesToSend: [],
startedAV: false,
isInitiator: isInitiator
};
pc.onicecandidate = function(event) {
// if (easyRTC.debugPrinter) {
// easyRTC.debugPrinter("saw ice message:\n" + event.candidate);
// }
if (newPeerConn.cancelled) {
return;
}
if (event.candidate && easyRTC.peerConns[otherUser]) {
var candidateData = {
type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate
};
if (easyRTC.peerConns[otherUser].startedAV) {
sendMessage(otherUser, "candidate", candidateData);
}
else {
easyRTC.peerConns[otherUser].candidatesToSend.push(candidateData);
}
}
};
pc.onaddstream = function(event) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("saw incoming media stream");
}
if (newPeerConn.cancelled)
return;
easyRTC.peerConns[otherUser].startedAV = true;
easyRTC.peerConns[otherUser].sharingAudio = easyRTC.haveAudioVideo.audio;
easyRTC.peerConns[otherUser].sharingVideo = easyRTC.haveAudioVideo.video;
easyRTC.peerConns[otherUser].connectTime = new Date().getTime();
easyRTC.peerConns[otherUser].stream = event.stream;
if (easyRTC.peerConns[otherUser].callSuccessCB) {
if (easyRTC.peerConns[otherUser].sharingAudio || easyRTC.peerConns[otherUser].sharingVideo) {
easyRTC.peerConns[otherUser].callSuccessCB(otherUser, "audiovideo");
}
}
if (easyRTC.audioEnabled || easyRTC.videoEnabled) {
updateConfiguration();
}
if (easyRTC.streamAcceptor) {
easyRTC.streamAcceptor(otherUser, event.stream);
}
};
pc.onremovestream = function(event) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("saw remove on remote media stream");
}
// console.log("saw onremovestream ", event);
if (easyRTC.peerConns[otherUser]) {
easyRTC.peerConns[otherUser].stream = null;
if (easyRTC.onStreamClosed) {
easyRTC.onStreamClosed(otherUser);
}
delete easyRTC.peerConns[otherUser];
easyRTC.updateConfigurationInfo();
}
};
easyRTC.peerConns[otherUser] = newPeerConn;
} catch (e) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter(JSON.stringify(e));
}
failureCB(e.message);
return null;
}
if (easyRTC.videoEnabled || easyRTC.audioEnabled) {
if (easyRTC.localStream === null) {
message = "Application program error: attempt to share audio or video before calling easyRTC.initMediaSource.";
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter(message);
}
alert(message);
}
else {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("adding local media stream to peer connection");
}
pc.addStream(easyRTC.localStream);
}
}
function initOutGoingChannel(otherUser) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("saw initOutgoingChannel call");
}
var dataChannel = pc.createDataChannel(easyRTC.datachannelName, easyRTC.datachannelConstraints);
easyRTC.peerConns[otherUser].dataChannelS = dataChannel;
if (!easyRTC.isMozilla) {
easyRTC.peerConns[otherUser].dataChannelR = dataChannel;
}
if (!easyRTC.isMozillia) {
dataChannel.onmessage = function(event) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("saw dataChannel.onmessage event");
}
if (easyRTC.receivePeerCB) {
easyRTC.receivePeerCB(otherUser, JSON.parse(event.data), null);
}
};
}
// dataChannel.binaryType = "blob";
dataChannel.onopen = function(event) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("saw dataChannel.onopen event");
}
if (easyRTC.peerConns[otherUser]) {
easyRTC.peerConns[otherUser].dataChannelReady = true;
if (easyRTC.peerConns[otherUser].callSuccessCB) {
easyRTC.peerConns[otherUser].callSuccessCB(otherUser, "datachannel");
}
if (easyRTC.onDataChannelOpen) {
easyRTC.onDataChannelOpen(otherUser);
}
easyRTC.updateConfigurationInfo();
}
};
dataChannel.onclose = function(event) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("saw dataChannelS.onclose event");
}
if (easyRTC.peerConns[otherUser]) {
easyRTC.peerConns[otherUser].dataChannelReady = false;
delete easyRTC.peerConns[otherUser].dataChannelS;
}
if (easyRTC.onDataChannelClose) {
easyRTC.onDataChannelClose(otherUser);
}
easyRTC.updateConfigurationInfo();
};
}
function initIncomingChannel(otherUser) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("initializing incoming channel handler for " + otherUser);
}
easyRTC.peerConns[otherUser].pc.ondatachannel = function(event) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("saw incoming data channel");
}
var dataChannel = event.channel;
easyRTC.peerConns[otherUser].dataChannelR = dataChannel;
if (!easyRTC.isMozilla) {
easyRTC.peerConns[otherUser].dataChannelS = dataChannel;
easyRTC.peerConns[otherUser].dataChannelReady = true;
}
dataChannel.onmessage = function(event) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("saw dataChannel.onmessage event");
}
if (easyRTC.receivePeerCB) {
easyRTC.receivePeerCB(otherUser, JSON.parse(event.data), null);
}
};
dataChannel.onclose = function(event) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("saw dataChannelR.onclose event");
}
if (easyRTC.peerConns[otherUser]) {
easyRTC.peerConns[otherUser].dataChannelReady = false;
delete easyRTC.peerConns[otherUser].dataChannelR;
}
if (easyRTC.onDataChannelClose) {
easyRTC.onDataChannelClose(openUser);
}
easyRTC.updateConfigurationInfo();
};
};
}
//
// added for interoperability
//
if (easyRTC.isMozilla) {
if (!easyRTC.dataEnabled) {
mediaConstraints.mandatory.MozDontOfferDataChannel = true;
}
else {
delete mediaConstraints.mandatory.MozDontOfferDataChannel;
}
}
if (easyRTC.dataEnabled) {
if (isInitiator || easyRTC.isMozilla) {
try {
initOutGoingChannel(otherUser);
} catch (channelErrorEvent) {
failureCB(easyRTC.formatError(channelErrorEvent));
}
}
if (!isInitiator || easyRTC.isMozilla) {
initIncomingChannel(otherUser);
}
}
pc.onconnection = function() {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("setup pc.onconnection ");
}
};
return pc;
};
var doAnswer = function(caller, msg) {
if (!easyRTC.localStream && (easyRTC.videoEnabled || easyRTC.audioEnabled)) {
easyRTC.initMediaSource(
function(s) {
doAnswer(caller, msg);
},
function(err) {
easyRTC.showError(easyRTC.errCodes.MEDIA_ERR, "Error getting local media stream: " + err);
});
return;
}
var pc = buildPeerConnection(caller, false, function(message) {
easyRTC.showError(easyRTC.errCodes.SYSTEM_ERR, message);
});
var newPeerConn = easyRTC.peerConns[caller];
if (!pc) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("buildPeerConnection failed. Call not answered");
}
return;
}
var setLocalAndSendMessage = function(sessionDescription) {
if (newPeerConn.cancelled)
return;
var sendAnswer = function() {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("sending answer");
}
sendMessage(caller, "answer", sessionDescription, function() {
}, easyRTC.onError);
easyRTC.peerConns[caller].startedAV = true;
if (pc.connectDataConnection && easyRTC.dataEnabled ) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("calling connectDataConnection(5002,5001)");
}
pc.connectDataConnection(5002, 5001);
}
};
pc.setLocalDescription(sessionDescription, sendAnswer, function(message) {
easyRTC.showError(easyRTC.errCodes.INTERNAL_ERR, "setLocalDescription: " + message);
});
};
var sd = null;
if (window.mozRTCSessionDescription) {
sd = new mozRTCSessionDescription(msg.actualData);
}
else {
sd = new RTCSessionDescription(msg.actualData);
}
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("sdp || " + JSON.stringify(sd));
}
var invokeCreateAnswer = function() {
if (newPeerConn.cancelled)
return;
pc.createAnswer(setLocalAndSendMessage,
function(message) {
easyRTC.showError(easyRTC.errCodes.INTERNAL_ERR, "create-answer: " + message);
},
mediaConstraints);
};
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("about to call setRemoteDescription in doAnswer");
}
try {
// limitBandWidth(sd);
pc.setRemoteDescription(sd, invokeCreateAnswer, function(message) {
easyRTC.showError(easyRTC.errCodes.INTERNAL_ERR, "set-remote-description: " + message);
});
} catch (srdError) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("saw exception in setRemoteDescription");
}
easyRTC.showError(easyRTC.errCodes.INTERNAL_ERR, "setRemoteDescription failed: " + srdError.message);
}
};
var onRemoteHangup = function(caller) {
delete easyRTC.offersPending[caller];
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("Saw onremote hangup event");
}
if (easyRTC.peerConns[caller]) {
easyRTC.peerConns[caller].cancelled = true;
if (easyRTC.peerConns[caller].startedAV) {
if (easyRTC.onStreamClosed) {
easyRTC.onStreamClosed(caller);
}
}
else {
if (easyRTC.callCancelled) {
easyRTC.callCancelled(caller, true);
}
}
try {
easyRTC.peerConns[caller].pc.close();
} catch (anyErrors) {
}
;
delete easyRTC.peerConns[caller];
easyRTC.updateConfigurationInfo();
}
else {
if (easyRTC.callCancelled) {
easyRTC.callCancelled(caller, true);
}
}
};
//
// the queuedMessages logic is cruft leftover from the early days of using the appengine.
// The app engine has many machines running in parallel. This means when
// a client sends a sequence of messages to another client via the server,
// one at a time, they don't necessarily arrive in the same order in which
// they were sent. In particular, candidates arriving before an offer can throw
// a wrench in the gears. So we queue the messages up until we are ready for them.
//
var queuedMessages = {};
var clearQueuedMessages = function(caller) {
queuedMessages[caller] = {
candidates: []
};
};
var onChannelMessage = function(msg) {
if (msg.senderId && msg.senderId !== "server") {
if (easyRTC.receivePeerCB) {
easyRTC.receivePeerCB(msg.senderId, msg.msgData);
}
}
else {
if (easyRTC.receiveServerCB) {
easyRTC.receiveServerCB(msg);
}
}
};
var onChannelCmd = function(msg) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("received message from socket server=" + JSON.stringify(msg));
}
var caller = msg.senderId;
var msgType = msg.msgType;
var actualData = msg.msgData;
var pc;
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter('received message of type ' + msgType);
}
if (typeof queuedMessages[caller] === "undefined") {
clearQueuedMessages(caller);
}
var processConnectedList = function(connectedList) {
for (var i in easyRTC.peerConns) {
if (typeof connectedList[i] === 'undefined') {
if (easyRTC.peerConns[i].startedAV) {
onRemoteHangup(i);
clearQueuedMessages(i);
}
}
}
};
var processCandidate = function(actualData) {
var candidate = null;
if (window.mozRTCIceCandidate) {
candidate = new mozRTCIceCandidate({
sdpMLineIndex: actualData.label,
candidate: actualData.candidate
});
}
else {
candidate = new RTCIceCandidate({
sdpMLineIndex: actualData.label,
candidate: actualData.candidate
});
}
pc = easyRTC.peerConns[caller].pc;
pc.addIceCandidate(candidate);
};
var flushCachedCandidates = function(caller) {
if (queuedMessages[caller]) {
for (var i = 0; i < queuedMessages[caller].candidates.length; i++) {
processCandidate(queuedMessages[caller].candidates[i]);
}
delete queuedMessages[caller];
}
};
var processOffer = function(caller, actualData) {
var helper = function(wasAccepted) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("offer accept=" + wasAccepted);
}
delete easyRTC.offersPending[caller];
if (wasAccepted) {
doAnswer(caller, actualData);
flushCachedCandidates(caller);
}
else {
sendMessage(caller, "reject", {
rejected: true
}, function() {
}, function() {
});
clearQueuedMessages(caller);
}
};
//
// There is a very rare case of two callers sending each other offers
// before receiving the others offer. In such a case, the caller with the
// greater valued easyrtcid will delete its pending call information and do a
// simple answer to the other caller's offer.
//
if (easyRTC.acceptancePending[caller] && caller < easyRTC.myEasyrtcid) {
delete easyRTC.acceptancePending[caller];
if (queuedMessages[caller]) {
delete queuedMessages[caller];
}
if (easyRTC.peerConns[caller].wasAcceptedCB) {
easyRTC.peerConns[caller].wasAcceptedCB(true, caller);
}
delete easyRTC.peerConns[caller];
helper(true);
return;
}
easyRTC.offersPending[caller] = actualData;
if (!easyRTC.acceptCheck) {
helper(true);
}
else {
easyRTC.acceptCheck(caller, helper);
}
};
if (msgType === 'token') {
easyRTC.myEasyrtcid = msg.easyrtcid;
//
// There are two formats that the iceserver may be coming down in:
// {url:"turn:username@host", credential:"password"}
// or {url:"turn:host", username:"username", credential:"password"}
// if it's the former, we split it to look like the latter.
//
easyRTC.pc_config = {iceServers: []};
for (var i in msg.iceConfig.iceServers) {
var item = msg.iceConfig.iceServers[i];
var fixedItem;
if (item.url.indexOf('turn:') == 0) {
if (item.username) {
fixedItem = createIceServer(item.url, item.username, item.credential);
}
else {
var parts = item.url.substring("turn:".length).split("@");
if (parts.length != 2) {
easyRTC.showError("badparam", "turn server url looked like " + item.url);
}
var username = parts[0];
var url = "turn:" + parts[1];
fixedItem = createIceServer(url, username, item.credential);
}
}
else { // is stun server entry
fixedItem = item;
}
easyRTC.pc_config.iceServers.push(fixedItem);
}
easyRTC.cookieOwner = msg.isOwner ? true : false;
easyRTC.roomCanNotifyOwner = msg.canNotifyOwner;
easyRTC.roomHasPassword = msg.hasPassword;
easyRTC.room = msg.room ? msg.room : null;
if (easyRTC.sipConfig) {
if (!easyRTC.initSipUa) {
// console.log("SIP connection parameters provided but no sip stuff");
}
easyRTC.initSipUa();
}
if (successCallback) {
successCallback(easyRTC.myEasyrtcid, easyRTC.cookieOwner);
}
}
else if (msgType === 'updateInfo') {
easyRTC.updateConfigurationInfo();
}
else if (msgType === 'list') {
var isPrimaryOwner = easyRTC.cookieOwner ? true : false;
for (id in actualData.connections) {
var item = actualData.connections[id];
if (item.isOwner &&
item.clientConnectTime < actualData.connections[easyRTC.myEasyrtcid].clientConnectTime) {
isPrimaryOwner = false;
}
}
delete actualData.connections[easyRTC.myEasyrtcid];
easyRTC.lastLoggedInList = actualData.connections;
processConnectedList(actualData.connections);
if (easyRTC.loggedInListener) {
easyRTC.loggedInListener(actualData.connections, isPrimaryOwner);
}
}
else if (msgType === 'forwardToUrl') {
if (actualData.newWindow) {
window.open(actualData.url);
}
else {
window.location.href = actualData.url;
}
}
else if (msgType === 'offer') {
processOffer(caller, actualData);
}
else if (msgType === 'reject') {
delete easyRTC.acceptancePending[caller];
if (queuedMessages[caller]) {
delete queuedMessages[caller];
}
if (easyRTC.peerConns[caller]) {
if (easyRTC.peerConns[caller].wasAcceptedCB) {
easyRTC.peerConns[caller].wasAcceptedCB(false, caller);
}
delete easyRTC.peerConns[caller];
}
}
else if (msgType === 'answer') {
delete easyRTC.acceptancePending[caller];
if (easyRTC.peerConns[caller].wasAcceptedCB) {
easyRTC.peerConns[caller].wasAcceptedCB(true, caller);
}
easyRTC.peerConns[caller].startedAV = true;
for (var i = 0; i < easyRTC.peerConns[caller].candidatesToSend.length; i++) {
sendMessage(caller, "candidate", easyRTC.peerConns[caller].candidatesToSend[i]);
}
pc = easyRTC.peerConns[caller].pc;
var sd = null;
if (window.mozRTCSessionDescription) {
sd = new mozRTCSessionDescription(actualData.actualData);
}
else {
sd = new RTCSessionDescription(actualData.actualData);
}
if (!sd) {
throw "Could not create the RTCSessionDescription";
}
// limitBandWidth(sd);
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("about to call initiating setRemoteDescription");
}
pc.setRemoteDescription(sd, function() {
if (pc.connectDataConnection && easyRTC.dataEnabled ) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("calling connectDataConnection(5001,5002)");
}
pc.connectDataConnection(5001, 5002); // these are like ids for data channels
}
});
flushCachedCandidates(caller);
}
else if (msgType === 'candidate') {
if (easyRTC.peerConns[caller] && easyRTC.peerConns[caller].startedAV) {
processCandidate(actualData.actualData);
}
else {
if (!easyRTC.peerConns[caller]) {
queuedMessages[caller] = {
candidates: []
};
}
queuedMessages[caller].candidates.push(actualData.actualData);
}
}
else if (msgType === 'hangup') {
onRemoteHangup(caller);
clearQueuedMessages(caller);
}
else if (msgType === 'error') {
easyRTC.showError(actualData.errorCode, actualData.errorText);
}
};
if (!window.io) {
easyRTC.onError("Your HTML has not included the socket.io.js library");
}
function connectToWSServer() {
// easyRTC.webSocket = io.connect(easyRTC.serverPath, {
// 'force new connection': true, 'connect timeout': 10000
// });
easyRTC.webSocket = io.connect(easyRTC.serverPath, {
'connect timeout': 10000
});
if (!easyRTC.webSocket) {
throw "io.connect failed";
}
easyRTC.webSocket.on('error', function() {
if (easyRTC.myEasyrtcid) {
easyRTC.showError(easyRTC.errCodes.SIGNAL_ERROR, "Miscellaneous error from signalling server. It may be ignorable.");
}
else {
errorCallback("Unable to reach the easyRTC signalling server.");
}
});
easyRTC.webSocket.on("connect", function(event) {
easyRTC.webSocketConnected = true;
if (!easyRTC.webSocket || !easyRTC.webSocket.socket || !easyRTC.webSocket.socket.sessionid) {
easyRTC.showError(easyRTC.errCodes.CONNECT_ERR, "Socket.io connect event fired with bad websocket.");
}
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("saw socketserver onconnect event");
}
if (easyRTC.webSocketConnected) {
// sendAuthenticate();
easyRTC.updateConfigurationInfo = updateConfiguration;
updateConfiguration();
}
else {
errorCallback("Internal communications failure.");
}
}
);
easyRTC.webSocket.on("message", onChannelMessage);
easyRTC.webSocket.on("easyRTCCmd", onChannelCmd);
easyRTC.webSocket.on("easyrtcCmd", onChannelCmd);
easyRTC.webSocket.on("disconnect", function(code, reason, wasClean) {
easyRTC.webSocketConnected = false;
easyRTC.updateConfigurationInfo = function() {
};
easyRTC.oldConfig = {};
easyRTC.disconnectBody();
if (easyRTC.disconnectListener) {
easyRTC.disconnectListener();
}
});
}
connectToWSServer();
function getStatistics(pc, track, results) {
var successcb = function(stats) {
for (var i in stats) {
results[i] = stats[i];
}
};
var failurecb = function(event) {
results.error = event;
};
pc.getStats(track, successcb, failurecb);
}
function DeltaRecord(added, deleted, modified) {
function objectNotEmpty(obj) {
for (var i in obj) {
return true;
}
return false;
}
var result = {};
if (objectNotEmpty(added)) {
result.added = added;
}
if (objectNotEmpty(deleted)) {
result.deleted = deleted;
}
if (objectNotEmpty(result)) {
return result;
}
else {
return null;
}
}
function findDeltas(oldVersion, newVersion) {
var i;
var added = {}, deleted = {};
for (i in newVersion) {
if (oldVersion === null || typeof oldVersion[i] === 'undefined') {
added[i] = newVersion[i];
}
else if (typeof newVersion[i] === 'object') {
var subPart = findDeltas(oldVersion[i], newVersion[i]);
if (subPart !== null) {
added[i] = newVersion[i];
}
}
else if (newVersion[i] !== oldVersion[i]) {
added[i] = newVersion[i];
}
}
for (i in oldVersion) {
if (typeof newVersion[i] === 'undefined') {
deleted = oldVersion[i];
}
}
return new DeltaRecord(added, deleted);
}
easyRTC.oldConfig = {}; // used internally by updateConfiguration
//
// this function collects configuration info that will be sent to the server.
// It returns that information, leaving it the responsibility of the caller to
// do the actual sending.
//
easyRTC.collectExternalConnections = function() {
return {};
};
easyRTC.collectConfigurationInfo = function() {
var connectionList = {};
for (var i in easyRTC.peerConns) {
connectionList[i] = {
connectTime: easyRTC.peerConns[i].connectTime,
isInitiator: easyRTC.peerConns[i].isInitiator ? true : false
};
}
var externalConnections = easyRTC.collectExternalConnections();
for (var i in externalConnections) {
connectionList[i] = {
connectTime: externalConnections[i].connectTime,
isInitiator: externalConnections[i].isInitiator ? true : false
};
}
var newConfig = {
sharingAudio: easyRTC.haveAudioVideo.audio ? true : false,
sharingVideo: easyRTC.haveAudioVideo.video ? true : false,
sharingData: easyRTC.dataEnabled ? true : false,
nativeVideoWidth: easyRTC.nativeVideoWidth,
nativeVideoHeight: easyRTC.nativeVideoHeight,
windowWidth: window.innerWidth,
windowHeight: window.innerHeight,
apiKey: easyRTC.apiKey,
appDefinedFields: easyRTC.appDefinedFields,
connectionList: connectionList,
applicationName: applicationName,
screenWidth: window.screen.width,
screenHeight: window.screen.height,
cookieEnabled: navigator.cookieEnabled,
os: navigator.oscpu,
language: navigator.language
};
if (easyRTC.cookieId && document.cookie) {
var cookies = document.cookie.split(/[; ]/);
var target = easyRTC.cookieId + "=";
for (var i in cookies) {
if (cookies[i].indexOf(target) === 0) {
var cookie = cookies[i].substring(target.length);
cookieData = cookie.split(".");
newConfig.easyrtcsid = cookieData[0];
}
}
}
if (easyRTC.referer) {
newConfig.referer = easyRTC.referer;
}
if (easyRTC.userName) {
newConfig.userName = easyRTC.userName;
}
return newConfig;
};
function updateConfiguration() {
var newConfig = easyRTC.collectConfigurationInfo();
//
// we need to give the getStats calls a chance to fish out the data.
// The longest I've seen it take is 5 milliseconds so 100 should be overkill.
//
var sendDeltas = function() {
var alteredData = findDeltas(easyRTC.oldConfig, newConfig);
//
// send all the configuration information that changes during the session
//
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("cfg=" + JSON.stringify(alteredData.added));
}
easyRTC.sendEasyrtcCmd('setUserCfg', alteredData.added);
easyRTC.oldConfig = newConfig;
};
if (easyRTC.oldConfig === {}) {
sendDeltas();
}
else {
setTimeout(sendDeltas, 100);
}
}
};
// this flag controls whether the initManaged routine adds close buttons to the caller
// video objects
/** @private */
easyRTC.autoAddCloseButtons = true;
/** By default, the initManaged routine sticks a "close" button on top of each caller
* video object that it manages. Call this function (before calling initManaged) to disable that particular feature.
*
*/
easyRTC.dontAddCloseButtons = function() {
easyRTC.autoAddCloseButtons = false;
}
/**
* Provides a layer on top of the easyRTC.initMediaSource and easyRTC.connect, assign the local media stream to
* the video object identified by monitorVideoId, assign remote video streams to
* the video objects identified by videoIds, and then call onReady. One of it's
* side effects is to add hangup buttons to the remote video objects, buttons
* that only appear when you hover over them with the mouse cursor. This method will also add the
* easyRTCMirror class to the monitor video object so that it behaves like a mirror.
* @param {String} applicationName - name of the application.
* @param {String} monitorVideoId - the id of the video object used for monitoring the local stream.
* @param {Array} videoIds - an array of video object ids (strings)
* @param {Function} onReady - a callback function used on success.
* @param {Function} onFailure - a callbackfunction used on failure (failed to get local media or a connection of the signaling server).
* @example
* easyRTC.initManaged('multiChat', 'selfVideo', ['remote1', 'remote2', 'remote3'],
* function() {
* console.log("successfully connected.");
* });
*/
easyRTC.initManaged = function(applicationName, monitorVideoId, videoIds, onReady, onFailure) {
var numPEOPLE = videoIds.length;
var refreshPane = 0;
var onCall = null, onHangup = null, gotMediaCallback = null, gotConnectionCallback = null;
if (videoIds === null) {
videoIds = [];
}
// verify that video ids were not typos.
if (monitorVideoId && !document.getElementById(monitorVideoId)) {
easyRTC.showError(easyRTC.errCodes.DEVELOPER_ERR, "The monitor video id passed to initManaged was bad, saw " + monitorVideoId);
return;
}
document.getElementById(monitorVideoId).muted = "muted";
for (var i in videoIds) {
var name = videoIds[i];
var video = document.getElementById(name);
if (!video) {
easyRTC.showError(easyRTC.errCodes.DEVELOPER_ERR, "The caller video id '" + name + "' passed to initManaged was bad.");
return;
}
else {
video.caller = "";
}
}
/** Sets an event handler that gets called when the local media stream is
* created or not. Can only be called after calling easyRTC.initManaged.
* @param {Function} gotMediaCB has the signature function(gotMedia, why)
* @example
* easyRTC.setGotMedia( function(gotMediaCB, why) {
* if( gotMedia ) {
* console.log("Got the requested user media");
* }
* else {
* console.log("Failed to get media because: " + why);
* }
* });
*/
easyRTC.setGotMedia = function(gotMediaCB) {
gotMediaCallback = gotMediaCB;
};
/** Sets an event handler that gets called when a connection to the signalling
* server has or has not been made. Can only be called after calling easyRTC.initManaged.
* @param {Function} gotConnectionCB has the signature (gotConnection, why)
* @example
* easyRTC.setGotConnection( function(gotConnection, why) {
* if( gotConnection ) {
* console.log("Successfully connected to signalling server");
* }
* else {
* console.log("Failed to connect to signalling server because: " + why);
* }
* });
*/
easyRTC.setGotConnection = function(gotConnectionCB) {
gotConnectionCallback = gotConnectionCB;
};
/** Sets an event handler that gets called when a call is established.
* It's only purpose (so far) is to support transitions on video elements.
* This function is only defined after easyRTC.initManaged is called.
* The slot argument is the index into the array of video ids.
* @param {Function} cb has the signature function(easyrtcid, slot) {}
* @example
* easyRTC.setOnCall( function(easyrtcid, slot) {
* console.log("call with " + easyrtcid + "established");
* });
*/
easyRTC.setOnCall = function(cb) {
onCall = cb;
};
/** Sets an event handler that gets called when a call is ended.
* it's only purpose (so far) is to support transitions on video elements.
x * this function is only defined after easyRTC.initManaged is called.
* The slot is parameter is the index into the array of video ids.
* Note: if you call easyRTC.getConnectionCount() from inside your callback
* it's count will reflect the number of connections before the hangup started.
* @param {Function} cb has the signature function(easyrtcid, slot) {}
* @example
* easyRTC.setOnHangup( function(easyrtcid, slot) {
* console.log("call with " + easyrtcid + "ended");
* });
*/
easyRTC.setOnHangup = function(cb) {
onHangup = cb;
};
function getIthVideo(i) {
if (videoIds[i]) {
return document.getElementById(videoIds[i]);
}
else {
return null;
}
}
easyRTC.getIthCaller = function(i) {
if (i < 0 || i > videoIds.length) {
return null;
}
return getIthVideo(i).caller;
};
easyRTC.getSlotOfCaller = function(easyrtcid) {
for (var i = 0; i < numPEOPLE; i++) {
if (easyRTC.getIthCaller(i) === easyrtcid) {
return i;
}
}
return -1; // caller not connected
};
easyRTC.setOnStreamClosed(function(caller) {
for (var i = 0; i < numPEOPLE; i++) {
var video = getIthVideo(i);
if (video.caller === caller) {
easyRTC.setVideoObjectSrc(video, "");
video.caller = "";
if (onHangup) {
onHangup(caller, i);
}
}
}
});
//
// Only accept incoming calls if we have a free video object to display
// them in.
//
easyRTC.setAcceptChecker(function(caller, helper) {
for (var i = 0; i < numPEOPLE; i++) {
var video = getIthVideo(i);
if (video.caller == "") {
helper(true);
return;
}
}
helper(false);
});
easyRTC.setStreamAcceptor(function(caller, stream) {
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("stream acceptor called");
}
var i, video;
if (refreshPane && refreshPane.caller == "") {
easyRTC.setVideoObjectSrc(video, stream);
if (onCall) {
onCall(caller);
}
refreshPane = null;
return;
}
for (i = 0; i < numPEOPLE; i++) {
video = getIthVideo(i);
if (video.caller == caller) {
easyRTC.setVideoObjectSrc(video, stream);
// if (onCall) {
// onCall(caller, i);
// }
return;
}
}
for (i = 0; i < numPEOPLE; i++) {
video = getIthVideo(i);
if (!video.caller || video.caller == "") {
video.caller = caller;
if (onCall) {
onCall(caller, i);
}
easyRTC.setVideoObjectSrc(video, stream);
return;
}
}
//
// no empty slots, so drop whatever caller we have in the first slot and use that one.
//
video = getIthVideo(0);
if (video) {
easyRTC.hangup(video.caller);
easyRTC.setVideoObjectSrc(video, stream);
if (onCall) {
onCall(caller, 0);
}
}
video.caller = caller;
});
if (easyRTC.autoAddCloseButtons) {
var addControls = function(video) {
var parentDiv = video.parentNode;
video.caller = "";
var closeButton = document.createElement("div");
closeButton.className = "closeButton";
closeButton.onclick = function() {
if (video.caller) {
easyRTC.hangup(video.caller);
easyRTC.setVideoObjectSrc(video, "");
video.caller = "";
}
};
parentDiv.appendChild(closeButton);
};
for (var i = 0; i < numPEOPLE; i++) {
addControls(getIthVideo(i));
}
}
var monitorVideo = null;
if (easyRTC.videoEnabled && monitorVideoId !== null) {
monitorVideo = document.getElementById(monitorVideoId);
if (!monitorVideo) {
alert("Programmer error: no object called " + monitorVideoId);
return;
}
monitorVideo.muted = "muted";
monitorVideo.defaultMuted = true;
}
var nextInitializationStep;
if (easyRTC.debugPrinter) {
easyRTC.debugPrinter("No sip initialization ");
}
nextInitializationStep = function(token, isOwner) {
if (gotConnectionCallback) {
gotConnectionCallback(true, "");
}
onReady(easyRTC.myEasyrtcid, easyRTC.cookieOwner);
};
easyRTC.initMediaSource(
function() {
if (gotMediaCallback) {
gotMediaCallback(true, null);
}
if (monitorVideo !== null) {
easyRTC.setVideoObjectSrc(monitorVideo, easyRTC.getLocalStream());
}
function connectError(why) {
if (gotConnectionCallback) {
gotConnectionCallback(false, why);
}
else {
easyRTC.showError(easyRTC.errCodes.CONNECT_ERR, why);
}
if (onFailure) {
onFailure(why);
}
}
easyRTC.connect(applicationName, nextInitializationStep, connectError);
},
function(errmesg) {
if (gotMediaCallback) {
gotMediaCallback(false, errmesg);
}
else {
easyRTC.showError(easyRTC.errCodes.MEDIA_ERR, errmesg);
}
if (onFailure) {
onFailure(errmesg);
}
}
);
};
//
// the below code is a copy of the standard polyfill adapter.js
//
var RTCPeerConnection = null;
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";
webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1]);
// The RTCPeerConnection object.
RTCPeerConnection = mozRTCPeerConnection;
// The RTCSessionDescription object.
RTCSessionDescription = mozRTCSessionDescription;
// The RTCIceCandidate object.
RTCIceCandidate = mozRTCIceCandidate;
// Get UserMedia (only difference is the prefix).
// Code from Adam Barth.
getUserMedia = navigator.mozGetUserMedia.bind(navigator);
// Creates iceServer from the url for FF.
createIceServer = function(url, username, password) {
var iceServer = null;
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 &&
(url.indexOf('transport=udp') !== -1 ||
url.indexOf('?transport') === -1)) {
// Create iceServer with turn url.
// Ignore the transport parameter from TURN url.
var 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.
createIceServer = function(url, username, password) {
var iceServer = null;
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.
var 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.
RTCPeerConnection = webkitRTCPeerConnection;
// Get UserMedia (only difference is the prefix).
// Code from Adam Barth.
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 {
console.log("Browser does not appear to be WebRTC-capable");
}
//
// This is the end of the polyfill adapter.js
//