seepur/app/Controllers/Ws/SignalingController.js

184 lines
6.4 KiB
JavaScript
Raw Normal View History

2020-04-12 14:25:42 +00:00
'use strict'
const User = use('App/Models/User');
const UserChildUtils = use('App/Utils/UserChildUtils');
2020-04-13 01:52:08 +00:00
const Child = use('App/Models/Child');
2020-04-12 14:25:42 +00:00
const IceServer = use('App/Models/IceServer');
2020-04-13 01:28:33 +00:00
const UserChannel = use('App/Controllers/Ws/UserChannelController')
2020-04-12 14:25:42 +00:00
const calls = {};
class SignalingController {
constructor({socket, request, auth, call}) {
this.callId = socket.topic.split(':')[1];
this.user = auth.user;
this.socket = socket;
this.request = request;
2020-04-13 01:52:08 +00:00
this.register(call)
.then(_ => {
console.log(`User #${this.user.id} connected to call ${this.callId}`);
})
.catch(e => {
console.error(e);
});
2020-04-12 14:25:42 +00:00
}
2020-04-13 01:28:33 +00:00
async register(callModel) {
2020-04-13 01:52:08 +00:00
if (!calls[this.callId])
2020-04-12 23:33:24 +00:00
calls[this.callId] =
new CallSession(callModel, this.onCallEnded.bind(this));
2020-04-13 01:52:08 +00:00
else
2020-04-12 14:25:42 +00:00
console.log(`Call #${this.callId} Already Found`);
const callSession = calls[this.callId];
2020-04-13 01:52:08 +00:00
success = await callSession.registerUser(this.user, this.socket)
if (!success) throw new Error('Invalid User');
return true;
2020-04-12 14:25:42 +00:00
}
2020-04-12 23:33:24 +00:00
onCallEnded(callId) {
console.log(`Call ${callId} Ended`);
delete calls[callId];
}
2020-04-12 14:25:42 +00:00
onClose() {
2020-04-12 23:33:24 +00:00
calls[this.callId].removeUser(this.user);
2020-04-12 14:25:42 +00:00
console.log(`User #${this.user.id} left call ${this.callId}`);
}
}
class CallSession {
// states: NEW -> STARTED -> IN_PROGRESS -> ENDED
2020-04-12 23:33:24 +00:00
constructor(callModel, onCallEndedCallback) {
this.onCallEndedCallback = onCallEndedCallback;
2020-04-12 14:25:42 +00:00
this.callId = callModel.id;
this.callModel = callModel;
this.state = callModel.state;
this.user_1 = {id: callModel.user_1, socket: null, userModel: null};
this.user_2 = {id: callModel.user_2, socket: null, userModel: null};
this.startTime = Date.now();
2020-04-12 23:33:24 +00:00
this.heartbeat =
setInterval(this.onHeartbeat.bind(this), 1000); // Every second
2020-04-12 14:25:42 +00:00
}
onHeartbeat() {
2020-04-12 23:33:24 +00:00
const now = Date.now();
const elapsed = ((now - this.startTime) / 60000).toPrecision(1);
const newStateTimeout = 5; // 5 min
const startedStateTimeout = 10; // 10 min
2020-04-13 01:52:08 +00:00
// console.log(`Heartbeat for call #${this.callId} State: ${
// this.state}, time-elapsed: ${(elapsed)}min`);
2020-04-12 23:33:24 +00:00
if (this.state === 'ENDED') return this.endCall();
if (this.state === 'NEW' && elapsed >= newStateTimeout)
return this.endCall();
if (this.state === 'STARTED' && elapsed >= startedStateTimeout)
return this.endCall();
}
removeUser(user) {
let userIndex = -1;
if (this.user_1.id === user.id)
userIndex = 1;
else if (this.user_2.id === user.id)
userIndex = 2;
if (userIndex < 0) return false;
this[`user_${userIndex}`].userModel = null;
this[`user_${userIndex}`].socket = null;
this.updateState();
if (this.state === 'ENDED') this.endCall();
2020-04-12 14:25:42 +00:00
}
async registerUser(user, socket) {
2020-04-13 01:52:08 +00:00
if (!this.child) this.child = await Child.find(this.callModel.child_id);
2020-04-12 14:25:42 +00:00
let userIndex = -1;
if (this.user_1.id === user.id)
userIndex = 1;
else if (this.user_2.id === user.id)
userIndex = 2;
if (userIndex < 0) return false;
2020-04-13 01:28:33 +00:00
const otherUser = userIndex === 1 ? 2 : 1;
2020-04-12 14:25:42 +00:00
this[`user_${userIndex}`].userModel = user;
this[`user_${userIndex}`].socket = socket;
socket.on('wrtc:sdp:offer', this.onSdpOffer.bind(this));
socket.on('wrtc:sdp:answer', this.onSdpAnswer.bind(this));
socket.on('wrtc:ice', this.onIceCandidate.bind(this));
await this.updateState();
2020-04-13 01:28:33 +00:00
if (this.state === 'STARTED') {
2020-04-12 23:33:24 +00:00
await this.sendStandby(socket, userIndex);
2020-04-13 01:28:33 +00:00
// Send event to other user about the call
2020-04-13 01:52:08 +00:00
console.log(
`trying to find user ${this[`user_${otherUser}`].id} channel...`);
2020-04-13 01:28:33 +00:00
const otherUserChannel =
UserChannel.getUserChannel(this[`user_${otherUser}`].id);
2020-04-13 01:52:08 +00:00
if (otherUserChannel) {
// console.log(otherUserChannel);
console.log('Sending notification to other user');
const payload = {callId: this.callId, child: this.child.toJSON()};
console.dir(payload);
otherUserChannel.emit('call:incoming', payload);
}
2020-04-13 01:28:33 +00:00
} else if (this.state === 'IN_PROGRESS')
2020-04-12 23:33:24 +00:00
await this.sendStart(socket, userIndex);
2020-04-12 14:25:42 +00:00
return true;
}
2020-04-12 23:33:24 +00:00
endCall() {
this.state = 'ENDED';
if (this.callModel.state != 'ENDED') {
this.callModel.state = this.state;
this.callModel.save();
}
if (this.user_1.socket) this.user_1.socket.close();
if (this.user_2.socket) this.user_1.socket.close();
clearInterval(this.heartbeat);
this.onCallEndedCallback(this.callId);
}
2020-04-12 14:25:42 +00:00
async sendStandby(socket, userIndex) {
2020-04-12 23:33:24 +00:00
console.log(`Call #${this.callId} sendStandby -> ${userIndex}`);
2020-04-12 14:25:42 +00:00
const iceServers = (await IceServer.all()).rows.map(i => i.toJSON());
socket.emit('call:standby', {iceServers, id: userIndex});
}
async sendStart(socket, userIndex) {
2020-04-12 23:33:24 +00:00
console.log(`Call #${this.callId} sendStart -> ${userIndex}`);
2020-04-12 14:25:42 +00:00
const iceServers = (await IceServer.all()).rows.map(i => i.toJSON());
socket.emit('call:start', {iceServers, id: userIndex});
}
async updateState() {
console.log(`Call #${this.callId} state=${this.state}`);
switch (this.state) {
case 'NEW':
if (this.areAllPartnersConnected())
this.state = 'IN_PROGRESS';
else
this.state = 'STARTED';
2020-04-12 23:33:24 +00:00
break;
2020-04-12 14:25:42 +00:00
case 'STARTED':
if (this.areAllPartnersConnected()) this.state = 'IN_PROGRESS';
2020-04-12 23:33:24 +00:00
break;
case 'IN_PROGRESS':
if (!this.areAllPartnersConnected()) this.state = 'ENDED';
break;
2020-04-12 14:25:42 +00:00
}
2020-04-12 23:33:24 +00:00
this.callModel.state = this.state;
await this.callModel.save();
2020-04-12 14:25:42 +00:00
console.log(`Call #${this.callId} state=${this.state}`);
}
areAllPartnersConnected() {
return !!this.user_1.socket && !!this.user_2.socket;
}
async onIceCandidate(payload) {
2020-04-12 23:33:24 +00:00
const {from = payload.id, ice} = payload;
2020-04-12 14:25:42 +00:00
const to = from === 1 ? 2 : 1;
2020-04-12 23:33:24 +00:00
this[`user_${to}`].socket.emit('wrtc:ice', {ice});
2020-04-12 14:25:42 +00:00
console.log(`[Signal] [onIceCandidate] ${from} -> ${to}`)
return true;
}
async onSdpOffer(payload) {
const {from = payload.id, sdp} = payload;
const to = from === 1 ? 2 : 1;
this[`user_${to}`].socket.emit('wrtc:sdp:offer', {sdp});
console.log(`[Signal] [onSdpOffer] ${from} -> ${to}`)
return true;
}
async onSdpAnswer(payload) {
const {from = payload.id, sdp} = payload;
const to = from === 1 ? 2 : 1;
this[`user_${to}`].socket.emit('wrtc:sdp:answer', {sdp});
console.log(`[Signal] [onSdpAnswer] ${from} -> ${to}`)
return true;
}
}
module.exports = SignalingController