WebRTC audio/video call and conferencing server.
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.
 
 
 
 
 
 

493 lines
14 KiB

/*
* Spreed WebRTC.
* Copyright (C) 2013-2014 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
define(['underscore', 'text!partials/chat.html', 'text!partials/chatroom.html'], function(_, templateChat, templateChatroom) {
return ["$compile", "safeDisplayName", "mediaStream", "safeApply", "desktopNotify", "translation", "playSound", "fileUpload", "randomGen", "buddyData", "$timeout", function($compile, safeDisplayName, mediaStream, safeApply, desktopNotify, translation, playSound, fileUpload, randomGen, buddyData, $timeout) {
var displayName = safeDisplayName;
var group_chat_id = "";
var controller = ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
$scope.layout.chat = false;
$scope.layout.chatMaximized = false;
var ctrl = this;
var rooms = ctrl.rooms = {};
ctrl.visibleRooms = [];
ctrl.group = group_chat_id;
ctrl.get = function(id) {
return ctrl.rooms[id];
}
$scope.currentRoom = null;
$scope.currentRoomActive = false;
$scope.getVisibleRooms = function() {
var res = [];
for (var i = 0; i < ctrl.visibleRooms.length; i++) {
var r = rooms[ctrl.visibleRooms[i]];
if (!r || r.id === ctrl.group) {
continue;
}
res.push(r);
}
return res;
};
$scope.getGroupRoom = function() {
return rooms[ctrl.group];
};
mediaStream.api.e.on("received.chat", function(event, id, from, data, p2p) {
//console.log("received", data, id, from);
var roomid = id;
if (roomid === mediaStream.api.id) {
roomid = from;
} else {
if (roomid !== ctrl.group && from !== mediaStream.api.id) {
console.log("Received chat message for invalid room", roomid, id, from);
return;
}
}
var with_message = !! data.Message;
var room = rooms[roomid];
if (!room) {
if (!with_message) {
return;
}
// No room with this id, get one with the from id
$scope.$emit("startchat", from, {
restore: with_message
});
room = rooms[from];
}
if (with_message && from !== mediaStream.api.id) {
room.newmessage = true;
room.peerIsTyping = "no";
room.p2p( !! p2p);
}
room.$broadcast("received", from, data);
safeApply(room);
});
mediaStream.api.e.on("received.userleftorjoined", function(event, dataType, data) {
var room = rooms[data.Id];
if (room) {
switch (dataType) {
case "Left":
if (data.Status !== "soft") {
room.enabled = false;
room.$broadcast("received", data.Id, {
Type: "LeftOrJoined",
"LeftOrJoined": "left"
});
safeApply(room);
}
break;
case "Joined":
if (!room.enabled) {
room.enabled = true;
_.delay(function() {
room.$broadcast("received", data.Id, {
Type: "LeftOrJoined",
"LeftOrJoined": "joined"
});
safeApply(room);
}, 1000);
}
break;
default:
break;
}
}
});
$scope.$parent.$on("startchat", function(event, id, options) {
//console.log("startchat requested", event, id);
if (id === group_chat_id) {
$scope.showGroupRoom(null, options);
} else {
$scope.showRoom(id, {
title: translation._("Chat with")
}, options);
}
});
}];
var compile = function(tElement, tAttrs) {
var chat = $compile(templateChatroom);
return function(scope, iElement, iAttrs, controller) {
var pane = iElement.find(".chatpane");
scope.showGroupRoom = function(settings, options) {
var stngs = $.extend({
title: translation._("Room chat")
}, settings);
return scope.showRoom(controller.group, stngs, options);
};
scope.showRoom = function(id, settings, opts) {
var options = $.extend({}, opts);
var subscope = controller.rooms[id];
var index = controller.visibleRooms.length;
if (!subscope) {
console.log("Create new chatroom", [id]);
controller.visibleRooms.push(id);
subscope = controller.rooms[id] = scope.$new();
translation.inject(subscope);
subscope.id = id;
subscope.isgroupchat = id === controller.group ? true : false;
subscope.index = index;
subscope.settings = settings;
subscope.visible = false;
subscope.newmessage = false;
subscope.enabled = true;
subscope.peerIsTyping = "no";
subscope.firstmessage = true;
subscope.p2pstate = false;
subscope.active = false;
subscope.pending = 0;
if (!subscope.isgroupchat) {
buddyData.push(id);
}
subscope.hide = function() {
scope.hideRoom(id);
};
//TODO(longsleep): This is currently never called. Find a suitable way to clean up old chats.
subscope.kill = function() {
if (!subscope.isgroupchat) {
buddyData.pop(id);
}
scope.killRoom(id);
};
subscope.seen = function() {
subscope.pending = 0;
scope.$emit("chatseen", subscope.id);
if (subscope.newmessage) {
subscope.newmessage = false;
subscope.$broadcast("seen");
}
};
subscope.deactivate = function() {
scope.deactivateRoom();
};
subscope.toggleMax = function() {
scope.toggleMax();
};
subscope.sendChat = function(to, message, status, mid, noloop) {
//console.log("send chat", to, scope.peer);
var peercall = mediaStream.webrtc.findTargetCall(to);
if (message && !mid) {
mid = randomGen.random({
hex: true
});
}
if (peercall && peercall.peerconnection.datachannelReady) {
subscope.p2p(true);
// Send out stuff through data channel.
_.delay(function() {
mediaStream.api.apply("sendChat", {
send: function(type, data) {
// We also send to self, to display our own stuff.
if (!noloop) {
mediaStream.api.received({
Type: data.Type,
Data: data,
From: mediaStream.api.id,
To: peercall.id
});
}
return peercall.peerconnection.send(data);
}
})(to, message, status, mid);
}, 100);
} else {
subscope.p2p(false);
_.delay(function() {
mediaStream.api.send2("sendChat", function(type, data) {
if (!noloop) {
//console.log("looped to self", type, data);
mediaStream.api.received({
Type: data.Type,
Data: data,
From: mediaStream.api.id,
To: to
});
}
})(to, message, status, mid);
}, 100);
}
return mid;
};
subscope.p2p = function(state) {
if (state !== subscope.p2pstate) {
subscope.p2pstate = state;
subscope.$broadcast("p2p", state);
}
};
//console.log("Creating new chat room", controller, subscope, index);
subscope.$on("submit", function(event, input) {
subscope.seen();
var mid = subscope.sendChat(event.targetScope.id, input);
event.targetScope.$broadcast("received", null, {
Type: "Message",
Status: {
State: "sent",
"Mid": mid
}
});
});
subscope.$on("submitseen", function(event, pending) {
//console.log("submitseen", pending);
subscope.sendChat(event.targetScope.id, null, {
SeenMids: pending
}, null, true);
});
subscope.$on("submitreceived", function(event, mid) {
subscope.sendChat(event.targetScope.id, null, {
State: "delivered",
Mid: mid
}, null, true);
});
subscope.$on("typing", function(event, params) {
if (params.who === "local") {
var room = event.targetScope.id;
subscope.seen();
//console.log("typing event", params.status);
if (!subscope.isgroupchat) {
// Transmit typing events to private chats.
subscope.sendChat(event.targetScope.id, null, {
Typing: params.status
});
}
} else {
subscope.peerIsTyping = params.status;
//console.log("peer typing event", params.status, subscope.peerIsTyping);
}
safeApply(subscope);
});
subscope.$on("incoming", function(event, message, from, userid) {
if (from !== userid) {
subscope.pending++;
scope.$emit("chatincoming", subscope.id);
}
if (subscope.firstmessage || !desktopNotify.windowHasFocus) {
var room = event.targetScope.id;
// Make sure we are not in group chat or the message is from ourselves
// before we beep and shout.
if (!subscope.isgroupchat && from !== userid) {
playSound.play("message1");
desktopNotify.notify(translation._("Message from ") + displayName(from), message);
}
subscope.firstmessage = false;
}
});
chat(subscope, function(clonedElement, $scope) {
pane.append(clonedElement);
$scope.element = clonedElement;
$scope.visible = true;
if (options.autofocus) {
_.defer(function() {
$scope.$broadcast("focus");
});
}
// Support drag and drop file uploads in Chat.
var namespace = "file_" + scope.id;
var binder = fileUpload.bindDrop(namespace, clonedElement, _.bind(function(files) {
console.log("File dragged", files);
_.each(files, _.bind(function(f) {
var info = $.extend({
id: f.id
}, f.info);
console.log("Advertising file", f, info);
$scope.sendChat(subscope.id, "File", {
FileInfo: info
});
}, this));
}, this));
binder.namespace = function() {
// Inject own id into namespace.
return namespace + "_" + scope.myid;
};
});
} else {
if (options.restore) {
if (!subscope.visible) {
controller.visibleRooms.push(id);
subscope.index = index;
subscope.visible = true;
}
}
if (options.autofocus && subscope.visible) {
subscope.$broadcast("focus");
}
}
if (!options.noactivate) {
scope.activateRoom(subscope.id, true);
}
if (options.restore && !options.noenable) {
if (!scope.layout.chat) {
scope.layout.chat = true;
}
}
safeApply(subscope);
return subscope;
};
scope.hideRoom = function(id) {
var subscope = controller.rooms[id];
if (!subscope) {
console.log("hideRoom called for unknown room", id);
return;
}
var element = subscope.element;
var index = subscope.index;
controller.visibleRooms.splice(index, 1);
subscope.visible = false;
subscope.firstmessage = true;
// Refresh index of the rest of the rooms.
_.each(controller.visibleRooms, function(id, idx) {
var s = controller.rooms[id];
//console.log("updated idx", idx, s.index);
s.index = idx;
});
if (scope.currentRoom === subscope) {
scope.currentRoom = null;
scope.currentRoomActive = false;
}
if (!controller.visibleRooms.length) {
scope.showGroupRoom(null, {
restore: true,
noenable: true,
noactivate: true
});
// If last visible room was removed, hide chat.
scope.layout.chat = false;
}
};
scope.killRoom = function(id) {
scope.hideRoom(id);
var subscope = controller.rooms[id];
if (!subscope) {
return;
}
delete controller.rooms[id];
$timeout(function() {
subscope.$destroy();
}, 0);
};
scope.toggleMax = function() {
scope.layout.chatMaximized = !scope.layout.chatMaximized;
};
scope.activateRoom = function(id, active) {
var subscope = controller.rooms[id];
if (!subscope) {
return;
}
var visible = !! scope.layout.chat;
var flip = false;
//console.log("toggleActive", active, id, scope.currentRoom, scope.currentRoom == subscope, subscope.active);
if (scope.currentRoom == subscope) {
subscope.active = active;
scope.currentRoomActive = true;
if (visible) {
flip = true;
}
} else {
if (scope.currentRoom) {
scope.currentRoom.active = false;
//scope.currentRoom.hide();
if (visible) {
flip = true;
}
}
if (active) {
scope.currentRoom = subscope;
scope.currentRoomActive = true;
}
subscope.active = active;
}
if (flip) {
pane.toggleClass("flip");
}
};
scope.deactivateRoom = function() {
scope.currentRoomActive = false;
};
scope.$watch("layout.chat", function(chat) {
if (!chat) {
pane.removeClass("flip");
}
scope.layout.chatMaximized = false;
});
scope.$on("room", function(event, room) {
var subscope = scope.showGroupRoom(null, {
restore: true,
noenable: true,
noactivate: true
});
if (room) {
var msg = $("<span>").text(translation._("You are now in room %s ...", room));
subscope.$broadcast("display", null, $("<i>").append(msg));
}
});
};
};
return {
restrict: 'E',
replace: true,
scope: true,
template: templateChat,
controller: controller,
compile: compile
}
}];
});