appeerjs
Version:
A basic encapsulation of Native WebRTC, this would offer an easy to use and understand API for beginners out there
469 lines (366 loc) • 13.8 kB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
module.exports.RTCSessionDescription = window.RTCSessionDescription ||
window.mozRTCSessionDescription;
module.exports.RTCPeerConnection = window.RTCPeerConnection ||
window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
module.exports.RTCIceCandidate = window.RTCIceCandidate ||
window.mozRTCIceCandidate;
},{}],2:[function(require,module,exports){
'use strict';
var EventDispatcher = require('./eventdispatcher');
var MediaConnection = require('./mediaconnection');
var logger = require('./logger');
function Appeer(id, options) {
if (! (this instanceof Appeer)) return new Appeer(id, options);
var defaultConfig = {
iceServers: [{
'url': 'stun:stun.1.google.com:19302'
}]
};
this.id = id || '';
this.options = options || {};
this.socket = null;
this.options.config = options.config || defaultConfig;
this.connections = {};
this.rooms = {};
this.pendingCalls = [];
// Initialize the connection to socket.io server
this._initSocketIo(options.host + ':' + options.port);
logger.setLogLevel(this.options.debug);
}
// Inherit from EventDispatcher
Object.assign(Appeer.prototype, EventDispatcher.prototype);
Appeer.prototype._initSocketIo = function (host, options) {
var self = this;
options = options || {};
options.secure = true; // Always use https
options.query = 'customId=' + this.id;
this.socket = io.connect(host, options);
this.socket.on('message', function (message) {
self._handleMessages(message);
});
this.socket.on('error', function (error) {
self.emit('error', { error: error });
});
this.on('on-stream-added', function (event) {
var stream = event.data.stream,
pendingCallsLen = self.pendingCalls.length;
// Store the stream, to be used for making offer in incoming-member message
self.stream = stream;
for (var i = 0; i < pendingCallsLen; i++) {
var peerId = self.pendingCalls[i],
connection = this.connections[peerId];
if (! connection) continue;
// Wrap around IIFE to preserve the variable connection's value
(function (conn) {
conn.localStream = stream;
conn._makeOffer(connection.pc);
})(connection);
}
});
};
Appeer.prototype.call = function (id, stream, options) {
var options = options || {};
// Used for making offer in incoming-member message
this.stream = stream;
options.originator = true;
options._stream = stream;
// Proceed to a single call, if peerId is not a room
if (! this.rooms[id]) {
// Immediately create a connection to start the calling process
this.connections[id] = new MediaConnection(id, this, options);
} else {
// Inform the server that someone wants to call a room
this.socket.send({
type: 'call',
from: this.id,
to: id
});
}
};
Appeer.prototype._handleMessages = function (message) {
var from = message.id,
payload = message.payload,
type = message.type;
switch (type) {
case 'connect':
this.emit('connection', { id: message.appeerId });
break;
case 'answer':
this.connections[from].handleSdp(payload);
break;
case 'candidate':
this.connections[from].handleCandidate(payload, from);
break;
case 'offer':
logger.log('Setting remote description on offer from', from);
var connection = new MediaConnection(from, this, {
originator: false,
_payload: message.payload
});
this.connections[from] = connection;
this.emit('call', { call: connection });
break;
case 'incoming-call':
var room = message.room;
logger.log('Incoming call from', from, 'in room', room);
this.rooms[room][from] = from.toString();
this.socket.send({
type: 'answer-call',
to: room
});
break;
case 'incoming-member':
logger.log('Incoming member', from);
var room = message.room,
connection = new MediaConnection(from, this, {
originator: true,
_stream: this.stream
});
this.connections[from] = connection;
this.rooms[room][from] = from.toString();
if (! this.stream) {
logger.log('Stream not yet available, adding pending call from', from);
this.pendingCalls.push(from);
}
break;
case 'close':
this.emit('close', { id: from });
break;
default:
var error = message.error;
logger.error('Invalid message with type', type, 'from', from);
logger.error('AppeerJS Error:', error);
break;
}
};
Appeer.prototype.join = function (room, cb) {
if (! room) return;
var self = this;
this.room = room;
this.socket.send({
room: room,
type: 'join'
}, function (room) {
logger.log('Successfully joined room:', room);
self.rooms[room] = {};
if (typeof cb === 'function') cb();
});
};
Appeer.prototype.close = function (id) {
if (! id) return;
var connection = this.connections[id],
room = this.rooms[id];
if (connection) {
// Close the single connection
this.connections[id].close();
} else if (room) {
// Close multiple connection
for (var member in room) {
if (room.hasOwnProperty(member)) {
this.connections[member].close(id);
// TODO: Replace this line when reconnection feature is implemented
delete this.connections[member];
}
}
}
};
module.exports = Appeer;
},{"./eventdispatcher":3,"./logger":5,"./mediaconnection":6}],3:[function(require,module,exports){
/**
* @author mrdoob / http://mrdoob.com/
*/
'use strict';
function EventDispatcher() {}
Object.assign(EventDispatcher.prototype, {
addEventListener: function (type, listener) {
if (this._listeners === undefined) this._listeners = {};
var listeners = this._listeners;
if (listeners[type] === undefined) {
listeners[type] = [];
}
if (listeners[type].indexOf(listener) === - 1) {
listeners[type].push(listener);
}
},
hasEventListener: function (type, listener) {
if (this._listeners === undefined) return false;
var listeners = this._listeners;
if (listeners[type] !== undefined && listeners[type].indexOf(listener) !== -1) {
return true;
}
return false;
},
removeEventListener: function (type, listener) {
if (this._listeners === undefined) return;
var listeners = this._listeners;
var listenerArray = listeners[ type ];
if (listenerArray !== undefined) {
var index = listenerArray.indexOf(listener);
if (index !== -1) {
listenerArray.splice(index, 1);
}
}
},
dispatchEvent: function (event) {
if (this._listeners === undefined) return;
var listeners = this._listeners;
var listenerArray = listeners[event.type];
if (listenerArray !== undefined) {
event.target = this;
var i = 0,
array = [],
length = listenerArray.length;
for (i = 0; i < length; i++) {
array[i] = listenerArray[i];
}
for (i = 0; i < length; i++) {
array[i].call(this, event);
}
}
},
on: function (eventName, listener) {
this.addEventListener(eventName, listener);
},
emit: function (eventName, data) {
var event = {
type: eventName,
data: data
};
this.dispatchEvent(event);
}
});
module.exports = EventDispatcher;
},{}],4:[function(require,module,exports){
window.Appeer = require('./appeer');
window.RTCPeerConnection = require('./adapter').RTCPeerConnection;
window.RTCSessionDescription = require('./adapter').RTCSessionDescription;
window.RTCIceCandidate = require('./adapter').RTCIceCandidate;
},{"./adapter":1,"./appeer":2}],5:[function(require,module,exports){
'use strict';
function logger() {
var debug = false;
return {
setLogLevel: setLogLevel,
log: log,
error: error
};
function setLogLevel(newDebug) {
debug = newDebug;
}
function log() {
if (debug) {
console.log.apply(console, arguments);
}
}
function error() {
if (debug) {
console.error.apply(console, arguments);
}
}
}
module.exports = logger();
},{}],6:[function(require,module,exports){
'use strict';
var logger = require('./logger');
function MediaConnection(peerId, appeer, options) {
if (! (this instanceof MediaConnection)) return new MediaConnection(peerId, appeer, options);
this.options = options || {};
this.peerId = peerId;
this.appeer = appeer;
this.socket = appeer.socket;
this.localStream = options._stream;
// Starts an RTC Connection
this._startConnection();
this.pc.onaddstream = this._handleAddStream.bind(appeer);
this.pc.onicecandidate = this._handleIceCandidate.bind(this);
}
MediaConnection.prototype._startConnection = function () {
this.pc = new RTCPeerConnection(this.appeer.options.config, {
optional: [{ DtlsSrtpKeyAgreement: true }]
});
if (this.options.originator && this.localStream) {
this._makeOffer(this.pc);
} else if (this.options.originator === false) {
this.handleSdp(this.options._payload);
}
};
MediaConnection.prototype._makeOffer = function (pc) {
var self = this;
logger.log('Creating offer to', self.peerId);
pc.addStream(this.localStream);
pc.createOffer(function (offer) {
logger.log('Setting local description on offer');
pc.setLocalDescription(offer, function () {
self.socket.send({
type: 'offer',
payload: offer,
to: self.peerId
});
}, function (error) {
logger.error('Error setting local description on offer', error);
});
}, function (error) {
logger.error('Error on creating offer', error);
});
};
MediaConnection.prototype.handleSdp = function (answer) {
logger.log('Setting remote description on answer from', this.peerId);
this.pc.setRemoteDescription(new RTCSessionDescription(answer));
};
MediaConnection.prototype._handleAddStream = function (event) {
logger.log('Incoming remote media stream', event);
this.emit('stream', { stream: event.stream });
};
MediaConnection.prototype._handleIceCandidate = function (event) {
logger.log('On Ice Candidate:', this.peerId);
if (event.candidate) {
this.socket.emit('message', {
type: 'candidate',
payload: event.candidate,
to: this.peerId
});
}
};
MediaConnection.prototype.answer = function (stream) {
this.pc.addStream(stream);
this._handleOffer(this.peerId);
this.appeer.emit('on-stream-added', { stream: stream });
};
MediaConnection.prototype._handleOffer = function (peerId) {
var pc = this.pc,
socket = this.socket;
logger.log('Creating answer to', peerId);
pc.createAnswer(function (answer) {
logger.log('Setting local description on answer');
pc.setLocalDescription(answer, function () {
socket.emit('message', {
type: 'answer',
payload: answer,
to: peerId
});
}, function (error) {
logger.error('Error setting local description on answer', error);
});
}, function (error) {
logger.error('Error on create answer from offer', error);
});
};
MediaConnection.prototype.handleCandidate = function (candidate) {
logger.log('Adding ice candidate from', this.peerId);
this.pc.addIceCandidate(new RTCIceCandidate(candidate));
};
MediaConnection.prototype.close = function (room) {
var pc = this.pc;
if (!! pc && pc.signalingState !== 'closed') {
pc.close();
pc = null;
this.socket.send({
type: 'close',
to: this.peerId,
room: room
});
}
};
module.exports = MediaConnection;
},{"./logger":5}]},{},[4]);