vchat
Version:
An experimental video chat server/client hybrid
264 lines (262 loc) • 9.2 kB
JavaScript
import Ember from "ember";
const { Controller, inject, on, run } = Ember;
export default Controller.extend({
debug: inject.service(),
media: inject.service(),
settings: inject.service(),
socketClient: inject.service(),
socketServer: inject.service(),
uri: inject.service(),
//--------------------------------------------------------------------------
//Sources array full of object url's and socket id's
src: [], //an array full of id's and url's to stream objects
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
//Server/Client mode, view state booleans, and input values
hostMode: false,
hostCall: false,
hostPort: localStorage.hostPort || '9090',
chooseMode: true,
joinCall: false,
joinAddress: localStorage.joinAddress || 'https://localhost/', //our target address, ip, or url
joinPort: localStorage.joinPort || '9090', //our target port
//--------------------------------------------------------------------------
bindEvents: on('init', function(){
let debug = this.get('debug');
let socketClient = this.get('socketClient');
let socketServer = this.get('socketServer');
let media = this.get('media');
this.send('openModal', 'modal-waiting', 'Detecting Camera...');
media.on('connect', () => {
this.send('closeModal');
});
media.on('error', (err) => {
this.send('openModal', 'modal-alert', err);
});
socketClient.on('connect', () => {
this.addSource(0, this.get('media.mySrc'));
debug.debug('WebRTC connection established');
this.send('closeModal');
});
socketClient.on('connect_error', (err) => {
debug.error('connection error!');
debug.error(err);
this.send('openModal', 'modal-alert', 'Unable to connect to: ' + this.get('socketClient.connectedTo'));
this.handleEnd();
});
socketClient.on('disconnect', () => {
debug.debug('socket disconnected.');
this.handleEnd();
});
socketClient.on('addSource', (id, src) => {
this.addSource(id, src);
debug.debug('adding peer source, id:' + id);
});
socketClient.on('removeSource', (id) => {
this.removeSource(id);
debug.debug('removing peer source, id:' + id);
});
socketServer.on('open', () => {
this.readyToHost();
debug.debug('socket server opened successfully');
});
socketServer.on('close', () => {
this.readyToCall();
debug.debug('socket server closed');
});
socketServer.on('error', (err) => {
this.send('openModal', 'modal-alert', err);
debug.debug('socket server had an error: ' + err);
});
}),
actions: {
close: function(el){
this.send('openModal', 'modal-kick', el);
},
snapshot: function(blob){
this.send('openModal', 'modal-snapshot', blob);
},
hostCall: function(){
this.setProperties({
chooseMode: false,
joinCall: false,
hostCall: true
});
},
joinCall: function(){
this.setProperties({
chooseMode: false,
joinCall: true,
hostCall: false
});
},
choose: function(){
this.showChooser();
},
connect: function(){
if(this.get('media.mySrc'))
{
localStorage.joinAddress = this.get('joinAddress');
localStorage.joinPort = this.get('joinPort');
this.send('openModal', 'modal-waiting', 'Connecting...');
this.get('socketClient').connectServer(this.get('joinAddress'), this.get('joinPort'));
}
else
{
this.send('openModal', 'modal-alert', 'This device does not have a camera and microphone.');
}
},
host: function(){
if(this.get('media.mySrc'))
{
this.send('openModal', 'modal-waiting', 'Connecting...');
localStorage.hostPort = this.get('hostPort');
this.setProperties({
chooseMode: false,
joinCall: false,
hostCall: false,
hostMode: true
});
this.get('socketServer').listen(parseInt(this.get('hostPort'), 10));
}
else
{
this.send('openModal', 'modal-alert', 'This device does not have a camera and microphone.');
}
},
disconnect: function(){
this.get('socketClient').disconnect();
}
},
readyToHost: function(){
this.get('socketClient').connectServer('localhost', this.get('hostPort'));
},
readyToCall: function(){
this.set('hostMode', false);
this.handleEnd();
},
//--------------------------------------------------------------------------
//Wrapper to perform all maniuplations needed to show our host/join
//chooser
showChooser: function(){
this.setProperties({
chooseMode: true,
joinCall: false,
hostCall: false
});
},
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
//The list of things to reset when our conversation has effectively ended
handleEnd: function(){
let srcs = this.get('src');
srcs.forEach((src) => {
if(src.id !== 0)
{
this.revokeObject(src.src);
}
});
this.get('src').clear();
this.get('socketClient').disconnect();
if(this.get('hostMode'))
{
this.setProperties({
joinCall: false,
hostMode: false
});
this.get('socketServer').stopListening();
}
else
{
this.showChooser();
}
},
//--------------------------------------------------------------------------
//Helper function to find an ideal place to insert a new video sqare in the grid
findMinCoords: function(){
let maxx = 0;
let maxy = 0;
let srcs = this.get('src');
let matrix = [];
srcs.forEach((src) => {
if(src.col > maxx)
{
maxx = src.col;
}
if(src.row > maxy)
{
maxy = src.row;
}
});
maxx = parseInt(maxx, 10);
maxy = parseInt(maxy, 10);
if(maxx > maxy)
{
maxy = maxx;
}
else
{
maxx = maxy;
}
for(let a = 0; a < maxx; a++)
{
matrix[a] = [];
for(let y = 0; y < maxy; y++)
{
matrix[a][y] = false;
}
}
srcs.forEach((src) => {
matrix[parseInt(src.col, 10)-1][parseInt(src.row, 10)-1] = true;
});
for(let c = 0; c < maxx; c++)
{
for(let i = 0; i < srcs.length; i++)
{
if(!matrix[c][i])
{
return {x: c+1, y: i+1};
}
if(!matrix[i][c])
{
return {x: i+1, y: c+1};
}
}
}
return {x:1, y: maxy+1};
},
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
//Helper functions to manage source blobs
addSource: function(id, src){
let coord = this.findMinCoords();
this.get('src').pushObject(Ember.Object.create({
col: coord.x,
row: coord.y,
sizex: 5,
sizey: 5,
id: id,
src: src,
effect: 'color',
isOwner: 0 !== id && this.get('hostMode'),
volume: 0 === id ? 0 : 1 //we probably don't want to hear ourselves talk...
}));
},
removeSource: function(id){
//just a note, we never need to worry about our own stream being passed
//in here. The stream id passed in will always come from a socket event
let src = this.get('src').findBy('id', id);
if(src)
{
this.get('src').removeObject(src);
this.revokeObject(src.get('src'));
}
},
revokeObject: function (objectUrl){
run.scheduleOnce('afterRender', () => {
let url = window.URL;
url.revokeObjectURL(objectUrl);
});
}
//--------------------------------------------------------------------------
});