You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
462 lines
15 KiB
462 lines
15 KiB
var WebRTC = require('webrtc');
|
|
var WildEmitter = require('wildemitter');
|
|
var webrtcSupport = require('webrtcsupport');
|
|
var attachMediaStream = require('attachmediastream');
|
|
var mockconsole = require('mockconsole');
|
|
var SocketIoConnection = require('./socketioconnection');
|
|
|
|
function SimpleWebRTC(opts) {
|
|
var self = this;
|
|
var options = opts || {};
|
|
var config = this.config = {
|
|
url: 'https://signaling.simplewebrtc.com:443/',
|
|
socketio: {/* 'force new connection':true*/},
|
|
connection: null,
|
|
debug: false,
|
|
localVideoEl: '',
|
|
remoteVideosEl: '',
|
|
enableDataChannels: true,
|
|
autoRequestMedia: false,
|
|
autoRemoveVideos: true,
|
|
adjustPeerVolume: true,
|
|
peerVolumeWhenSpeaking: 0.25,
|
|
media: {
|
|
video: true,
|
|
audio: true
|
|
},
|
|
receiveMedia: { // FIXME: remove old chrome <= 37 constraints format
|
|
mandatory: {
|
|
OfferToReceiveAudio: true,
|
|
OfferToReceiveVideo: true
|
|
}
|
|
},
|
|
localVideo: {
|
|
autoplay: true,
|
|
mirror: true,
|
|
muted: true
|
|
}
|
|
};
|
|
var item, connection;
|
|
|
|
// We also allow a 'logger' option. It can be any object that implements
|
|
// log, warn, and error methods.
|
|
// We log nothing by default, following "the rule of silence":
|
|
// http://www.linfo.org/rule_of_silence.html
|
|
this.logger = function () {
|
|
// we assume that if you're in debug mode and you didn't
|
|
// pass in a logger, you actually want to log as much as
|
|
// possible.
|
|
if (opts.debug) {
|
|
return opts.logger || console;
|
|
} else {
|
|
// or we'll use your logger which should have its own logic
|
|
// for output. Or we'll return the no-op.
|
|
return opts.logger || mockconsole;
|
|
}
|
|
}();
|
|
|
|
// set our config from options
|
|
for (item in options) {
|
|
this.config[item] = options[item];
|
|
}
|
|
|
|
// attach detected support for convenience
|
|
this.capabilities = webrtcSupport;
|
|
|
|
// call WildEmitter constructor
|
|
WildEmitter.call(this);
|
|
|
|
// create default SocketIoConnection if it's not passed in
|
|
if (this.config.connection === null) {
|
|
connection = this.connection = new SocketIoConnection(this.config);
|
|
} else {
|
|
connection = this.connection = this.config.connection;
|
|
}
|
|
|
|
connection.on('connect', function () {
|
|
self.emit('connectionReady', connection.getSessionid());
|
|
self.sessionReady = true;
|
|
self.testReadiness();
|
|
});
|
|
|
|
connection.on('message', function (message) {
|
|
var peers = self.webrtc.getPeers(message.from, message.roomType);
|
|
var peer;
|
|
|
|
if (message.type === 'offer') {
|
|
if (peers.length) {
|
|
peers.forEach(function (p) {
|
|
if (p.sid == message.sid) peer = p;
|
|
});
|
|
//if (!peer) peer = peers[0]; // fallback for old protocol versions
|
|
}
|
|
if (!peer) {
|
|
peer = self.webrtc.createPeer({
|
|
id: message.from,
|
|
sid: message.sid,
|
|
type: message.roomType,
|
|
enableDataChannels: self.config.enableDataChannels && message.roomType !== 'screen',
|
|
sharemyscreen: message.roomType === 'screen' && !message.broadcaster,
|
|
broadcaster: message.roomType === 'screen' && !message.broadcaster ? self.connection.getSessionid() : null
|
|
});
|
|
self.emit('createdPeer', peer);
|
|
}
|
|
peer.handleMessage(message);
|
|
} else if (peers.length) {
|
|
peers.forEach(function (peer) {
|
|
if (message.sid) {
|
|
if (peer.sid === message.sid) {
|
|
peer.handleMessage(message);
|
|
}
|
|
} else {
|
|
peer.handleMessage(message);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
connection.on('remove', function (room) {
|
|
if (room.id !== self.connection.getSessionid()) {
|
|
self.webrtc.removePeers(room.id, room.type);
|
|
}
|
|
});
|
|
|
|
// instantiate our main WebRTC helper
|
|
// using same logger from logic here
|
|
opts.logger = this.logger;
|
|
opts.debug = false;
|
|
this.webrtc = new WebRTC(opts);
|
|
|
|
// attach a few methods from underlying lib to simple.
|
|
['mute', 'unmute', 'pauseVideo', 'resumeVideo', 'pause', 'resume', 'sendToAll', 'sendDirectlyToAll'].forEach(function (method) {
|
|
self[method] = self.webrtc[method].bind(self.webrtc);
|
|
});
|
|
|
|
// proxy events from WebRTC
|
|
this.webrtc.on('*', function () {
|
|
self.emit.apply(self, arguments);
|
|
});
|
|
|
|
// log all events in debug mode
|
|
if (config.debug) {
|
|
this.on('*', this.logger.log.bind(this.logger, 'SimpleWebRTC event:'));
|
|
}
|
|
|
|
// check for readiness
|
|
this.webrtc.on('localStream', function () {
|
|
self.testReadiness();
|
|
});
|
|
|
|
this.webrtc.on('message', function (payload) {
|
|
self.connection.emit('message', payload);
|
|
});
|
|
|
|
this.webrtc.on('peerStreamAdded', this.handlePeerStreamAdded.bind(this));
|
|
this.webrtc.on('peerStreamRemoved', this.handlePeerStreamRemoved.bind(this));
|
|
|
|
// echo cancellation attempts
|
|
if (this.config.adjustPeerVolume) {
|
|
this.webrtc.on('speaking', this.setVolumeForAll.bind(this, this.config.peerVolumeWhenSpeaking));
|
|
this.webrtc.on('stoppedSpeaking', this.setVolumeForAll.bind(this, 1));
|
|
}
|
|
|
|
connection.on('stunservers', function (args) {
|
|
// resets/overrides the config
|
|
self.webrtc.config.peerConnectionConfig.iceServers = args;
|
|
self.emit('stunservers', args);
|
|
});
|
|
connection.on('turnservers', function (args) {
|
|
// appends to the config
|
|
self.webrtc.config.peerConnectionConfig.iceServers = self.webrtc.config.peerConnectionConfig.iceServers.concat(args);
|
|
self.emit('turnservers', args);
|
|
});
|
|
|
|
this.webrtc.on('iceFailed', function (peer) {
|
|
// local ice failure
|
|
});
|
|
this.webrtc.on('connectivityError', function (peer) {
|
|
// remote ice failure
|
|
});
|
|
|
|
|
|
// sending mute/unmute to all peers
|
|
this.webrtc.on('audioOn', function () {
|
|
self.webrtc.sendToAll('unmute', {name: 'audio'});
|
|
});
|
|
this.webrtc.on('audioOff', function () {
|
|
self.webrtc.sendToAll('mute', {name: 'audio'});
|
|
});
|
|
this.webrtc.on('videoOn', function () {
|
|
self.webrtc.sendToAll('unmute', {name: 'video'});
|
|
});
|
|
this.webrtc.on('videoOff', function () {
|
|
self.webrtc.sendToAll('mute', {name: 'video'});
|
|
});
|
|
|
|
// screensharing events
|
|
this.webrtc.on('localScreen', function (stream) {
|
|
var item,
|
|
el = document.createElement('video'),
|
|
container = self.getRemoteVideoContainer();
|
|
|
|
el.oncontextmenu = function () { return false; };
|
|
el.id = 'localScreen';
|
|
attachMediaStream(stream, el);
|
|
if (container) {
|
|
container.appendChild(el);
|
|
}
|
|
|
|
self.emit('localScreenAdded', el);
|
|
self.connection.emit('shareScreen');
|
|
|
|
self.webrtc.peers.forEach(function (existingPeer) {
|
|
var peer;
|
|
if (existingPeer.type === 'video') {
|
|
peer = self.webrtc.createPeer({
|
|
id: existingPeer.id,
|
|
type: 'screen',
|
|
sharemyscreen: true,
|
|
enableDataChannels: false,
|
|
receiveMedia: {
|
|
mandatory: {
|
|
OfferToReceiveAudio: false,
|
|
OfferToReceiveVideo: false
|
|
}
|
|
},
|
|
broadcaster: self.connection.getSessionid(),
|
|
});
|
|
self.emit('createdPeer', peer);
|
|
peer.start();
|
|
}
|
|
});
|
|
});
|
|
this.webrtc.on('localScreenStopped', function (stream) {
|
|
self.stopScreenShare();
|
|
/*
|
|
self.connection.emit('unshareScreen');
|
|
self.webrtc.peers.forEach(function (peer) {
|
|
if (peer.sharemyscreen) {
|
|
peer.end();
|
|
}
|
|
});
|
|
*/
|
|
});
|
|
|
|
this.webrtc.on('channelMessage', function (peer, label, data) {
|
|
if (data.type == 'volume') {
|
|
self.emit('remoteVolumeChange', peer, data.volume);
|
|
}
|
|
});
|
|
|
|
if (this.config.autoRequestMedia) this.startLocalVideo();
|
|
}
|
|
|
|
|
|
SimpleWebRTC.prototype = Object.create(WildEmitter.prototype, {
|
|
constructor: {
|
|
value: SimpleWebRTC
|
|
}
|
|
});
|
|
|
|
SimpleWebRTC.prototype.leaveRoom = function () {
|
|
if (this.roomName) {
|
|
this.connection.emit('leave');
|
|
this.webrtc.peers.forEach(function (peer) {
|
|
peer.end();
|
|
});
|
|
if (this.getLocalScreen()) {
|
|
this.stopScreenShare();
|
|
}
|
|
this.emit('leftRoom', this.roomName);
|
|
this.roomName = undefined;
|
|
}
|
|
};
|
|
|
|
SimpleWebRTC.prototype.disconnect = function () {
|
|
this.connection.disconnect();
|
|
delete this.connection;
|
|
};
|
|
|
|
SimpleWebRTC.prototype.handlePeerStreamAdded = function (peer) {
|
|
var self = this;
|
|
var container = this.getRemoteVideoContainer();
|
|
var video = attachMediaStream(peer.stream);
|
|
|
|
// store video element as part of peer for easy removal
|
|
peer.videoEl = video;
|
|
video.id = this.getDomId(peer);
|
|
|
|
if (container) container.appendChild(video);
|
|
|
|
this.emit('videoAdded', video, peer);
|
|
|
|
// send our mute status to new peer if we're muted
|
|
// currently called with a small delay because it arrives before
|
|
// the video element is created otherwise (which happens after
|
|
// the async setRemoteDescription-createAnswer)
|
|
window.setTimeout(function () {
|
|
if (!self.webrtc.isAudioEnabled()) {
|
|
peer.send('mute', {name: 'audio'});
|
|
}
|
|
if (!self.webrtc.isVideoEnabled()) {
|
|
peer.send('mute', {name: 'video'});
|
|
}
|
|
}, 250);
|
|
};
|
|
|
|
SimpleWebRTC.prototype.handlePeerStreamRemoved = function (peer) {
|
|
var container = this.getRemoteVideoContainer();
|
|
var videoEl = peer.videoEl;
|
|
if (this.config.autoRemoveVideos && container && videoEl) {
|
|
container.removeChild(videoEl);
|
|
}
|
|
if (videoEl) this.emit('videoRemoved', videoEl, peer);
|
|
};
|
|
|
|
SimpleWebRTC.prototype.getDomId = function (peer) {
|
|
return [peer.id, peer.type, peer.broadcaster ? 'broadcasting' : 'incoming'].join('_');
|
|
};
|
|
|
|
// set volume on video tag for all peers takse a value between 0 and 1
|
|
SimpleWebRTC.prototype.setVolumeForAll = function (volume) {
|
|
this.webrtc.peers.forEach(function (peer) {
|
|
if (peer.videoEl) peer.videoEl.volume = volume;
|
|
});
|
|
};
|
|
|
|
SimpleWebRTC.prototype.joinRoom = function (name, cb) {
|
|
var self = this;
|
|
this.roomName = name;
|
|
this.connection.emit('join', name, function (err, roomDescription) {
|
|
if (err) {
|
|
self.emit('error', err);
|
|
} else {
|
|
var id,
|
|
client,
|
|
type,
|
|
peer;
|
|
for (id in roomDescription.clients) {
|
|
client = roomDescription.clients[id];
|
|
for (type in client) {
|
|
if (client[type]) {
|
|
peer = self.webrtc.createPeer({
|
|
id: id,
|
|
type: type,
|
|
enableDataChannels: self.config.enableDataChannels && type !== 'screen',
|
|
receiveMedia: {
|
|
mandatory: {
|
|
OfferToReceiveAudio: type !== 'screen' && self.config.receiveMedia.mandatory.OfferToReceiveAudio,
|
|
OfferToReceiveVideo: self.config.receiveMedia.mandatory.OfferToReceiveVideo
|
|
}
|
|
}
|
|
});
|
|
self.emit('createdPeer', peer);
|
|
peer.start();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cb) cb(err, roomDescription);
|
|
self.emit('joinedRoom', name);
|
|
});
|
|
};
|
|
|
|
SimpleWebRTC.prototype.getEl = function (idOrEl) {
|
|
if (typeof idOrEl === 'string') {
|
|
return document.getElementById(idOrEl);
|
|
} else {
|
|
return idOrEl;
|
|
}
|
|
};
|
|
|
|
SimpleWebRTC.prototype.startLocalVideo = function () {
|
|
var self = this;
|
|
this.webrtc.startLocalMedia(this.config.media, function (err, stream) {
|
|
if (err) {
|
|
self.emit('localMediaError', err);
|
|
} else {
|
|
attachMediaStream(stream, self.getLocalVideoContainer(), self.config.localVideo);
|
|
}
|
|
});
|
|
};
|
|
|
|
SimpleWebRTC.prototype.stopLocalVideo = function () {
|
|
this.webrtc.stopLocalMedia();
|
|
};
|
|
|
|
// this accepts either element ID or element
|
|
// and either the video tag itself or a container
|
|
// that will be used to put the video tag into.
|
|
SimpleWebRTC.prototype.getLocalVideoContainer = function () {
|
|
var el = this.getEl(this.config.localVideoEl);
|
|
if (el && el.tagName === 'VIDEO') {
|
|
el.oncontextmenu = function () { return false; };
|
|
return el;
|
|
} else if (el) {
|
|
var video = document.createElement('video');
|
|
video.oncontextmenu = function () { return false; };
|
|
el.appendChild(video);
|
|
return video;
|
|
} else {
|
|
return;
|
|
}
|
|
};
|
|
|
|
SimpleWebRTC.prototype.getRemoteVideoContainer = function () {
|
|
return this.getEl(this.config.remoteVideosEl);
|
|
};
|
|
|
|
SimpleWebRTC.prototype.shareScreen = function (cb) {
|
|
this.webrtc.startScreenShare(cb);
|
|
};
|
|
|
|
SimpleWebRTC.prototype.getLocalScreen = function () {
|
|
return this.webrtc.localScreen;
|
|
};
|
|
|
|
SimpleWebRTC.prototype.stopScreenShare = function () {
|
|
this.connection.emit('unshareScreen');
|
|
var videoEl = document.getElementById('localScreen');
|
|
var container = this.getRemoteVideoContainer();
|
|
var stream = this.getLocalScreen();
|
|
|
|
if (this.config.autoRemoveVideos && container && videoEl) {
|
|
container.removeChild(videoEl);
|
|
}
|
|
|
|
// a hack to emit the event the removes the video
|
|
// element that we want
|
|
if (videoEl) this.emit('videoRemoved', videoEl);
|
|
if (stream) stream.stop();
|
|
this.webrtc.peers.forEach(function (peer) {
|
|
if (peer.broadcaster) {
|
|
peer.end();
|
|
}
|
|
});
|
|
//delete this.webrtc.localScreen;
|
|
};
|
|
|
|
SimpleWebRTC.prototype.testReadiness = function () {
|
|
var self = this;
|
|
if (this.webrtc.localStream && this.sessionReady) {
|
|
self.emit('readyToCall', self.connection.getSessionid());
|
|
}
|
|
};
|
|
|
|
SimpleWebRTC.prototype.createRoom = function (name, cb) {
|
|
if (arguments.length === 2) {
|
|
this.connection.emit('create', name, cb);
|
|
} else {
|
|
this.connection.emit('create', name);
|
|
}
|
|
};
|
|
|
|
SimpleWebRTC.prototype.sendFile = function () {
|
|
if (!webrtcSupport.dataChannel) {
|
|
return this.emit('error', new Error('DataChannelNotSupported'));
|
|
}
|
|
|
|
};
|
|
|
|
module.exports = SimpleWebRTC;
|
|
|