'use strict' const User = use('App/Models/User'); const UserChildUtils = use('App/Utils/UserChildUtils'); const Call = use('App/Models/Call'); const IceServer = use('App/Models/IceServer'); 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; this.register(call); console.log(`User #${this.user.id} connected to call ${this.callId}`); } register(callModel) { if (!calls[this.callId]) calls[this.callId] = new CallSession(callModel); else console.log(`Call #${this.callId} Already Found`); const callSession = calls[this.callId]; callSession.registerUser(this.user, this.socket) .then( success => { }) .catch( error => { }); } onClose() { console.log(`User #${this.user.id} left call ${this.callId}`); } } class CallSession { // states: NEW -> STARTED -> IN_PROGRESS -> ENDED constructor(callModel) { 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(); this.heartbeat = setInterval(this.onHeartbeat.bind(this), 5000); } onHeartbeat() { console.log(`We have ${Object.keys(calls).length} ongoing calls. Ids=${ Object.keys(calls)}`) console.log(`Heartbeat for call #${this.callId} State: ${this.state}`); } async registerUser(user, socket) { 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 = 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(); if (this.state === 'STARTED') await this.sendStandby(socket, userIndex); if (this.state === 'IN_PROGRESS') await this.sendStart(socket, userIndex); return true; } async sendStandby(socket, userIndex) { const iceServers = (await IceServer.all()).rows.map(i => i.toJSON()); socket.emit('call:standby', {iceServers, id: userIndex}); } async sendStart(socket, userIndex) { 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'; case 'STARTED': if (this.areAllPartnersConnected()) this.state = 'IN_PROGRESS'; } console.log(`Call #${this.callId} state=${this.state}`); } areAllPartnersConnected() { return !!this.user_1.socket && !!this.user_2.socket; } async onIceCandidate(payload) { const {from = id, ice} = payload; const to = from === 1 ? 2 : 1; this[`user_${to}`].socket.emit('wrtc:ice', {sdp}); 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