roomrtc
Version:
RoomRTC enables quick development of webRTC
319 lines (289 loc) • 10.6 kB
JavaScript
const events = require("eventemitter2");
const adapter = require("webrtc-adapter");
const socketio = require("socket.io-client");
const Promise = require("promise");
const WebRTC = require("./webrtc");
const EventEmitter = events.EventEmitter2;
module.exports = class RoomRTC extends EventEmitter {
constructor(opts) {
super();
this.self = this;
this.logger = console;
this.options = opts || {};
this.connection = null;
this.connectionReady = false;
this.roomName = null;
this.localStream = null;
this.config = {
autoConnect: true,
autoCallPeers: true,
connectMediaServer: false,
username: false,
url: "https://roomrtc-signaling-server.herokuapp.com",
media: {
audio: false,
video: true
},
peerMediaConstraints: {
offerToReceiveVideo: true,
offerToReceiveAudio: true
},
localVideo: {
autoplay: true,
mirror: true,
muted: true
}
}
// override default config
for (let item in this.options) {
if (this.options.hasOwnProperty(item)) {
this.config[item] = this.options[item];
}
}
// init webrtc
this.webrtc = new WebRTC();
this.webrtc.on("peerStreamAdded", this.handlePeerStreamAdded.bind(this));
this.webrtc.on("peerStreamRemoved", this.handlePeerStreamRemoved.bind(this));
this.webrtc.on("message", payload => {
this.logger.debug("send message command", payload);
this.connection.emit("message", payload);
});
// debug all webrtc events
this.webrtc.onAny((event, value) => {
this.emit.call(this, event, value);
})
// log all data to the console
this.onAny(this.logger.debug.bind(this.logger, "RoomRTC event:"));
if (this.config.autoConnect) {
// init connection to signaling server
this.connect();
}
}
connect() {
this.connection = socketio.connect(this.config.url);
this.connection.on("connect", () => {
this.connectionReady = true;
this.connectionId = this.connection.id
this.emit("connected", this.connectionId);
// this.verifyReady();
});
this.connection.on("message", msg => {
// this.logger.debug("Receive message from singaling server:", msg);
if (msg.type == "offer") {
// create answer
let peer = this.webrtc.peers.find(p => p.id === msg.from);
if (!peer) {
this.logger.debug("Creating a new peer connection to:", msg.from);
peer = this.webrtc.createPeerConnection({
id: msg.from,
sid: msg.sid
});
this.emit("peerCreated", peer);
}
peer.processMessage(msg);
} else {
// process message
let peers = this.webrtc.peers;
peers.forEach(peer => {
if (msg.sid) {
if (peer.sid === msg.sid) {
peer.processMessage(msg);
}
} else {
peer.processMessage(msg);
}
});
}
});
this.connection.on('ready', data => {
this.emit("readyToCall", this.connectionId);
});
this.connection.on("remove", info => {
this.logger.info("removePeerConnectionById", info);
this.webrtc.removePeerConnectionById(info.id);
})
this.connection.on("iceservers", servers => {
this.logger.debug("Got iceservers info", servers);
// TODO: concat to peer connection
});
}
disconnect() {
if (this.connection) {
this.connection.disconnect();
delete this.connection;
}
}
/**
* Verify connection ready then emit the event
*/
verifyReady() {
if (this.connectionReady) {
if (!this.config.connectMediaServer) {
this.emit("readyToCall", this.connectionId);
} else {
this.emit('readyToJoinMediaServer', this.connectionId);
}
}
}
/**
* join to exists room
* @return roomData(clients, number of participants)
*/
joinRoom(name) {
if (!name) return Promise.reject("No room to join");
// set name of the room wanna join
this.roomName = name;
return new Promise((resolve, reject) => {
this.connection.emit("join", name, (err, roomData) => {
this.logger.debug("join callback: ", err, roomData);
if (err) {
this.emit("error", err);
return reject(err);
} else if (!this.config.connectMediaServer && this.config.autoCallPeers) {
// try to call everyone in the room (without media server)
let clients = roomData.clients || [];
for (let id in clients) {
// let clientConstraints = clients[id];
let peer = this.webrtc.createPeerConnection({
id: id
});
this.emit("peerCreated", peer);
peer.start();
}
this.emit("roomJoined", name);
return resolve(roomData);
} else {
this.logger.info('joinRoom media server ok!');
return resolve(roomData);
}
});
});
}
/**
* Request to join media server
*/
joinMediaServer(name) {
if (!name) return Promise.reject("No room to join");
// set name of the room wanna join
this.roomName = name;
return Promise.resolve()
.then(() => {
let username = this.config.username || this.connectionId;
// TODO: Rename webrtc.createPeerConnection() --> webrtc.createPeer()
let peer = this.webrtc.createPeerConnection({
id: this.connectionId,
connectMediaServer: this.config.connectMediaServer
});
this.emit("peerCreated", peer);
peer.start();
// do not peer.start();
// return peer.pc.createOffer({
// offerToReceiveAudio: 1,
// offerToReceiveVideo: 1
// });
})
// .then((description) => {
// // send back capabilities info
// this.connection.emit('message', {
// type: 'capabilities',
// usePlanB: this.webrtc.isPlanB(),
// capabilities: description.sdp
// })
// })
}
/**
* Create a new room
* @return name of the room
*/
createRoom(name) {
if (!name) return Promise.reject("No room to create");
// send command to create a room
return new Promise((resolve, reject) => {
this.connection.emit('create', name, (err, roomName) => {
if (err) {
this.emit("error", err);
} else {
this.roomName = roomName;
this.emit("roomCreated", roomName);
return resolve(roomName);
}
});
});
}
/**
* Request to access a local camera and microphone
* @return: a promise handle
*/
initMediaSource(mediaConstraints, devName) {
this.logger.debug("Requesting local media ...");
let dev = devName || "default";
let constrains = mediaConstraints || this.config.media;
return navigator.mediaDevices.getUserMedia(constrains)
.then(stream => {
// TODO: add event all to all tracks of the stream, multiple streams ?
this.localStream = stream;
this.webrtc.addLocalStream(stream);
return stream;
});
}
stop(stream) {
stream = stream || this.localStream;
this.stopStream(stream);
}
/**
* Replaces the deprecated MediaStream.stop method
*/
stopStream(stream) {
if (!stream) return;
// stop audio tracks
for (let track of stream.getAudioTracks()) {
try {
track.stop();
} catch (err) {
this.logger.debug("stop audio track error:", err);
}
}
// stop video tracks
for (let track of stream.getVideoTracks()) {
try {
track.stop();
} catch (err) {
this.logger.debug("stop video track error:", err);
}
}
// stop stream
if (typeof stream.stop === 'function') {
try {
stream.stop();
} catch (err) {}
}
}
/**
* utils to get, revoke a stream as url
* @return: blobUrl
*/
getStreamAsUrl(stream) {
var URL = window.URL || window.webkitURL;
return URL.createObjectURL(stream);
}
/**
* revokeObjectURL
*/
revokeObjectURL(url) {
var URL = window.URL || window.webkitURL;
URL.revokeObjectURL(url);
}
/**
* handle streaming
*/
handlePeerStreamAdded(peer, stream) {
let sid = stream && stream.id;
this.logger.debug("A new remote video added:", peer.id, sid);
this.emit("videoAdded", peer, stream);
}
handlePeerStreamRemoved(peer, stream) {
let sid = stream && stream.id;
this.logger.debug("A remote video removed:", peer.id, sid);
this.emit("videoRemoved", peer, stream);
}
}