Compare commits
147 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e781eb6423 | ||
|
|
15f4f03ba3 | ||
|
|
4f9b6f7180 | ||
|
|
b36ec5fd01 | ||
|
|
ac95ea03fe | ||
|
|
ae535fcb7d | ||
|
|
957cc6afc1 | ||
|
|
16fdd59617 | ||
|
|
fabf8f42c6 | ||
|
|
c98a56dc37 | ||
|
|
deb68dd420 | ||
|
|
0fd1a7fa08 | ||
|
|
c6ff8aa5dd | ||
|
|
06f025e92a | ||
|
|
f14329f2cd | ||
|
|
53e525597a | ||
|
|
54b3cbcf94 | ||
|
|
2852740e71 | ||
|
|
5322ba086b | ||
|
|
d2f95f3c81 | ||
|
|
3747251821 | ||
|
|
159ba82167 | ||
|
|
e34a8e6b60 | ||
|
|
17a6e360a2 | ||
|
|
b690f5d4a1 | ||
|
|
30f3168bf7 | ||
|
|
115f2e4663 | ||
|
|
fa15a75928 | ||
|
|
4db75446f3 | ||
|
|
d9f7b8b6cc | ||
|
|
05bbfda5bb | ||
|
|
e465b3ed90 | ||
|
|
1825f47ef2 | ||
|
|
169d613ac4 | ||
|
|
3dac5eeff5 | ||
|
|
f79651f806 | ||
|
|
6048d0a325 | ||
|
|
6f12446c99 | ||
|
|
af682f8727 | ||
|
|
9123923818 | ||
|
|
aee7a8e1bd | ||
|
|
5b44edb3cc | ||
|
|
806d4ea443 | ||
|
|
1e35ca5e4d | ||
|
|
d4f00d76ab | ||
|
|
37282e63b3 | ||
|
|
4b218499ae | ||
|
|
f16a1cdf44 | ||
|
|
702f02568d | ||
|
|
b6808d87bc | ||
|
|
8042bd2aa6 | ||
|
|
053b2d5af2 | ||
|
|
222164333b | ||
|
|
db50810e4b | ||
|
|
720851dcb9 | ||
|
|
d7203b8b1a | ||
|
|
204ca29ed7 | ||
|
|
fdada53a4a | ||
|
|
81eb3754a0 | ||
|
|
d260f1db61 | ||
|
|
74f078f166 | ||
|
|
e16cee4187 | ||
|
|
a904e35c67 | ||
|
|
b87cd9f842 | ||
|
|
fed34e7671 | ||
|
|
ed57f72117 | ||
|
|
4d39d4ccc3 | ||
|
|
79cdd94833 | ||
|
|
e0645b41d3 | ||
|
|
aa7f0c8a0b | ||
|
|
2362770cce | ||
|
|
8334036cf4 | ||
|
|
eec513e9e3 | ||
|
|
f2a7a43ba7 | ||
|
|
61bbbaf6eb | ||
|
|
3519a6ec7b | ||
|
|
d21f994eee | ||
|
|
b32acf0dfb | ||
|
|
71a56e13d9 | ||
|
|
0f6d0a0439 | ||
|
|
3032ea7684 | ||
|
|
04cfbafc33 | ||
|
|
57fcee676a | ||
|
|
2f5d090ca5 | ||
|
|
8d796f328b | ||
|
|
ffb1d6ea17 | ||
|
|
4447e5dac6 | ||
|
|
dbed14db5e | ||
|
|
254103e21f | ||
|
|
d0b39e1c97 | ||
|
|
4bb555e4b2 | ||
|
|
8d0ee3ded9 | ||
|
|
98d1ca8505 | ||
|
|
e766bad4ce | ||
|
|
9eb2873cfa | ||
|
|
c7e2331284 | ||
|
|
02ca5e5732 | ||
|
|
bc2d72638b | ||
|
|
40de181959 | ||
|
|
70bc071cb8 | ||
|
|
567ac23c2c | ||
|
|
af50bd5b94 | ||
|
|
899f0ee83d | ||
|
|
29b3ea07e0 | ||
|
|
c0a316c7df | ||
|
|
f624833f1f | ||
|
|
4c661ffca6 | ||
|
|
0819f23049 | ||
|
|
1e9a463245 | ||
|
|
447d8f5677 | ||
|
|
d2453b1f1f | ||
|
|
9460138cc3 | ||
|
|
0063461858 | ||
|
|
248d7a3173 | ||
|
|
51277270fe | ||
|
|
394738394d | ||
|
|
6c4a5bd2bc | ||
|
|
6347730dc7 | ||
|
|
3da8e39745 | ||
|
|
f4acf97b00 | ||
|
|
e4e66a03d7 | ||
|
|
ed78c0053c | ||
|
|
398fd18b8e | ||
|
|
d3003d4fcd | ||
|
|
ee94eca733 | ||
|
|
0696fb2c5a | ||
|
|
e6fbb0934e | ||
|
|
faaf24d3c4 | ||
|
|
fcf785f32c | ||
|
|
0508628871 | ||
|
|
27502d3fa8 | ||
|
|
1057ff36cd | ||
|
|
8d5e50c0ca | ||
|
|
8db602c8bd | ||
|
|
06494cf821 | ||
|
|
0fe6a55700 | ||
|
|
e20274c2f7 | ||
|
|
b77106f61a | ||
|
|
4d25b139cc | ||
|
|
6ce48a5b7b | ||
|
|
69b0e2ad32 | ||
|
|
cd0c9393d8 | ||
|
|
2494444ca4 | ||
|
|
c76b78eb46 | ||
|
|
78fcc8b72c | ||
|
|
652412cd4f | ||
|
|
78801aa9e5 |
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.bundle.js -text -diff
|
||||
3
.gitignore
vendored
@@ -1 +1,4 @@
|
||||
node_modules
|
||||
*.swp
|
||||
.idea/
|
||||
*.iml
|
||||
24
Makefile
@@ -1,19 +1,21 @@
|
||||
NPM = npm
|
||||
BROWSERIFY = browserify
|
||||
GLOBAL_FLAGS = -e
|
||||
MODULE_DIR = modules
|
||||
MODULE_SUBDIRS = $(wildcard $(MODULE_DIR)/*/)
|
||||
MODULES = $(MODULE_SUBDIRS:$(MODULE_DIR)/%/=%)
|
||||
GLOBAL_FLAGS = -x jquery -e
|
||||
OUTPUT_DIR = .
|
||||
DEPLOY_DIR = libs/modules
|
||||
DEPLOY_DIR = libs
|
||||
|
||||
all:FLAGS = $(GLOBAL_FLAGS)
|
||||
all:$(MODULES)
|
||||
all: compile deploy clean
|
||||
|
||||
debug:FLAGS = -d $(GLOBAL_FLAGS)
|
||||
debug:$(MODULES)
|
||||
compile:FLAGS = $(GLOBAL_FLAGS)
|
||||
compile: app
|
||||
|
||||
$(MODULES): *.js
|
||||
$(BROWSERIFY) $(FLAGS) $(MODULE_DIR)/$@/$@.js -s $@ -o $(OUTPUT_DIR)/$@.bundle.js
|
||||
debug: compile-debug deploy clean
|
||||
|
||||
compile-debug:FLAGS = -d $(GLOBAL_FLAGS)
|
||||
compile-debug: app
|
||||
|
||||
app:
|
||||
$(NPM) update && $(BROWSERIFY) $(FLAGS) app.js -s APP -o $(OUTPUT_DIR)/app.bundle.js
|
||||
|
||||
clean:
|
||||
@rm -f $(OUTPUT_DIR)/*.bundle.js
|
||||
|
||||
17
README.md
@@ -10,12 +10,25 @@ Jitsi Meet allows for very efficient collaboration. It allows users to stream th
|
||||
|
||||
Installing Jitsi Meet is quite a simple experience. For Debian-based systems, we recommend following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document, which uses the package system.
|
||||
|
||||
For other systems, or if you wish to install all components manually, see the [detailed installation instructions](https://github.com/jitsi/jitsi-meet/blob/master/doc/manual-install.md).
|
||||
For other systems, or if you wish to install all components manually, see the [detailed manual installation instructions](https://github.com/jitsi/jitsi-meet/blob/master/doc/manual-install.md).
|
||||
|
||||
## Development tools
|
||||
## Building the sources
|
||||
|
||||
Jitsi Meet uses [Browserify](http://browserify.org). If you want to make changes in the code you need to [install Browserify](http://browserify.org/#install). Browserify requires [nodejs](http://nodejs.org).
|
||||
|
||||
On Debian/Ubuntu systems, the required packages can be installed with:
|
||||
```
|
||||
sudo apt-get install npm
|
||||
sudo npm install -g browserify
|
||||
cd jitsi-meet
|
||||
npm install
|
||||
```
|
||||
|
||||
To build the Jitsi Meet application, just type
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
## Discuss
|
||||
Please use the [Jitsi dev mailing list](http://lists.jitsi.org/pipermail/dev/) to discuss feature requests before opening an issue on github.
|
||||
|
||||
|
||||
202
api_connector.js
@@ -1,202 +0,0 @@
|
||||
/**
|
||||
* Implements API class that communicates with external api class
|
||||
* and provides interface to access Jitsi Meet features by external
|
||||
* applications that embed Jitsi Meet
|
||||
*/
|
||||
var APIConnector = (function () {
|
||||
|
||||
function APIConnector() { }
|
||||
|
||||
/**
|
||||
* List of the available commands.
|
||||
* @type {{
|
||||
* displayName: inputDisplayNameHandler,
|
||||
* muteAudio: toggleAudio,
|
||||
* muteVideo: toggleVideo,
|
||||
* filmStrip: toggleFilmStrip
|
||||
* }}
|
||||
*/
|
||||
var commands =
|
||||
{
|
||||
displayName: VideoLayout.inputDisplayNameHandler,
|
||||
muteAudio: toggleAudio,
|
||||
muteVideo: toggleVideo,
|
||||
toggleFilmStrip: BottomToolbar.toggleFilmStrip,
|
||||
toggleChat: BottomToolbar.toggleChat,
|
||||
toggleContactList: BottomToolbar.toggleContactList
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Maps the supported events and their status
|
||||
* (true it the event is enabled and false if it is disabled)
|
||||
* @type {{
|
||||
* incomingMessage: boolean,
|
||||
* outgoingMessage: boolean,
|
||||
* displayNameChange: boolean,
|
||||
* participantJoined: boolean,
|
||||
* participantLeft: boolean
|
||||
* }}
|
||||
*/
|
||||
var events =
|
||||
{
|
||||
incomingMessage: false,
|
||||
outgoingMessage:false,
|
||||
displayNameChange: false,
|
||||
participantJoined: false,
|
||||
participantLeft: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the API should be enabled or not.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
APIConnector.isEnabled = function () {
|
||||
var hash = location.hash;
|
||||
if(hash && hash.indexOf("external") > -1 && window.postMessage)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the APIConnector. Setups message event listeners that will
|
||||
* receive information from external applications that embed Jitsi Meet.
|
||||
* It also sends a message to the external application that APIConnector
|
||||
* is initialized.
|
||||
*/
|
||||
APIConnector.init = function () {
|
||||
if (window.addEventListener)
|
||||
{
|
||||
window.addEventListener('message',
|
||||
APIConnector.processMessage, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.attachEvent('onmessage', APIConnector.processMessage);
|
||||
}
|
||||
APIConnector.sendMessage({type: "system", loaded: true});
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends message to the external application.
|
||||
* @param object
|
||||
*/
|
||||
APIConnector.sendMessage = function (object) {
|
||||
window.parent.postMessage(JSON.stringify(object), "*");
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes a message event from the external application
|
||||
* @param event the message event
|
||||
*/
|
||||
APIConnector.processMessage = function(event)
|
||||
{
|
||||
var message;
|
||||
try {
|
||||
message = JSON.parse(event.data);
|
||||
} catch (e) {}
|
||||
|
||||
if(!message.type)
|
||||
return;
|
||||
switch (message.type)
|
||||
{
|
||||
case "command":
|
||||
APIConnector.processCommand(message);
|
||||
break;
|
||||
case "event":
|
||||
APIConnector.processEvent(message);
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown type of the message");
|
||||
return;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes commands from external applicaiton.
|
||||
* @param message the object with the command
|
||||
*/
|
||||
APIConnector.processCommand = function (message)
|
||||
{
|
||||
if(message.action != "execute")
|
||||
{
|
||||
console.error("Unknown action of the message");
|
||||
return;
|
||||
}
|
||||
for(var key in message)
|
||||
{
|
||||
if(commands[key])
|
||||
commands[key].apply(null, message[key]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes events objects from external applications
|
||||
* @param event the event
|
||||
*/
|
||||
APIConnector.processEvent = function (event) {
|
||||
if(!event.action)
|
||||
{
|
||||
console.error("Event with no action is received.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch(event.action)
|
||||
{
|
||||
case "add":
|
||||
for(var i = 0; i < event.events.length; i++)
|
||||
{
|
||||
events[event.events[i]] = true;
|
||||
}
|
||||
break;
|
||||
case "remove":
|
||||
for(var i = 0; i < event.events.length; i++)
|
||||
{
|
||||
events[event.events[i]] = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown action for event.");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the event is enabled ot not.
|
||||
* @param name the name of the event.
|
||||
* @returns {*}
|
||||
*/
|
||||
APIConnector.isEventEnabled = function (name) {
|
||||
return events[name];
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends event object to the external application that has been subscribed
|
||||
* for that event.
|
||||
* @param name the name event
|
||||
* @param object data associated with the event
|
||||
*/
|
||||
APIConnector.triggerEvent = function (name, object) {
|
||||
APIConnector.sendMessage({
|
||||
type: "event", action: "result", event: name, result: object});
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the listeners.
|
||||
*/
|
||||
APIConnector.dispose = function () {
|
||||
if(window.removeEventListener)
|
||||
{
|
||||
window.removeEventListener("message",
|
||||
APIConnector.processMessage, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.detachEvent('onmessage', APIConnector.processMessage);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return APIConnector;
|
||||
})();
|
||||
343
chat.js
@@ -1,343 +0,0 @@
|
||||
/* global $, Util, connection, nickname:true, getVideoSize, getVideoPosition, showToolbar, processReplacements */
|
||||
/**
|
||||
* Chat related user interface.
|
||||
*/
|
||||
var Chat = (function (my) {
|
||||
var notificationInterval = false;
|
||||
var unreadMessages = 0;
|
||||
|
||||
/**
|
||||
* Initializes chat related interface.
|
||||
*/
|
||||
my.init = function () {
|
||||
var storedDisplayName = window.localStorage.displayname;
|
||||
if (storedDisplayName) {
|
||||
nickname = storedDisplayName;
|
||||
|
||||
Chat.setChatConversationMode(true);
|
||||
}
|
||||
|
||||
$('#nickinput').keydown(function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
var val = Util.escapeHtml(this.value);
|
||||
this.value = '';
|
||||
if (!nickname) {
|
||||
nickname = val;
|
||||
window.localStorage.displayname = nickname;
|
||||
|
||||
connection.emuc.addDisplayNameToPresence(nickname);
|
||||
connection.emuc.sendPresence();
|
||||
|
||||
Chat.setChatConversationMode(true);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#usermsg').keydown(function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
var value = this.value;
|
||||
$('#usermsg').val('').trigger('autosize.resize');
|
||||
this.focus();
|
||||
var command = new CommandsProcessor(value);
|
||||
if(command.isCommand())
|
||||
{
|
||||
command.processCommand();
|
||||
}
|
||||
else
|
||||
{
|
||||
var message = Util.escapeHtml(value);
|
||||
connection.emuc.sendMessage(message, nickname);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var onTextAreaResize = function () {
|
||||
resizeChatConversation();
|
||||
Chat.scrollChatToBottom();
|
||||
};
|
||||
$('#usermsg').autosize({callback: onTextAreaResize});
|
||||
|
||||
$("#chatspace").bind("shown",
|
||||
function () {
|
||||
unreadMessages = 0;
|
||||
setVisualNotification(false);
|
||||
});
|
||||
|
||||
addSmileys();
|
||||
};
|
||||
|
||||
/**
|
||||
* Appends the given message to the chat conversation.
|
||||
*/
|
||||
my.updateChatConversation = function (from, displayName, message) {
|
||||
var divClassName = '';
|
||||
|
||||
if (connection.emuc.myroomjid === from) {
|
||||
divClassName = "localuser";
|
||||
}
|
||||
else {
|
||||
divClassName = "remoteuser";
|
||||
|
||||
if (!Chat.isVisible()) {
|
||||
unreadMessages++;
|
||||
Util.playSoundNotification('chatNotification');
|
||||
setVisualNotification(true);
|
||||
}
|
||||
}
|
||||
|
||||
//replace links and smileys
|
||||
var escMessage = message.replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br/>'); //Strophe already escapes special symbols on sending, so we escape here only tags to avoid double &
|
||||
var escDisplayName = Util.escapeHtml(displayName);
|
||||
message = processReplacements(escMessage);
|
||||
|
||||
var messageContainer =
|
||||
'<div class="chatmessage">'+
|
||||
'<img src="../images/chatArrow.svg" class="chatArrow">' +
|
||||
'<div class="username ' + divClassName +'">' + escDisplayName + '</div>' +
|
||||
'<div class="timestamp">' + getCurrentTime() + '</div>' +
|
||||
'<div class="usermessage">' + message + '</div>' +
|
||||
'</div>';
|
||||
|
||||
$('#chatconversation').append(messageContainer);
|
||||
$('#chatconversation').animate(
|
||||
{ scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
|
||||
};
|
||||
|
||||
/**
|
||||
* Appends error message to the conversation
|
||||
* @param errorMessage the received error message.
|
||||
* @param originalText the original message.
|
||||
*/
|
||||
my.chatAddError = function(errorMessage, originalText)
|
||||
{
|
||||
errorMessage = Util.escapeHtml(errorMessage);
|
||||
originalText = Util.escapeHtml(originalText);
|
||||
|
||||
$('#chatconversation').append('<div class="errorMessage"><b>Error: </b>'
|
||||
+ 'Your message' + (originalText? (' \"'+ originalText + '\"') : "")
|
||||
+ ' was not sent.' + (errorMessage? (' Reason: ' + errorMessage) : '')
|
||||
+ '</div>');
|
||||
$('#chatconversation').animate(
|
||||
{ scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the subject to the UI
|
||||
* @param subject the subject
|
||||
*/
|
||||
my.chatSetSubject = function(subject)
|
||||
{
|
||||
if(subject)
|
||||
subject = subject.trim();
|
||||
$('#subject').html(linkify(Util.escapeHtml(subject)));
|
||||
if(subject == "")
|
||||
{
|
||||
$("#subject").css({display: "none"});
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#subject").css({display: "block"});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets the chat conversation mode.
|
||||
*/
|
||||
my.setChatConversationMode = function (isConversationMode) {
|
||||
if (isConversationMode) {
|
||||
$('#nickname').css({visibility: 'hidden'});
|
||||
$('#chatconversation').css({visibility: 'visible'});
|
||||
$('#usermsg').css({visibility: 'visible'});
|
||||
$('#smileysarea').css({visibility: 'visible'});
|
||||
$('#usermsg').focus();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizes the chat area.
|
||||
*/
|
||||
my.resizeChat = function () {
|
||||
var chatSize = PanelToggler.getPanelSize();
|
||||
|
||||
$('#chatspace').width(chatSize[0]);
|
||||
$('#chatspace').height(chatSize[1]);
|
||||
|
||||
resizeChatConversation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates if the chat is currently visible.
|
||||
*/
|
||||
my.isVisible = function () {
|
||||
return $('#chatspace').is(":visible");
|
||||
};
|
||||
/**
|
||||
* Shows and hides the window with the smileys
|
||||
*/
|
||||
my.toggleSmileys = function() {
|
||||
var smileys = $('#smileysContainer');
|
||||
if(!smileys.is(':visible')) {
|
||||
smileys.show("slide", { direction: "down", duration: 300});
|
||||
} else {
|
||||
smileys.hide("slide", { direction: "down", duration: 300});
|
||||
}
|
||||
$('#usermsg').focus();
|
||||
};
|
||||
|
||||
/**
|
||||
* Scrolls chat to the bottom.
|
||||
*/
|
||||
my.scrollChatToBottom = function() {
|
||||
setTimeout(function () {
|
||||
$('#chatconversation').scrollTop(
|
||||
$('#chatconversation')[0].scrollHeight);
|
||||
}, 5);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the smileys container to the chat
|
||||
*/
|
||||
function addSmileys() {
|
||||
var smileysContainer = document.createElement('div');
|
||||
smileysContainer.id = 'smileysContainer';
|
||||
function addClickFunction(smiley, number) {
|
||||
smiley.onclick = function addSmileyToMessage() {
|
||||
var usermsg = $('#usermsg');
|
||||
var message = usermsg.val();
|
||||
message += smileys['smiley' + number];
|
||||
usermsg.val(message);
|
||||
usermsg.get(0).setSelectionRange(message.length, message.length);
|
||||
Chat.toggleSmileys();
|
||||
usermsg.focus();
|
||||
};
|
||||
}
|
||||
for(var i = 1; i <= 21; i++) {
|
||||
var smileyContainer = document.createElement('div');
|
||||
smileyContainer.id = 'smiley' + i;
|
||||
smileyContainer.className = 'smileyContainer';
|
||||
var smiley = document.createElement('img');
|
||||
smiley.src = 'images/smileys/smiley' + i + '.svg';
|
||||
smiley.className = 'smiley';
|
||||
addClickFunction(smiley, i);
|
||||
smileyContainer.appendChild(smiley);
|
||||
smileysContainer.appendChild(smileyContainer);
|
||||
}
|
||||
|
||||
$("#chatspace").append(smileysContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the chat conversation.
|
||||
*/
|
||||
function resizeChatConversation() {
|
||||
var msgareaHeight = $('#usermsg').outerHeight();
|
||||
var chatspace = $('#chatspace');
|
||||
var width = chatspace.width();
|
||||
var chat = $('#chatconversation');
|
||||
var smileys = $('#smileysarea');
|
||||
|
||||
smileys.height(msgareaHeight);
|
||||
$("#smileys").css('bottom', (msgareaHeight - 26) / 2);
|
||||
$('#smileysContainer').css('bottom', msgareaHeight);
|
||||
chat.width(width - 10);
|
||||
chat.height(window.innerHeight - 15 - msgareaHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides a visual notification, indicating that a message has arrived.
|
||||
*/
|
||||
function setVisualNotification(show) {
|
||||
var unreadMsgElement = document.getElementById('unreadMessages');
|
||||
var unreadMsgBottomElement = document.getElementById('bottomUnreadMessages');
|
||||
|
||||
var glower = $('#chatButton');
|
||||
var bottomGlower = $('#chatBottomButton');
|
||||
|
||||
if (unreadMessages) {
|
||||
unreadMsgElement.innerHTML = unreadMessages.toString();
|
||||
unreadMsgBottomElement.innerHTML = unreadMessages.toString();
|
||||
|
||||
ToolbarToggler.dockToolbar(true);
|
||||
|
||||
var chatButtonElement
|
||||
= document.getElementById('chatButton').parentNode;
|
||||
var leftIndent = (Util.getTextWidth(chatButtonElement) -
|
||||
Util.getTextWidth(unreadMsgElement)) / 2;
|
||||
var topIndent = (Util.getTextHeight(chatButtonElement) -
|
||||
Util.getTextHeight(unreadMsgElement)) / 2 - 3;
|
||||
|
||||
unreadMsgElement.setAttribute(
|
||||
'style',
|
||||
'top:' + topIndent +
|
||||
'; left:' + leftIndent + ';');
|
||||
|
||||
var chatBottomButtonElement
|
||||
= document.getElementById('chatBottomButton').parentNode;
|
||||
var bottomLeftIndent = (Util.getTextWidth(chatBottomButtonElement) -
|
||||
Util.getTextWidth(unreadMsgBottomElement)) / 2;
|
||||
var bottomTopIndent = (Util.getTextHeight(chatBottomButtonElement) -
|
||||
Util.getTextHeight(unreadMsgBottomElement)) / 2 - 2;
|
||||
|
||||
unreadMsgBottomElement.setAttribute(
|
||||
'style',
|
||||
'top:' + bottomTopIndent +
|
||||
'; left:' + bottomLeftIndent + ';');
|
||||
|
||||
|
||||
if (!glower.hasClass('icon-chat-simple')) {
|
||||
glower.removeClass('icon-chat');
|
||||
glower.addClass('icon-chat-simple');
|
||||
}
|
||||
}
|
||||
else {
|
||||
unreadMsgElement.innerHTML = '';
|
||||
unreadMsgBottomElement.innerHTML = '';
|
||||
glower.removeClass('icon-chat-simple');
|
||||
glower.addClass('icon-chat');
|
||||
}
|
||||
|
||||
if (show && !notificationInterval) {
|
||||
notificationInterval = window.setInterval(function () {
|
||||
glower.toggleClass('active');
|
||||
bottomGlower.toggleClass('active glowing');
|
||||
}, 800);
|
||||
}
|
||||
else if (!show && notificationInterval) {
|
||||
window.clearInterval(notificationInterval);
|
||||
notificationInterval = false;
|
||||
glower.removeClass('active');
|
||||
bottomGlower.removeClass('glowing');
|
||||
bottomGlower.addClass('active');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current time in the format it is shown to the user
|
||||
* @returns {string}
|
||||
*/
|
||||
function getCurrentTime() {
|
||||
var now = new Date();
|
||||
var hour = now.getHours();
|
||||
var minute = now.getMinutes();
|
||||
var second = now.getSeconds();
|
||||
if(hour.toString().length === 1) {
|
||||
hour = '0'+hour;
|
||||
}
|
||||
if(minute.toString().length === 1) {
|
||||
minute = '0'+minute;
|
||||
}
|
||||
if(second.toString().length === 1) {
|
||||
second = '0'+second;
|
||||
}
|
||||
return hour+':'+minute+':'+second;
|
||||
}
|
||||
|
||||
return my;
|
||||
}(Chat || {}));
|
||||
@@ -1,20 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Jitsi Meet: Unsupported Browser</title>
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="css/chromeonly.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- wrap starts here -->
|
||||
<div id="wrap">
|
||||
<a href="http://google.com/chrome"><div id="left"></div></a>
|
||||
<div id="middle"></div>
|
||||
<div id="text">
|
||||
<p>This application is currently only supported by <a href="http://google.com/chrome">Chrome</a>, <a href="http://www.chromium.org/">Chromium</a> and <a href="http://www.opera.com">Opera</a></p>
|
||||
<p><a href="http://google.com/chrome">Download Chrome</a></p>
|
||||
<p class="firefox">We are hoping that <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=977864">multistream support</a> for Firefox would not be long so that we could all use this application with our favorite browser.</p>
|
||||
</div>
|
||||
<!-- wrap ends here -->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
98
commands.js
@@ -1,98 +0,0 @@
|
||||
/**
|
||||
* Handles commands received via chat messages.
|
||||
*/
|
||||
var CommandsProcessor = (function()
|
||||
{
|
||||
/**
|
||||
* Constructs new CommandProccessor instance from a message.
|
||||
* @param message the message
|
||||
* @constructor
|
||||
*/
|
||||
function CommandsPrototype(message)
|
||||
{
|
||||
/**
|
||||
* Extracts the command from the message.
|
||||
* @param message the received message
|
||||
* @returns {string} the command
|
||||
*/
|
||||
function getCommand(message)
|
||||
{
|
||||
if(message)
|
||||
{
|
||||
for(var command in commands)
|
||||
{
|
||||
if(message.indexOf("/" + command) == 0)
|
||||
return command;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
var command = getCommand(message);
|
||||
|
||||
/**
|
||||
* Returns the name of the command.
|
||||
* @returns {String} the command
|
||||
*/
|
||||
this.getCommand = function()
|
||||
{
|
||||
return command;
|
||||
}
|
||||
|
||||
|
||||
var messageArgument = message.substr(command.length + 2);
|
||||
|
||||
/**
|
||||
* Returns the arguments of the command.
|
||||
* @returns {string}
|
||||
*/
|
||||
this.getArgument = function()
|
||||
{
|
||||
return messageArgument;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this instance is valid command or not.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
CommandsPrototype.prototype.isCommand = function()
|
||||
{
|
||||
if(this.getCommand())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the command.
|
||||
*/
|
||||
CommandsPrototype.prototype.processCommand = function()
|
||||
{
|
||||
if(!this.isCommand())
|
||||
return;
|
||||
|
||||
commands[this.getCommand()](this.getArgument());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the data for topic command.
|
||||
* @param commandArguments the arguments of the topic command.
|
||||
*/
|
||||
var processTopic = function(commandArguments)
|
||||
{
|
||||
var topic = Util.escapeHtml(commandArguments);
|
||||
connection.emuc.setSubject(topic);
|
||||
}
|
||||
|
||||
/**
|
||||
* List with supported commands. The keys are the names of the commands and
|
||||
* the value is the function that processes the message.
|
||||
* @type {{String: function}}
|
||||
*/
|
||||
var commands = {
|
||||
"topic" : processTopic
|
||||
};
|
||||
|
||||
return CommandsPrototype;
|
||||
})();
|
||||
@@ -20,8 +20,9 @@ var config = {
|
||||
chromeExtensionId: 'diibjkoicjeejcmhdnailmkgecihlobk', // Id of desktop streamer Chrome extension
|
||||
desktopSharingSources: ['screen', 'window'],
|
||||
minChromeExtVersion: '0.1', // Required version of Chrome extension
|
||||
enableRtpStats: true, // Enables RTP stats processing
|
||||
openSctp: true, // Toggle to enable/disable SCTP channels
|
||||
disableStats: false,
|
||||
disableAudioLevels: false,
|
||||
channelLastN: -1, // The default value of the channel attribute last-n.
|
||||
adaptiveLastN: false,
|
||||
adaptiveSimulcast: false,
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
var ConnectionQuality = (function () {
|
||||
|
||||
/**
|
||||
* Constructs new ConnectionQuality object
|
||||
* @constructor
|
||||
*/
|
||||
function ConnectionQuality() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* local stats
|
||||
* @type {{}}
|
||||
*/
|
||||
var stats = {};
|
||||
|
||||
/**
|
||||
* remote stats
|
||||
* @type {{}}
|
||||
*/
|
||||
var remoteStats = {};
|
||||
|
||||
/**
|
||||
* Interval for sending statistics to other participants
|
||||
* @type {null}
|
||||
*/
|
||||
var sendIntervalId = null;
|
||||
|
||||
/**
|
||||
* Updates the local statistics
|
||||
* @param data new statistics
|
||||
*/
|
||||
ConnectionQuality.updateLocalStats = function (data) {
|
||||
stats = data;
|
||||
VideoLayout.updateLocalConnectionStats(100 - stats.packetLoss.total,stats);
|
||||
if(sendIntervalId == null)
|
||||
{
|
||||
startSendingStats();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Start statistics sending.
|
||||
*/
|
||||
function startSendingStats() {
|
||||
sendStats();
|
||||
sendIntervalId = setInterval(sendStats, 10000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends statistics to other participants
|
||||
*/
|
||||
function sendStats() {
|
||||
connection.emuc.addConnectionInfoToPresence(convertToMUCStats(stats));
|
||||
connection.emuc.sendPresence();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts statistics to format for sending through XMPP
|
||||
* @param stats the statistics
|
||||
* @returns {{bitrate_donwload: *, bitrate_uplpoad: *, packetLoss_total: *, packetLoss_download: *, packetLoss_upload: *}}
|
||||
*/
|
||||
function convertToMUCStats(stats) {
|
||||
return {
|
||||
"bitrate_download": stats.bitrate.download,
|
||||
"bitrate_upload": stats.bitrate.upload,
|
||||
"packetLoss_total": stats.packetLoss.total,
|
||||
"packetLoss_download": stats.packetLoss.download,
|
||||
"packetLoss_upload": stats.packetLoss.upload
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts statitistics to format used by VideoLayout
|
||||
* @param stats
|
||||
* @returns {{bitrate: {download: *, upload: *}, packetLoss: {total: *, download: *, upload: *}}}
|
||||
*/
|
||||
function parseMUCStats(stats) {
|
||||
return {
|
||||
bitrate: {
|
||||
download: stats.bitrate_download,
|
||||
upload: stats.bitrate_upload
|
||||
},
|
||||
packetLoss: {
|
||||
total: stats.packetLoss_total,
|
||||
download: stats.packetLoss_download,
|
||||
upload: stats.packetLoss_upload
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates remote statistics
|
||||
* @param jid the jid associated with the statistics
|
||||
* @param data the statistics
|
||||
*/
|
||||
ConnectionQuality.updateRemoteStats = function (jid, data) {
|
||||
if(data == null || data.packetLoss_total == null)
|
||||
{
|
||||
VideoLayout.updateConnectionStats(jid, null, null);
|
||||
return;
|
||||
}
|
||||
remoteStats[jid] = parseMUCStats(data);
|
||||
|
||||
VideoLayout.updateConnectionStats(jid, 100 - data.packetLoss_total,remoteStats[jid]);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Stops statistics sending.
|
||||
*/
|
||||
ConnectionQuality.stopSendingStats = function () {
|
||||
clearInterval(sendIntervalId);
|
||||
sendIntervalId = null;
|
||||
//notify UI about stopping statistics gathering
|
||||
VideoLayout.onStatsStop();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the local statistics.
|
||||
*/
|
||||
ConnectionQuality.getStats = function () {
|
||||
return stats;
|
||||
}
|
||||
|
||||
return ConnectionQuality;
|
||||
})();
|
||||
186
contact_list.js
@@ -1,186 +0,0 @@
|
||||
/**
|
||||
* Contact list.
|
||||
*/
|
||||
var ContactList = (function (my) {
|
||||
|
||||
var numberOfContacts = 0;
|
||||
var notificationInterval;
|
||||
|
||||
/**
|
||||
* Indicates if the chat is currently visible.
|
||||
*
|
||||
* @return <tt>true</tt> if the chat is currently visible, <tt>false</tt> -
|
||||
* otherwise
|
||||
*/
|
||||
my.isVisible = function () {
|
||||
return $('#contactlist').is(":visible");
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a contact for the given peerJid if such doesn't yet exist.
|
||||
*
|
||||
* @param peerJid the peerJid corresponding to the contact
|
||||
* @param id the user's email or userId used to get the user's avatar
|
||||
*/
|
||||
my.ensureAddContact = function(peerJid, id) {
|
||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
|
||||
|
||||
if (!contact || contact.length <= 0)
|
||||
ContactList.addContact(peerJid,id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a contact for the given peer jid.
|
||||
*
|
||||
* @param peerJid the jid of the contact to add
|
||||
* @param id the email or userId of the user
|
||||
*/
|
||||
my.addContact = function(peerJid, id) {
|
||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
var contactlist = $('#contactlist>ul');
|
||||
|
||||
var newContact = document.createElement('li');
|
||||
// XXX(gp) contact click event handling is now in videolayout.js. Is the
|
||||
// following statement (newContact.id = resourceJid) still relevant?
|
||||
newContact.id = resourceJid;
|
||||
newContact.className = "clickable";
|
||||
newContact.onclick = function(event) {
|
||||
if(event.currentTarget.className === "clickable") {
|
||||
$(ContactList).trigger('contactclicked', [peerJid]);
|
||||
}
|
||||
};
|
||||
|
||||
newContact.appendChild(createAvatar(id));
|
||||
newContact.appendChild(createDisplayNameParagraph("Participant"));
|
||||
|
||||
var clElement = contactlist.get(0);
|
||||
|
||||
if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)
|
||||
&& $('#contactlist>ul .title')[0].nextSibling.nextSibling)
|
||||
{
|
||||
clElement.insertBefore(newContact,
|
||||
$('#contactlist>ul .title')[0].nextSibling.nextSibling);
|
||||
}
|
||||
else {
|
||||
clElement.appendChild(newContact);
|
||||
}
|
||||
updateNumberOfParticipants(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a contact for the given peer jid.
|
||||
*
|
||||
* @param peerJid the peerJid corresponding to the contact to remove
|
||||
*/
|
||||
my.removeContact = function(peerJid) {
|
||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
|
||||
|
||||
if (contact && contact.length > 0) {
|
||||
var contactlist = $('#contactlist>ul');
|
||||
|
||||
contactlist.get(0).removeChild(contact.get(0));
|
||||
|
||||
updateNumberOfParticipants(-1);
|
||||
}
|
||||
};
|
||||
|
||||
my.setVisualNotification = function(show, stopGlowingIn) {
|
||||
var glower = $('#contactListButton');
|
||||
function stopGlowing() {
|
||||
window.clearInterval(notificationInterval);
|
||||
notificationInterval = false;
|
||||
glower.removeClass('glowing');
|
||||
if(!ContactList.isVisible()) {
|
||||
glower.removeClass('active');
|
||||
}
|
||||
}
|
||||
|
||||
if (show && !notificationInterval) {
|
||||
notificationInterval = window.setInterval(function () {
|
||||
glower.toggleClass('active glowing');
|
||||
}, 800);
|
||||
}
|
||||
else if (!show && notificationInterval) {
|
||||
stopGlowing();
|
||||
}
|
||||
if(stopGlowingIn) {
|
||||
setTimeout(stopGlowing, stopGlowingIn);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the number of participants in the contact list button and sets
|
||||
* the glow
|
||||
* @param delta indicates whether a new user has joined (1) or someone has
|
||||
* left(-1)
|
||||
*/
|
||||
function updateNumberOfParticipants(delta) {
|
||||
//when the user is alone we don't show the number of participants
|
||||
if(numberOfContacts === 0) {
|
||||
$("#numberOfParticipants").text('');
|
||||
numberOfContacts += delta;
|
||||
} else if(numberOfContacts !== 0 && !ContactList.isVisible()) {
|
||||
ContactList.setVisualNotification(true);
|
||||
numberOfContacts += delta;
|
||||
$("#numberOfParticipants").text(numberOfContacts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the avatar element.
|
||||
*
|
||||
* @return the newly created avatar element
|
||||
*/
|
||||
function createAvatar(id) {
|
||||
var avatar = document.createElement('img');
|
||||
avatar.className = "icon-avatar avatar";
|
||||
avatar.src = "https://www.gravatar.com/avatar/" + id + "?d=wavatar&size=30";
|
||||
|
||||
return avatar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the display name paragraph.
|
||||
*
|
||||
* @param displayName the display name to set
|
||||
*/
|
||||
function createDisplayNameParagraph(displayName) {
|
||||
var p = document.createElement('p');
|
||||
p.innerText = displayName;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indicates that the display name has changed.
|
||||
*/
|
||||
$(document).bind( 'displaynamechanged',
|
||||
function (event, peerJid, displayName) {
|
||||
if (peerJid === 'localVideoContainer')
|
||||
peerJid = connection.emuc.myroomjid;
|
||||
|
||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
var contactName = $('#contactlist #' + resourceJid + '>p');
|
||||
|
||||
if (contactName && displayName && displayName.length > 0)
|
||||
contactName.html(displayName);
|
||||
});
|
||||
|
||||
my.setClickable = function(resourceJid, isClickable) {
|
||||
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
|
||||
if(isClickable) {
|
||||
contact.addClass('clickable');
|
||||
} else {
|
||||
contact.removeClass('clickable');
|
||||
}
|
||||
};
|
||||
|
||||
return my;
|
||||
}(ContactList || {}));
|
||||
@@ -1,54 +0,0 @@
|
||||
body {
|
||||
width:100%;
|
||||
height:100%;
|
||||
background-color: white;
|
||||
color: #424242;
|
||||
font-family:'YanoneKaffeesatzLight',Verdana,Tahoma,Arial;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
#wrap{
|
||||
display: block;
|
||||
position: absolute;
|
||||
width:900px;
|
||||
height: 262px;
|
||||
overflow:hidden;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
top: 0; left: 0; bottom: 0; right: 0;
|
||||
}
|
||||
#left{
|
||||
display:inline-block;
|
||||
background-image:url(../images/chromelogo.png);
|
||||
background-repeat:no-repeat;
|
||||
width:246px;
|
||||
height:262px;
|
||||
float: left;
|
||||
}
|
||||
.firefox{
|
||||
font-size: 11pt;
|
||||
color: #c8c8c8;
|
||||
}
|
||||
#middle{
|
||||
display:inline-block;
|
||||
background-image:url(../images/chromepointer.png);
|
||||
background-repeat:no-repeat;
|
||||
width:53px;
|
||||
height:262px;
|
||||
float: left;
|
||||
}
|
||||
#text{
|
||||
display:inline-block;
|
||||
font-size: 18pt;
|
||||
width: 560px;
|
||||
vertical-align:middle;
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #087dba;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -99,5 +99,7 @@
|
||||
width: 90px;
|
||||
height: 16px;
|
||||
padding-top: 4px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
margin: 15px auto 0px auto;
|
||||
}
|
||||
|
||||
61
css/login_menu.css
Normal file
@@ -0,0 +1,61 @@
|
||||
/*Initialize*/
|
||||
ul.loginmenu {
|
||||
display:none;
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
padding-bottom: 7px;
|
||||
top: 45px;
|
||||
left: -5px;
|
||||
background-color: rgba(0,0,0,0.9);
|
||||
border: 1px solid rgba(256, 256, 256, 0.2);
|
||||
border-radius:8px;
|
||||
}
|
||||
|
||||
ul.loginmenu li {
|
||||
list-style-type: none;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
ul.loginmenu li.identity {
|
||||
color: #fff;
|
||||
font-size: 11pt;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
ul.loginmenu:after {
|
||||
content: url('../images/dropdownPointer.png');
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 18px;
|
||||
}
|
||||
|
||||
li a.authButton{
|
||||
background-color: #06a5df;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
padding-left: 29px;
|
||||
padding-right: 29px;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
font-size: 11pt;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span.authentication:hover ul.loginmenu, ul.loginmenu:hover {
|
||||
display:block !important;
|
||||
}
|
||||
|
||||
a.disabled {
|
||||
color: gray !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loginmenuPadding {
|
||||
width: 50px;
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
top: -30px;
|
||||
left: 0px;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
color: #00ccff;
|
||||
}
|
||||
|
||||
#settingsmenu input {
|
||||
#settingsmenu input, select {
|
||||
margin-top: 10px;
|
||||
margin-left: 10%;
|
||||
width: 80%;
|
||||
@@ -27,8 +27,8 @@
|
||||
}
|
||||
|
||||
#settingsmenu button {
|
||||
width: 36%;
|
||||
left: 32%;
|
||||
width: 45%;
|
||||
left: 26%;
|
||||
padding: 0;
|
||||
margin-top: 10px;
|
||||
}
|
||||
@@ -43,3 +43,7 @@
|
||||
#settingsmenu .icon-settings {
|
||||
padding: 34px;
|
||||
}
|
||||
|
||||
#languages_selectbox{
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
111
css/unsupported_browser.css
Normal file
@@ -0,0 +1,111 @@
|
||||
body {
|
||||
width:100%;
|
||||
height:100%;
|
||||
background-color: white;
|
||||
color: #424242;
|
||||
font-family:Helvetica,'YanoneKaffeesatzLight',Verdana,Tahoma,Arial;
|
||||
font-size: 28px;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
#wrap{
|
||||
display: block;
|
||||
position: absolute;
|
||||
width:900px;
|
||||
height: 365px;
|
||||
overflow:hidden;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
top: 0; left: 0; bottom: 0; right: 0;
|
||||
}
|
||||
.firefox{
|
||||
font-size: 11pt;
|
||||
color: #c8c8c8;
|
||||
width: 468px;
|
||||
text-align: center;
|
||||
margin: 30px auto 0px auto;
|
||||
padding-left: 15px;
|
||||
}
|
||||
#text{
|
||||
display:inline-block;
|
||||
font-size: 28px;
|
||||
width: 568px;
|
||||
vertical-align:middle;
|
||||
padding-top: 25px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #087dba;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
.browser {
|
||||
width: 138px;
|
||||
height: 163px;
|
||||
margin-top: 5px;
|
||||
background-color: #e8e8e8;
|
||||
border: 1px solid #cfcfcf;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.browser_wrapper
|
||||
{
|
||||
width: 138px;
|
||||
height: 188px;
|
||||
vertical-align: middle;
|
||||
color: #929391;
|
||||
font-size: 20px;
|
||||
float: left;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.supported_browsers
|
||||
{
|
||||
margin: 0px auto 0px auto;
|
||||
width: 460px;
|
||||
}
|
||||
|
||||
.clear
|
||||
{
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.button
|
||||
{
|
||||
background-color: #62c82a;
|
||||
border: 1px solid #3c8117;
|
||||
border-radius: 10px;
|
||||
color: #FFFFFF;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
width: 115px;
|
||||
height: 26px;
|
||||
padding-top: 13px;
|
||||
margin: 15px auto 0px auto;
|
||||
}
|
||||
|
||||
.logo
|
||||
{
|
||||
margin: 20px auto 0px auto;
|
||||
}
|
||||
|
||||
#chrome_logo
|
||||
{
|
||||
width: 78px;
|
||||
height: 78px;
|
||||
background-image: url('/images/chrome.png');
|
||||
}
|
||||
#chromium_logo
|
||||
{
|
||||
width: 77px;
|
||||
height: 79px;
|
||||
background-image: url('/images/chromium.png');
|
||||
}
|
||||
#opera_logo
|
||||
{
|
||||
width: 73px;
|
||||
height: 78px;
|
||||
background-image: url('/images/opera.png');
|
||||
}
|
||||
|
||||
|
||||
2
debian/jitsi-meet-prosody.postinst
vendored
@@ -62,7 +62,7 @@ case "$1" in
|
||||
if [ -f $PROSODY_HOST_CONFIG ] && ! grep -q "VirtualHost \"auth.$JVB_HOSTNAME\"" $PROSODY_HOST_CONFIG; then
|
||||
echo -e "\nVirtualHost \"auth.$JVB_HOSTNAME\"" >> $PROSODY_HOST_CONFIG
|
||||
echo -e " authentication = \"internal_plain\"\n" >> $PROSODY_HOST_CONFIG
|
||||
echo -e "admins = { \"$JICOFO_AUTH_USER@auth.$JVB_HOSTNAME\" }\n" >> $PROSODY_HOST_CONFIG
|
||||
sed -i "s/Component \"conference.$JVB_HOSTNAME\" \"muc\"/Component \"conference.$JVB_HOSTNAME\" \"muc\"\nadmins = { \"$JICOFO_AUTH_USER@auth.$JVB_HOSTNAME\" }\n/g" $PROSODY_HOST_CONFIG
|
||||
echo -e "Component \"focus.$JVB_HOSTNAME\"" >> $PROSODY_HOST_CONFIG
|
||||
echo -e " component_secret=\"$JICOFO_SECRET\"\n" >> $PROSODY_HOST_CONFIG
|
||||
PROSODY_CREATE_JICOFO_USER="true"
|
||||
|
||||
4
debian/jitsi-meet.install
vendored
@@ -3,7 +3,9 @@
|
||||
*.html /usr/share/jitsi-meet/
|
||||
*.ico /usr/share/jitsi-meet/
|
||||
libs /usr/share/jitsi-meet/
|
||||
service /usr/share/jitsi-meet/
|
||||
css /usr/share/jitsi-meet/
|
||||
sounds /usr/share/jitsi-meet/
|
||||
fonts /usr/share/jitsi-meet/
|
||||
images /usr/share/jitsi-meet/
|
||||
images /usr/share/jitsi-meet/
|
||||
lang /usr/share/jitsi-meet/
|
||||
4
debian/jitsi-meet.postinst
vendored
@@ -37,10 +37,6 @@ case "$1" in
|
||||
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
fi
|
||||
|
||||
if grep "# server_names_hash_bucket_size 64" /etc/nginx/nginx.conf > /dev/null; then
|
||||
sed -i "s/#\ server_names_hash_bucket_size\ 64/\ server_names_hash_bucket_size\ 64/" /etc/nginx/nginx.conf
|
||||
fi
|
||||
|
||||
# SSL for nginx
|
||||
db_get jitsi-meet/cert-choice
|
||||
CERT_CHOICE="$RET"
|
||||
|
||||
15
debian/patches/jquery-package
vendored
@@ -2,21 +2,18 @@ Index: jitsi-meet/index.html
|
||||
===================================================================
|
||||
--- jitsi-meet.orig/index.html
|
||||
+++ jitsi-meet/index.html
|
||||
@@ -9,7 +9,7 @@
|
||||
@@ -9,12 +9,12 @@
|
||||
<meta itemprop="name" content="Jitsi Meet"/>
|
||||
<meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
|
||||
<meta itemprop="image" content="/images/jitsilogo.png"/>
|
||||
- <script src="libs/jquery-2.1.1.min.js"></script>
|
||||
+ <script src="libs/jquery.min.js"></script>
|
||||
<script src="config.js?v=5"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script src="simulcast.js?v=5"></script><!-- simulcast handling -->
|
||||
<script src="libs/strophe/strophe.jingle.adapter.js?v=2"></script><!-- strophe.jingle bundles -->
|
||||
@@ -24,7 +24,7 @@
|
||||
<script src="libs/strophe/strophe.util.js"></script>
|
||||
<script src="libs/colibri/colibri.focus.js?v=10"></script><!-- colibri focus implementation -->
|
||||
<script src="libs/colibri/colibri.session.js?v=1"></script>
|
||||
<script src="config.js?v=6"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script src="libs/strophe/strophe.min.js?v=1"></script>
|
||||
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
|
||||
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
|
||||
- <script src="libs/jquery-ui.js"></script>
|
||||
+ <script src="libs/jquery-ui.min.js"></script>
|
||||
<script src="libs/rayo.js?v=1"></script>
|
||||
<script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||
<script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
|
||||
|
||||
12
debian/rules
vendored
@@ -1,10 +1,4 @@
|
||||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
# Sample debian/rules that uses debhelper.
|
||||
# This file was originally written by Joey Hess and Craig Small.
|
||||
# As a special exception, when this file is copied by dh-make into a
|
||||
# dh-make output file, you may use that output file without restriction.
|
||||
# This special exception was added by Craig Small in version 0.37 of dh-make.
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
@@ -19,7 +13,7 @@ override_dh_install:
|
||||
dh_installdirs
|
||||
dh_install -X/config.js
|
||||
yui-compressor -o debian/jitsi-meet/usr/share/jitsi-meet/libs/strophe/strophe.caps.jsonly.min.js \
|
||||
debian/patches/missing-source/libs/strophe/strophe.caps.jsonly.js
|
||||
debian/missing-source/libs/strophe/strophe.caps.jsonly.js
|
||||
yui-compressor -o debian/jitsi-meet/usr/share/jitsi-meet/libs/strophe/strophe.disco.min.js \
|
||||
debian/patches/missing-source/libs/strophe/sha1.js \
|
||||
debian/patches/missing-source/libs/strophe/strophe.disco.js
|
||||
debian/missing-source/libs/strophe/sha1.js \
|
||||
debian/missing-source/libs/strophe/strophe.disco.js
|
||||
|
||||
2
debian/watch
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
version=3
|
||||
https://github.com/jitsi/jitsi-meet/releases/ /jitsi/jitsi-meet/archive/(\S+)\.tar\.gz
|
||||
@@ -16,6 +16,7 @@ VirtualHost "jitmeet.example.com"
|
||||
}
|
||||
|
||||
Component "conference.jitmeet.example.com" "muc"
|
||||
admins = { "focusUser@auth.jitmeet.example.com" }
|
||||
|
||||
Component "jitsi-videobridge.jitmeet.example.com"
|
||||
component_secret = "jitmeetSecret"
|
||||
@@ -23,7 +24,5 @@ Component "jitsi-videobridge.jitmeet.example.com"
|
||||
VirtualHost "auth.jitmeet.example.com"
|
||||
authentication = "internal_plain"
|
||||
|
||||
admins = { "focusUser@auth.jitmeet.example.com" }
|
||||
|
||||
Component "focus.jitmeet.example.com"
|
||||
component_secret = "focusSecret"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
server_names_hash_bucket_size 64;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name jitsi-meet.example.com;
|
||||
@@ -17,7 +19,7 @@ server {
|
||||
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
|
||||
}
|
||||
|
||||
location ~ ^/([a-zA-Z0-9]+)$ {
|
||||
location ~ ^/([a-zA-Z0-9=\?]+)$ {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ server {
|
||||
root /srv/jitsi.example.com;
|
||||
index index.html;
|
||||
|
||||
location ~ ^/([a-zA-Z0-9]+)$ {
|
||||
location ~ ^/([a-zA-Z0-9=\?]+)$ {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
|
||||
|
||||
26
doc/influxdb.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Overview
|
||||
Jitsi Meet supports logging to an [InfluxDB](http://influxdb.com/) database.
|
||||
|
||||
# Configuration
|
||||
The following needs to be done to enable this functionality.
|
||||
|
||||
## Install InfluxDB
|
||||
The details are outside the scope of the document, see http://influxdb.com/download/ .
|
||||
|
||||
## Enable logging for Jitsi Videobridge
|
||||
Add the following properties to <code>/usr/share/jitsi-videobridge/.sip-communicator/sip-communicator.properties</code>.
|
||||
|
||||
- org.jitsi.videobridge.log.INFLUX_DB_ENABLED=true
|
||||
- org.jitsi.videobridge.log.INFLUX_URL_BASE=http://influxdb.example.com:8086
|
||||
- org.jitsi.videobridge.log.INFLUX_DATABASE=jitsi_database
|
||||
- org.jitsi.videobridge.log.INFLUX_USER=user
|
||||
- org.jitsi.videobridge.log.INFLUX_PASS=pass
|
||||
|
||||
## Enable logging for Jicofo
|
||||
Add the same properties as above to <code>/usr/share/jitsi-videobridge/.sip-communicator/sip-communicator.properties</code>.
|
||||
|
||||
## Enable logging for Jitsi Meet itself
|
||||
Change "logStats" to "true" in <code>/etc/jitsi/meet/you-domain.config.js</code> or the <code>config.js</code> file used in your installation.
|
||||
|
||||
# User interface
|
||||
You can explore the database using the [Jiloin](https://github.com/jitsi/jiloin) web interface.
|
||||
17
estos_log.js
@@ -1,17 +0,0 @@
|
||||
/* global Strophe */
|
||||
Strophe.addConnectionPlugin('logger', {
|
||||
// logs raw stanzas and makes them available for download as JSON
|
||||
connection: null,
|
||||
log: [],
|
||||
init: function (conn) {
|
||||
this.connection = conn;
|
||||
this.connection.rawInput = this.log_incoming.bind(this);
|
||||
this.connection.rawOutput = this.log_outgoing.bind(this);
|
||||
},
|
||||
log_incoming: function (stanza) {
|
||||
this.log.push([new Date().getTime(), 'incoming', stanza]);
|
||||
},
|
||||
log_outgoing: function (stanza) {
|
||||
this.log.push([new Date().getTime(), 'outgoing', stanza]);
|
||||
},
|
||||
});
|
||||
206
etherpad.js
@@ -1,206 +0,0 @@
|
||||
/* global $, config, connection, dockToolbar, Moderator, Prezi,
|
||||
setLargeVideoVisible, ToolbarToggler, Util, VideoLayout */
|
||||
var Etherpad = (function (my) {
|
||||
var etherpadName = null;
|
||||
var etherpadIFrame = null;
|
||||
var domain = null;
|
||||
var options = "?showControls=true&showChat=false&showLineNumbers=true&useMonospaceFont=false";
|
||||
|
||||
/**
|
||||
* Initializes the etherpad.
|
||||
*/
|
||||
my.init = function (name) {
|
||||
|
||||
if (config.etherpad_base && !etherpadName) {
|
||||
|
||||
domain = config.etherpad_base;
|
||||
|
||||
if (!name) {
|
||||
// In case we're the focus we generate the name.
|
||||
etherpadName = Math.random().toString(36).substring(7) +
|
||||
'_' + (new Date().getTime()).toString();
|
||||
shareEtherpad();
|
||||
}
|
||||
else
|
||||
etherpadName = name;
|
||||
|
||||
enableEtherpadButton();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens/hides the Etherpad.
|
||||
*/
|
||||
my.toggleEtherpad = function (isPresentation) {
|
||||
if (!etherpadIFrame)
|
||||
createIFrame();
|
||||
|
||||
var largeVideo = null;
|
||||
if (Prezi.isPresentationVisible())
|
||||
largeVideo = $('#presentation>iframe');
|
||||
else
|
||||
largeVideo = $('#largeVideo');
|
||||
|
||||
if ($('#etherpad>iframe').css('visibility') === 'hidden') {
|
||||
$('#activeSpeaker').css('visibility', 'hidden');
|
||||
largeVideo.fadeOut(300, function () {
|
||||
if (Prezi.isPresentationVisible()) {
|
||||
largeVideo.css({opacity: '0'});
|
||||
} else {
|
||||
VideoLayout.setLargeVideoVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
$('#etherpad>iframe').fadeIn(300, function () {
|
||||
document.body.style.background = '#eeeeee';
|
||||
$('#etherpad>iframe').css({visibility: 'visible'});
|
||||
$('#etherpad').css({zIndex: 2});
|
||||
});
|
||||
}
|
||||
else if ($('#etherpad>iframe')) {
|
||||
$('#etherpad>iframe').fadeOut(300, function () {
|
||||
$('#etherpad>iframe').css({visibility: 'hidden'});
|
||||
$('#etherpad').css({zIndex: 0});
|
||||
document.body.style.background = 'black';
|
||||
});
|
||||
|
||||
if (!isPresentation) {
|
||||
$('#largeVideo').fadeIn(300, function () {
|
||||
VideoLayout.setLargeVideoVisible(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
resize();
|
||||
};
|
||||
|
||||
my.isVisible = function() {
|
||||
var etherpadIframe = $('#etherpad>iframe');
|
||||
return etherpadIframe && etherpadIframe.is(':visible');
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizes the etherpad.
|
||||
*/
|
||||
function resize() {
|
||||
if ($('#etherpad>iframe').length) {
|
||||
var remoteVideos = $('#remoteVideos');
|
||||
var availableHeight
|
||||
= window.innerHeight - remoteVideos.outerHeight();
|
||||
var availableWidth = Util.getAvailableVideoWidth();
|
||||
|
||||
$('#etherpad>iframe').width(availableWidth);
|
||||
$('#etherpad>iframe').height(availableHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares the Etherpad name with other participants.
|
||||
*/
|
||||
function shareEtherpad() {
|
||||
connection.emuc.addEtherpadToPresence(etherpadName);
|
||||
connection.emuc.sendPresence();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Etherpad button and adds it to the toolbar.
|
||||
*/
|
||||
function enableEtherpadButton() {
|
||||
if (!$('#etherpadButton').is(":visible"))
|
||||
$('#etherpadButton').css({display: 'inline-block'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the IFrame for the etherpad.
|
||||
*/
|
||||
function createIFrame() {
|
||||
etherpadIFrame = document.createElement('iframe');
|
||||
etherpadIFrame.src = domain + etherpadName + options;
|
||||
etherpadIFrame.frameBorder = 0;
|
||||
etherpadIFrame.scrolling = "no";
|
||||
etherpadIFrame.width = $('#largeVideoContainer').width() || 640;
|
||||
etherpadIFrame.height = $('#largeVideoContainer').height() || 480;
|
||||
etherpadIFrame.setAttribute('style', 'visibility: hidden;');
|
||||
|
||||
document.getElementById('etherpad').appendChild(etherpadIFrame);
|
||||
|
||||
etherpadIFrame.onload = function() {
|
||||
|
||||
document.domain = document.domain;
|
||||
bubbleIframeMouseMove(etherpadIFrame);
|
||||
setTimeout(function() {
|
||||
//the iframes inside of the etherpad are not yet loaded when the etherpad iframe is loaded
|
||||
var outer = etherpadIFrame.contentDocument.getElementsByName("ace_outer")[0];
|
||||
bubbleIframeMouseMove(outer);
|
||||
var inner = outer.contentDocument.getElementsByName("ace_inner")[0];
|
||||
bubbleIframeMouseMove(inner);
|
||||
}, 2000);
|
||||
};
|
||||
}
|
||||
|
||||
function bubbleIframeMouseMove(iframe){
|
||||
var existingOnMouseMove = iframe.contentWindow.onmousemove;
|
||||
iframe.contentWindow.onmousemove = function(e){
|
||||
if(existingOnMouseMove) existingOnMouseMove(e);
|
||||
var evt = document.createEvent("MouseEvents");
|
||||
var boundingClientRect = iframe.getBoundingClientRect();
|
||||
evt.initMouseEvent(
|
||||
"mousemove",
|
||||
true, // bubbles
|
||||
false, // not cancelable
|
||||
window,
|
||||
e.detail,
|
||||
e.screenX,
|
||||
e.screenY,
|
||||
e.clientX + boundingClientRect.left,
|
||||
e.clientY + boundingClientRect.top,
|
||||
e.ctrlKey,
|
||||
e.altKey,
|
||||
e.shiftKey,
|
||||
e.metaKey,
|
||||
e.button,
|
||||
null // no related element
|
||||
);
|
||||
iframe.dispatchEvent(evt);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* On Etherpad added to muc.
|
||||
*/
|
||||
$(document).bind('etherpadadded.muc', function (event, jid, etherpadName) {
|
||||
console.log("Etherpad added", etherpadName);
|
||||
if (config.etherpad_base && !Moderator.isModerator()) {
|
||||
Etherpad.init(etherpadName);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* On focus changed event.
|
||||
*/
|
||||
// FIXME: there is no such event as 'focusechanged.muc'
|
||||
$(document).bind('focusechanged.muc', function (event, focus) {
|
||||
console.log("Focus changed");
|
||||
if (config.etherpad_base)
|
||||
shareEtherpad();
|
||||
});
|
||||
|
||||
/**
|
||||
* On video selected event.
|
||||
*/
|
||||
$(document).bind('video.selected', function (event, isPresentation) {
|
||||
if (!config.etherpad_base)
|
||||
return;
|
||||
|
||||
if (etherpadIFrame && etherpadIFrame.style.visibility !== 'hidden')
|
||||
Etherpad.toggleEtherpad(isPresentation);
|
||||
});
|
||||
|
||||
/**
|
||||
* Resizes the etherpad, when the window is resized.
|
||||
*/
|
||||
$(window).resize(function () {
|
||||
resize();
|
||||
});
|
||||
|
||||
return my;
|
||||
}(Etherpad || {}));
|
||||
BIN
images/chrome.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
BIN
images/chromium.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
images/dropdownPointer.png
Normal file
|
After Width: | Height: | Size: 234 B |
BIN
images/firefox.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
images/opera.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
189
index.html
@@ -10,61 +10,17 @@
|
||||
<meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
|
||||
<meta itemprop="image" content="/images/jitsilogo.png"/>
|
||||
<script src="libs/jquery-2.1.1.min.js"></script>
|
||||
<script src="config.js?v=5"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script src="simulcast.js?v=8"></script><!-- simulcast handling -->
|
||||
<script src="libs/strophe/strophe.jingle.adapter.js?v=4"></script><!-- strophe.jingle bundles -->
|
||||
<script src="config.js?v=6"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script src="libs/strophe/strophe.min.js?v=1"></script>
|
||||
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
|
||||
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
|
||||
<script src="libs/strophe/strophe.jingle.js?v=2"></script>
|
||||
<script src="libs/strophe/strophe.jingle.sdp.js?v=4"></script>
|
||||
<script src="libs/strophe/strophe.jingle.session.js?v=4"></script>
|
||||
<script src="libs/strophe/strophe.util.js"></script>
|
||||
<script src="libs/jquery-ui.js"></script>
|
||||
<script src="libs/rayo.js?v=1"></script>
|
||||
<script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||
<script src="libs/pako.bundle.js?v=1"></script><!-- zlib deflate -->
|
||||
<script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
|
||||
<script src="interface_config.js?v=5"></script>
|
||||
<script src="muc.js?v=17"></script><!-- simple MUC library -->
|
||||
<script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
|
||||
<script src="desktopsharing.js?v=3"></script><!-- desktop sharing -->
|
||||
<script src="app.js?v=23"></script><!-- application logic -->
|
||||
<script src="commands.js?v=1"></script><!-- application logic -->
|
||||
<script src="chat.js?v=15"></script><!-- chat logic -->
|
||||
<script src="contact_list.js?v=8"></script><!-- contact list logic -->
|
||||
<script src="side_panel_toggler.js?v=1"></script>
|
||||
<script src="util.js?v=7"></script><!-- utility functions -->
|
||||
<script src="etherpad.js?v=10"></script><!-- etherpad plugin -->
|
||||
<script src="prezi.js?v=7"></script><!-- prezi plugin -->
|
||||
<script src="smileys.js?v=3"></script><!-- smiley images -->
|
||||
<script src="replacement.js?v=7"></script><!-- link and smiley replacement -->
|
||||
<script src="moderatemuc.js?v=4"></script><!-- moderator plugin -->
|
||||
<script src="libs/app.bundle.js?v=34"></script>
|
||||
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
|
||||
<script src="videolayout.js?v=32"></script><!-- video ui -->
|
||||
<script src="connectionquality.js?v=1"></script>
|
||||
<script src="toolbar.js?v=7"></script><!-- toolbar ui -->
|
||||
<script src="toolbar_toggler.js?v=2"></script>
|
||||
<script src="canvas_util.js?v=1"></script><!-- canvas drawing utils -->
|
||||
<script src="audio_levels.js?v=4"></script><!-- audio levels plugin -->
|
||||
<script src="media_stream.js?v=2"></script><!-- media stream -->
|
||||
<script src="bottom_toolbar.js?v=6"></script><!-- media stream -->
|
||||
<script src="moderator.js?v=2"></script><!-- media stream -->
|
||||
<script src="roomname_generator.js?v=1"></script><!-- generator for random room names -->
|
||||
<script src="keyboard_shortcut.js?v=3"></script>
|
||||
<script src="recording.js?v=1"></script>
|
||||
<script src="tracking.js?v=1"></script><!-- tracking -->
|
||||
<script src="jitsipopover.js?v=3"></script>
|
||||
<script src="message_handler.js?v=2"></script>
|
||||
<script src="api_connector.js?v=2"></script>
|
||||
<script src="settings_menu.js?v=1"></script>
|
||||
<script src="service/RTC/RTCBrowserType.js?v=1"></script>
|
||||
<script src="service/RTC/StreamEventTypes.js?v=1"></script>
|
||||
<script src="service/RTC/MediaStreamTypes.js?v=1"></script>
|
||||
<script src="libs/modules/statistics.bundle.js?v=2"></script>
|
||||
<script src="libs/modules/RTC.bundle.js?v=2"></script>
|
||||
<script src="avatar.js?v=4"></script><!-- avatars -->
|
||||
<link rel="stylesheet" href="css/font.css?v=6"/>
|
||||
<link rel="stylesheet" href="css/toastr.css?v=1">
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=30"/>
|
||||
@@ -73,6 +29,7 @@
|
||||
<link rel="stylesheet" href="css/jquery-impromptu.css?v=4">
|
||||
<link rel="stylesheet" href="css/modaldialog.css?v=3">
|
||||
<link rel="stylesheet" href="css/popup_menu.css?v=4">
|
||||
<link rel="stylesheet" href="css/login_menu.css?v=1">
|
||||
<link rel="stylesheet" href="css/popover.css?v=2">
|
||||
<link rel="stylesheet" href="css/jitsi_popover.css?v=2">
|
||||
<link rel="stylesheet" href="css/contact_list.css?v=4">
|
||||
@@ -85,7 +42,6 @@
|
||||
<link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/diibjkoicjeejcmhdnailmkgecihlobk">
|
||||
<script src="libs/jquery-impromptu.js"></script>
|
||||
<script src="libs/jquery.autosize.js"></script>
|
||||
<script src="libs/prezi_player.js?v=2"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="welcome_page">
|
||||
@@ -96,76 +52,66 @@
|
||||
<a target="_new">
|
||||
<div class="watermark rightwatermark"></div>
|
||||
</a>
|
||||
<a class="poweredby" href="http://jitsi.org" target="_new" >powered by jitsi.org</a>
|
||||
<a class="poweredby" href="http://jitsi.org" target="_new" ><span data-i18n="poweredby"></span> jitsi.org</a>
|
||||
|
||||
<div id="enter_room_container">
|
||||
<div id="enter_room_form" >
|
||||
<div id="domain_name"></div>
|
||||
<div id="enter_room">
|
||||
<input id="enter_room_field" type="text" autofocus placeholder="Enter room name" />
|
||||
<input id="enter_room_field" type="text" autofocus data-i18n="[placeholder]welcomepage.roomname" placeholder="Enter room name" />
|
||||
<div class="icon-reload" id="reload_roomname"></div>
|
||||
<input id="enter_room_button" type="button" value="GO" />
|
||||
<input id="enter_room_button" type="button" data-i18n="[value]welcomepage.go" value="GO" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="brand_header"></div>
|
||||
<input type='checkbox' name='checkbox' id="disable_welcome"/>
|
||||
<label for="disable_welcome" class="disable_welcome_position">Don't show this page next time I enter</label>
|
||||
<label for="disable_welcome" class="disable_welcome_position" data-i18n="welcomepage.disable"></label>
|
||||
<div id="header_text"></div>
|
||||
</div>
|
||||
<div id="welcome_page_main">
|
||||
<div id="features">
|
||||
<div class="feature_row">
|
||||
<div class="feature_holder">
|
||||
<div class="feature_icon">Simple to use</div>
|
||||
<div class="feature_description">
|
||||
No downloads required. <span name="appName"></span> works directly within your browser. Simply share your conference URL with others to get started.
|
||||
<div class="feature_icon" data-i18n="welcomepage.feature1.title" ></div>
|
||||
<div class="feature_description" data-i18n="welcomepage.feature1.content" data-i18n-options='{ "postProcess": "resolveAppName" }'>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature_holder">
|
||||
<div class="feature_icon">Low bandwidth</div>
|
||||
<div class="feature_description">
|
||||
Multi-party video conferences work with as little as 128Kbps. Screen-sharing and audio-only conferences are possible with far less.
|
||||
<div class="feature_icon" data-i18n="welcomepage.feature2.title" ></div>
|
||||
<div class="feature_description" data-i18n="welcomepage.feature2.content">
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature_holder">
|
||||
<div class="feature_icon">Open source</div>
|
||||
<div class="feature_description">
|
||||
<span name="appName"></span> is licensed under MIT. You are free to download, use, modify, and share them as per these licenses.
|
||||
<div class="feature_icon" data-i18n="welcomepage.feature3.title" ></div>
|
||||
<div class="feature_description" data-i18n="welcomepage.feature3.content" data-i18n-options='{ "postProcess": "resolveAppName" }'>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature_holder">
|
||||
<div class="feature_icon">Unlimited users</div>
|
||||
<div class="feature_description">
|
||||
There are no artificial restrictions on the number of users or conference participants. Server power and bandwidth are the only limiting factors.
|
||||
<div class="feature_icon" data-i18n="welcomepage.feature4.title" ></div>
|
||||
<div class="feature_description" data-i18n="welcomepage.feature4.content">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature_row">
|
||||
<div class="feature_holder">
|
||||
<div class="feature_icon">Screen sharing</div>
|
||||
<div class="feature_description">
|
||||
It's easy to share your screen with others. <span name="appName"></span> is ideal for on-line presentations, lectures, and tech support sessions.
|
||||
</div>
|
||||
<div class="feature_icon" data-i18n="welcomepage.feature5.title" ></div>
|
||||
<div class="feature_description" data-i18n="welcomepage.feature5.content" data-i18n-options='{ "postProcess": "resolveAppName" }'>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature_holder">
|
||||
<div class="feature_icon">Secure rooms</div>
|
||||
<div class="feature_description">
|
||||
Need some privacy? <span name="appName"></span> conference rooms can be secured with a password in order to exclude unwanted guests and prevent interruptions.
|
||||
</div>
|
||||
<div class="feature_icon" data-i18n="welcomepage.feature6.title" ></div>
|
||||
<div class="feature_description" data-i18n="welcomepage.feature6.content" data-i18n-options='{ "postProcess": "resolveAppName" }'>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature_holder">
|
||||
<div class="feature_icon">Shared notes</div>
|
||||
<div class="feature_description">
|
||||
<span name="appName"></span> features Etherpad, a real-time collaborative text editor that's great for meeting minutes, writing articles, and more.
|
||||
</div>
|
||||
<div class="feature_icon" data-i18n="welcomepage.feature7.title" ></div>
|
||||
<div class="feature_description" data-i18n="welcomepage.feature7.content" data-i18n-options='{ "postProcess": "resolveAppName" }'></div>
|
||||
</div>
|
||||
<div class="feature_holder">
|
||||
<div class="feature_icon">Usage statistics</div>
|
||||
<div class="feature_description">
|
||||
Learn about your users through easy integration with Piwik, Google Analytics, and other usage monitoring and statistics systems.
|
||||
</div>
|
||||
<div class="feature_icon" data-i18n="welcomepage.feature8.title" ></div>
|
||||
<div class="feature_description" data-i18n="welcomepage.feature8.content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -175,36 +121,46 @@
|
||||
<div style="position: relative;" id="header_container">
|
||||
<div id="header">
|
||||
<span id="toolbar">
|
||||
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="mutePopover" content="Mute / Unmute" onclick='toggleAudio();'>
|
||||
<span id="authentication" class="authentication" style="display: none">
|
||||
<a class="button" id="toolbar_button_authentication" >
|
||||
<i id="authButton" class="icon-avatar"></i>
|
||||
</a>
|
||||
<ul class="loginmenu">
|
||||
<span class="loginmenuPadding"></span>
|
||||
<li id="toolbar_auth_identity" class="identity"></li>
|
||||
<li id="toolbar_button_login">
|
||||
<a class="authButton" data-i18n="toolbar.login"></a>
|
||||
</li>
|
||||
<li id="toolbar_button_logout">
|
||||
<a class="authButton" data-i18n="toolbar.logout"></a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="header_button_separator"></div>
|
||||
</span>
|
||||
<a class="button" id="toolbar_button_mute" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="mutePopover" data-i18n="[content]toolbar.mute" content="Mute / Unmute">
|
||||
<i id="mute" class="icon-microphone"></i>
|
||||
</a>
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleVideoPopover" content="Start / stop camera" onclick='toggleVideo();'>
|
||||
<a class="button" id="toolbar_button_camera" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleVideoPopover" data-i18n="[content]toolbar.videomute" content="Start / stop camera">
|
||||
<i id="video" class="icon-camera"></i>
|
||||
</a>
|
||||
<span id="authentication" style="display: none">
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Authenticate" onclick='authenticateClicked();'>
|
||||
<i id="authButton" class="icon-avatar"></i>
|
||||
</a>
|
||||
</span>
|
||||
<span id="recording" style="display: none">
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Record" onclick='toggleRecording();'>
|
||||
<a class="button" id="toolbar_button_record" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.record" content="Record">
|
||||
<i id="recordButton" class="icon-recEnable"></i>
|
||||
</a>
|
||||
</span>
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Lock / unlock room" onclick="Toolbar.openLockDialog();">
|
||||
<a class="button" id="toolbar_button_security" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.lock" content="Lock / unlock room">
|
||||
<i id="lockIcon" class="icon-security"></i>
|
||||
</a>
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Invite others" onclick="Toolbar.openLinkDialog();">
|
||||
<a class="button" id="toolbar_button_link" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.invite" content="Invite others">
|
||||
<i class="icon-link"></i>
|
||||
</a>
|
||||
<div class="header_button_separator"></div>
|
||||
<span class="toolbar_span">
|
||||
<a class="button" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="bottom" content="Open / close chat" onclick='BottomToolbar.toggleChat();'>
|
||||
<a class="button" id="toolbar_button_chat" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="bottom" data-i18n="[content]toolbar.chat" content="Open / close chat">
|
||||
<i id="chatButton" class="icon-chat">
|
||||
<span id="unreadMessages"></span>
|
||||
</i>
|
||||
@@ -212,38 +168,38 @@
|
||||
</span>
|
||||
<span id="prezi_button">
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Share Prezi" onclick='Prezi.openPreziDialog();'>
|
||||
<a class="button" id="toolbar_button_prezi" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.prezi" content="Share Prezi">
|
||||
<i class="icon-prezi"></i>
|
||||
</a>
|
||||
</span>
|
||||
<span id="etherpadButton">
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared document" onclick='Etherpad.toggleEtherpad(0);'>
|
||||
<a class="button" id="toolbar_button_etherpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared document" data-i18n="[content]toolbar.etherpad">
|
||||
<i class="icon-share-doc"></i>
|
||||
</a>
|
||||
</span>
|
||||
<div class="header_button_separator"></div>
|
||||
<span id="desktopsharing" style="display: none">
|
||||
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Share screen" onclick="toggleScreenSharing();">
|
||||
<a class="button" id="toolbar_button_desktopsharing" data-container="body" data-toggle="popover" data-placement="bottom" content="Share screen" data-i18n="[content]toolbar.sharescreen">
|
||||
<i class="icon-share-desktop"></i>
|
||||
</a>
|
||||
</span>
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Enter / Exit Full Screen" onclick='buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");Toolbar.toggleFullScreen();'>
|
||||
<a class="button" id="toolbar_button_fullScreen" data-container="body" data-toggle="popover" data-placement="bottom" content="Enter / Exit Full Screen" data-i18n="[content]toolbar.fullscreen">
|
||||
<i id="fullScreen" class="icon-full-screen"></i>
|
||||
</a>
|
||||
<span id="sipCallButton" style="display: none">
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Call SIP number" onclick='callSipButtonClicked();'>
|
||||
<a class="button" id="toolbar_button_sip" data-container="body" data-toggle="popover" data-placement="bottom" content="Call SIP number" data-i18n="[content]toolbar.sip">
|
||||
<i class="icon-telephone"></i></a>
|
||||
</span>
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Settings" onclick='PanelToggler.toggleSettingsMenu();'>
|
||||
<a class="button" id="toolbar_button_settings" data-container="body" data-toggle="popover" data-placement="bottom" content="Settings" data-i18n="[content]toolbar.Settings">
|
||||
<i id="settingsButton" class="icon-settings"></i>
|
||||
</a>
|
||||
<div class="header_button_separator"></div>
|
||||
<span id="hangup">
|
||||
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Hang Up" onclick='hangup();'>
|
||||
<a class="button" id="toolbar_button_hangup" data-container="body" data-toggle="popover" data-placement="bottom" content="Hang Up" data-i18n="[content]toolbar.hangup">
|
||||
<i class="icon-hangup" style="color:#ff0000;font-size: 1.4em;"></i>
|
||||
</a>
|
||||
</span>
|
||||
@@ -252,7 +208,7 @@
|
||||
<div id="subject"></div>
|
||||
</div>
|
||||
<div id="settings">
|
||||
<h1>Connection Settings</h1>
|
||||
<h1 data-i18n="connectionsettings"></h1>
|
||||
<form id="loginInfo">
|
||||
<label>JID: <input id="jid" type="text" name="jid" placeholder="me@example.com"/></label>
|
||||
<label>Password: <input id="password" type="password" name="password" placeholder="secret"/></label>
|
||||
@@ -260,14 +216,14 @@
|
||||
<input id="connect" type="submit" value="Connect" />
|
||||
</form>
|
||||
</div>
|
||||
<div id="reloadPresentation"><a onclick='Prezi.reloadPresentation();'><i title="Reload Prezi" class="fa fa-repeat fa-lg"></i></a></div>
|
||||
<div id="videospace" onmousemove="ToolbarToggler.showToolbar();">
|
||||
<div id="reloadPresentation"><a id="reloadPresentationLink"><i title="Reload Prezi" class="fa fa-repeat fa-lg"></i></a></div>
|
||||
<div id="videospace">
|
||||
<div id="largeVideoContainer" class="videocontainer">
|
||||
<div id="presentation"></div>
|
||||
<div id="etherpad"></div>
|
||||
<a target="_new"><div class="watermark leftwatermark"></div></a>
|
||||
<a target="_new"><div class="watermark rightwatermark"></div></a>
|
||||
<a class="poweredby" href="http://jitsi.org" target="_new" >powered by jitsi.org</a>
|
||||
<a class="poweredby" href="http://jitsi.org" target="_new" ><span data-i18n="poweredby"></span> jitsi.org</a>
|
||||
<div id="activeSpeaker">
|
||||
<img id="activeSpeakerAvatar" src=""/>
|
||||
<canvas id="activeSpeakerAudioLevel"></canvas>
|
||||
@@ -282,18 +238,13 @@
|
||||
</span>
|
||||
<audio id="localAudio" autoplay oncontextmenu="return false;" muted></audio>
|
||||
<span class="focusindicator"></span>
|
||||
<!--<div class="connectionindicator">
|
||||
<span class="connection connection_empty"><i class="icon-connection"></i></span>
|
||||
<span class="connection connection_full"><i class="icon-connection"></i></span>
|
||||
</div>-->
|
||||
|
||||
</span>
|
||||
<audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
|
||||
<audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>
|
||||
</div>
|
||||
<span id="bottomToolbar">
|
||||
<span class="bottomToolbar_span">
|
||||
<a class="bottomToolbarButton" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="top" content="Open / close chat" onclick='BottomToolbar.toggleChat();'>
|
||||
<a class="bottomToolbarButton" id="bottom_toolbar_chat" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="top" data-i18n="[content]bottomtoolbar.chat" content="Open / close chat">
|
||||
<i id="chatBottomButton" class="icon-chat-simple">
|
||||
<span id="bottomUnreadMessages"></span>
|
||||
</i>
|
||||
@@ -301,7 +252,7 @@
|
||||
</span>
|
||||
<div class="bottom_button_separator"></div>
|
||||
<span class="bottomToolbar_span">
|
||||
<a class="bottomToolbarButton" data-container="body" data-toggle="popover" data-placement="top" id="contactlistpopover" content="Open / close contact list" onclick='BottomToolbar.toggleContactList();'>
|
||||
<a class="bottomToolbarButton" id="bottom_toolbar_contact_list" data-container="body" data-toggle="popover" data-placement="top" id="contactlistpopover" data-i18n="[content]bottomtoolbar.contactlist" content="Open / close contact list">
|
||||
<i id="contactListButton" class="icon-contactList">
|
||||
<span id="numberOfParticipants"></span>
|
||||
</i>
|
||||
@@ -309,7 +260,7 @@
|
||||
</span>
|
||||
<div class="bottom_button_separator"></div>
|
||||
<span class="bottomToolbar_span">
|
||||
<a class="bottomToolbarButton" data-container="body" data-toggle="popover" shortcut="filmstripPopover" data-placement="top" content="Show / hide film strip" onclick='BottomToolbar.toggleFilmStrip()'>
|
||||
<a class="bottomToolbarButton" id="bottom_toolbar_film_strip" data-container="body" data-toggle="popover" shortcut="filmstripPopover" data-placement="top" data-i18n="[content]bottomtoolbar.filmstrip" content="Show / hide film strip">
|
||||
<i id="filmStripButton" class="icon-filmstrip"></i>
|
||||
</a>
|
||||
</span>
|
||||
@@ -317,36 +268,36 @@
|
||||
</div>
|
||||
<div id="chatspace" class="right-panel">
|
||||
<div id="nickname">
|
||||
Enter a nickname in the box below
|
||||
<span data-i18n="chat.nickname.title"></span>
|
||||
<form>
|
||||
<input type='text' id="nickinput" placeholder='Choose a nickname' autofocus>
|
||||
<input type='text' id="nickinput" data-i18n="[placeholder]chat.nickname.popover" autofocus>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!--div><i class="fa fa-comments"> </i><span class='nick'></span>: <span class='chattext'></span></div-->
|
||||
<div id="chatconversation"></div>
|
||||
<audio id="chatNotification" src="sounds/incomingMessage.wav" preload="auto"></audio>
|
||||
<textarea id="usermsg" placeholder='Enter text...' autofocus></textarea>
|
||||
<textarea id="usermsg" data-i18n="[placeholder]chat.messagebox" autofocus></textarea>
|
||||
<div id="smileysarea">
|
||||
<div id="smileys" onclick="Chat.toggleSmileys()">
|
||||
<div id="smileys" id="toggle_smileys">
|
||||
<img src="images/smile.svg"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="contactlist" class="right-panel">
|
||||
<ul>
|
||||
<li class="title"><i class="icon-contact-list"></i> CONTACT LIST</li>
|
||||
<li class="title"><i class="icon-contact-list"></i><span data-i18n="contactlist"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="settingsmenu" class="right-panel">
|
||||
<div class="icon-settings"> SETTINGS</div>
|
||||
<div class="icon-settings" data-i18n="settings.title"></div>
|
||||
<img id="avatar" src="https://www.gravatar.com/avatar/87291c37c25be69a072a4514931b1749?d=wavatar&size=30"/>
|
||||
<div class="arrow-up"></div>
|
||||
<input type="text" id="setDisplayName" placeholder="Name">
|
||||
<input type="text" id="setDisplayName" data-i18n="[placeholder]settings.name" placeholder="Name">
|
||||
<input type="text" id="setEmail" placeholder="E-Mail">
|
||||
<button onclick="SettingsMenu.update()" id="updateSettings">Update</button>
|
||||
<button id="updateSettings" data-i18n="settings.update"></button>
|
||||
</div>
|
||||
<a id="downloadlog" onclick='dump(event.target);' data-container="body" data-toggle="popover" data-placement="right" data-content="Download logs" ><i class="fa fa-cloud-download"></i></a>
|
||||
<a id="downloadlog" onclick='dump(event.target);' data-container="body" data-toggle="popover" data-placement="right" data-i18n="[data-content]downloadlogs" ><i class="fa fa-cloud-download"></i></a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,7 +5,7 @@ var interfaceConfig = {
|
||||
INITIAL_TOOLBAR_TIMEOUT: 20000,
|
||||
TOOLBAR_TIMEOUT: 4000,
|
||||
DEFAULT_REMOTE_DISPLAY_NAME: "Fellow Jitster",
|
||||
DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME: "Speaker",
|
||||
DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME: "speaker",
|
||||
DEFAULT_LOCAL_DISPLAY_NAME: "me",
|
||||
SHOW_JITSI_WATERMARK: true,
|
||||
JITSI_WATERMARK_LINK: "http://jitsi.org",
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
var KeyboardShortcut = (function(my) {
|
||||
//maps keycode to character, id of popover for given function and function
|
||||
var shortcuts = {
|
||||
67: {
|
||||
character: "C",
|
||||
id: "toggleChatPopover",
|
||||
function: BottomToolbar.toggleChat
|
||||
},
|
||||
70: {
|
||||
character: "F",
|
||||
id: "filmstripPopover",
|
||||
function: BottomToolbar.toggleFilmStrip
|
||||
},
|
||||
77: {
|
||||
character: "M",
|
||||
id: "mutePopover",
|
||||
function: toggleAudio
|
||||
},
|
||||
84: {
|
||||
character: "T",
|
||||
function: function() {
|
||||
if(!isAudioMuted()) {
|
||||
toggleAudio();
|
||||
}
|
||||
}
|
||||
},
|
||||
86: {
|
||||
character: "V",
|
||||
id: "toggleVideoPopover",
|
||||
function: toggleVideo
|
||||
}
|
||||
};
|
||||
|
||||
window.onkeyup = function(e) {
|
||||
var keycode = e.which;
|
||||
if(!($(":focus").is("input[type=text]") || $(":focus").is("input[type=password]") || $(":focus").is("textarea"))) {
|
||||
if (typeof shortcuts[keycode] === "object") {
|
||||
shortcuts[keycode].function();
|
||||
} else if (keycode >= "0".charCodeAt(0) && keycode <= "9".charCodeAt(0)) {
|
||||
var remoteVideos = $(".videocontainer:not(#mixedstream)"),
|
||||
videoWanted = keycode - "0".charCodeAt(0) + 1;
|
||||
if (remoteVideos.length > videoWanted) {
|
||||
remoteVideos[videoWanted].click();
|
||||
}
|
||||
}
|
||||
//esc while the smileys are visible hides them
|
||||
} else if (keycode === 27 && $('#smileysContainer').is(':visible')) {
|
||||
Chat.toggleSmileys();
|
||||
}
|
||||
};
|
||||
|
||||
window.onkeydown = function(e) {
|
||||
if(!($(":focus").is("input[type=text]") || $(":focus").is("input[type=password]") || $(":focus").is("textarea"))) {
|
||||
if(e.which === "T".charCodeAt(0)) {
|
||||
if(isAudioMuted()) {
|
||||
toggleAudio();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id indicates the popover associated with the shortcut
|
||||
* @returns {string} the keyboard shortcut used for the id given
|
||||
*/
|
||||
my.getShortcut = function(id) {
|
||||
for(var keycode in shortcuts) {
|
||||
if(shortcuts.hasOwnProperty(keycode)) {
|
||||
if (shortcuts[keycode].id === id) {
|
||||
return " (" + shortcuts[keycode].character + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
};
|
||||
return my;
|
||||
}(KeyboardShortcut || {}))
|
||||
|
||||
6
lang/languages-bg.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"en": "Английски",
|
||||
"bg": "Български",
|
||||
"de": "Немски",
|
||||
"tr": "Турски"
|
||||
}
|
||||
6
lang/languages-de.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"en": "Englisch",
|
||||
"bg": "Bulgarisch",
|
||||
"de": "Deutsch",
|
||||
"tr": "Türkisch"
|
||||
}
|
||||
5
lang/languages-tr.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"en": "İngilizce",
|
||||
"bg": "Bulgarca",
|
||||
"de": "Almanca"
|
||||
}
|
||||
6
lang/languages.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"en": "English",
|
||||
"bg": "Bulgarian",
|
||||
"de": "German",
|
||||
"tr": "Turkish"
|
||||
}
|
||||
204
lang/main-bg.json
Normal file
@@ -0,0 +1,204 @@
|
||||
{
|
||||
"contactlist": "СПИСЪК С КОНТАКТИ",
|
||||
"connectionsettings": "Настройки на връзката",
|
||||
"poweredby": "",
|
||||
"downloadlogs": "",
|
||||
"roomUrlDefaultMsg": "",
|
||||
"participant": "Участник",
|
||||
"me": "аз",
|
||||
"speaker": "",
|
||||
"defaultNickname": "",
|
||||
"defaultPreziLink": "",
|
||||
"welcomepage": {
|
||||
"go": "",
|
||||
"roomname": "Въведете име на стаята",
|
||||
"disable": "",
|
||||
"feature1": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
},
|
||||
"feature2": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
},
|
||||
"feature3": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
},
|
||||
"feature4": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
},
|
||||
"feature5": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
},
|
||||
"feature6": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
},
|
||||
"feature7": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
},
|
||||
"feature8": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
"mute": "",
|
||||
"videomute": "",
|
||||
"authenticate": "",
|
||||
"record": "",
|
||||
"lock": "",
|
||||
"invite": "",
|
||||
"chat": "",
|
||||
"prezi": "",
|
||||
"etherpad": "",
|
||||
"sharescreen": "",
|
||||
"fullscreen": "",
|
||||
"sip": "",
|
||||
"Settings": "",
|
||||
"hangup": "",
|
||||
"login": "",
|
||||
"logout": ""
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "",
|
||||
"filmstrip": "",
|
||||
"contactlist": ""
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "",
|
||||
"popover": ""
|
||||
},
|
||||
"messagebox": ""
|
||||
},
|
||||
"settings": {
|
||||
"title": "НАСТРОЙКИ",
|
||||
"update": "",
|
||||
"name": ""
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "",
|
||||
"moderator": "",
|
||||
"videomute": "",
|
||||
"mute": "",
|
||||
"kick": "",
|
||||
"muted": "",
|
||||
"domute": ""
|
||||
},
|
||||
"connectionindicator": {
|
||||
"bitrate": "",
|
||||
"packetloss": "",
|
||||
"resolution": "",
|
||||
"less": "",
|
||||
"more": "",
|
||||
"address": "",
|
||||
"remoteport": "",
|
||||
"remoteport_plural": "",
|
||||
"localport": "",
|
||||
"localport_plural": "",
|
||||
"localaddress": "",
|
||||
"localaddress_plural": "",
|
||||
"remoteaddress": "",
|
||||
"remoteaddress_plural": "",
|
||||
"transport": "",
|
||||
"bandwidth": "",
|
||||
"na": ""
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "",
|
||||
"moderator": "",
|
||||
"connected": "",
|
||||
"somebody": "",
|
||||
"me": "",
|
||||
"focus": "",
|
||||
"focusFail": "",
|
||||
"grantedTo": "",
|
||||
"grantedToUnknown": ""
|
||||
},
|
||||
"dialog": {
|
||||
"kickMessage": "",
|
||||
"popupError": "",
|
||||
"passwordError": "",
|
||||
"passwordError2": "",
|
||||
"joinError": "",
|
||||
"connectError": "",
|
||||
"error": "",
|
||||
"detectext": "",
|
||||
"failtoinstall": "",
|
||||
"failedpermissions": "",
|
||||
"bridgeUnavailable": "",
|
||||
"lockTitle": "",
|
||||
"lockMessage": "",
|
||||
"warning": "",
|
||||
"passwordNotSupported": "",
|
||||
"sorry": "",
|
||||
"internalError": "",
|
||||
"unableToSwitch": "",
|
||||
"SLDFailure": "",
|
||||
"SRDFailure": "",
|
||||
"oops": "",
|
||||
"defaultError": "",
|
||||
"passwordRequired": "",
|
||||
"Ok": "",
|
||||
"removePreziTitle": "",
|
||||
"removePreziMsg": "",
|
||||
"sharePreziTitle": "",
|
||||
"sharePreziMsg": "",
|
||||
"Remove": "",
|
||||
"Stop": "",
|
||||
"AuthMsg": "",
|
||||
"Authenticate": "",
|
||||
"Cancel": "",
|
||||
"logoutTitle": "",
|
||||
"logoutQuestion": "",
|
||||
"sessTerminated": "",
|
||||
"hungUp": "",
|
||||
"joinAgain": "",
|
||||
"Share": "",
|
||||
"preziLinkError": "",
|
||||
"Save": "",
|
||||
"recordingToken": "",
|
||||
"Dial": "",
|
||||
"sipMsg": "",
|
||||
"passwordCheck": "",
|
||||
"passwordMsg": "",
|
||||
"Invite": "",
|
||||
"shareLink": "",
|
||||
"settings1": "",
|
||||
"settings2": "",
|
||||
"settings3": "",
|
||||
"yourPassword": "",
|
||||
"Back": "",
|
||||
"serviceUnavailable": "",
|
||||
"gracefulShutdown": "",
|
||||
"Yes": "",
|
||||
"reservationError": "",
|
||||
"reservationErrorMsg": "",
|
||||
"password": "",
|
||||
"userPassword": "",
|
||||
"token": ""
|
||||
},
|
||||
"email": {
|
||||
"sharedKeyThis conference is password protected. Please use the following pin when joining:\n\n\n__sharedKey__\n\n": "",
|
||||
"subject": "",
|
||||
"bodyHey there, I%27d like to invite you to a __appName__ conference I%27ve just set up.\n\n\nPlease click on the following link in order to join the conference.\n\n\n__roomUrl__\n\n\n__sharedKeyText__\n Note that __appName__ is currently only supported by __supportedBrowsers__, so you need to be using one of these browsers.\n\n\nTalk to you in a sec!": [
|
||||
"Здравей, Бих искал да те поканя в една __appName__ конференция, която създадох.",
|
||||
"",
|
||||
"",
|
||||
"Кликни на следния линк за да се присъединиш в конференцията.",
|
||||
"",
|
||||
"",
|
||||
"__sharedKeyText__",
|
||||
"__appName__ поддържа __supportedBrowsers__, така че трябва да използваш един от тези браузъри.",
|
||||
"",
|
||||
"",
|
||||
"Ще се видим след секунда!"
|
||||
],
|
||||
"and": ""
|
||||
}
|
||||
}
|
||||
207
lang/main-de.json
Normal file
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"contactlist": "Kontaktliste",
|
||||
"connectionsettings": "Verbindungseinstellungen",
|
||||
"poweredby": "Betrieben von",
|
||||
"downloadlogs": "Log herunterladen",
|
||||
"roomUrlDefaultMsg": "Die Konferenz wird erstellt...",
|
||||
"participant": "Teilnehmer",
|
||||
"me": "ich",
|
||||
"speaker": "Sprecher",
|
||||
"defaultNickname": "Bsp.: __name__",
|
||||
"defaultPreziLink": "Bsp.: __url__",
|
||||
"welcomepage": {
|
||||
"go": "Los",
|
||||
"roomname": "Raumnamen eingeben",
|
||||
"disable": "Diese Seite beim nächsten Betreten nicht mehr anzeigen",
|
||||
"feature1": {
|
||||
"title": "Einfach zu benutzen",
|
||||
"content": "Kein Download nötig. __app__ läuft direkt im Browser. Einfach die Konferenzadresse teilen und los geht's."
|
||||
},
|
||||
"feature2": {
|
||||
"title": "Niedrige Bandbreite",
|
||||
"content": "Videokonferenzen mit mehreren Teilnehmen mit weniger als 128Kpbs. Bildschirmfreigaben und Telefonkonferenzen kommen sogar mit noch weniger Bandbreite aus."
|
||||
},
|
||||
"feature3": {
|
||||
"title": "Open Source",
|
||||
"content": "__app__ steht unter der MIT Lizenz. __app__ kann gemäss der Lizenz heruntergeladen, verwendet, verändert und weitergegeben werden."
|
||||
},
|
||||
"feature4": {
|
||||
"title": "Unbegrenzte Anzahl Benutzer",
|
||||
"content": "Es gibt keine künstliche Beschränkung der Anzahl der Benutzer oder Konferenzteilnehmer. Die Leistung des Servers und die Bandbreite sind die einzigen limitierenden Faktoren."
|
||||
},
|
||||
"feature5": {
|
||||
"title": "Bildschirmfreigabe",
|
||||
"content": "Es ist ganz einfach den Bildschirm zu teilen. __app__ ist ideal für Online-Präsentationen, Vorlesungen und Fernwartungsanfragen."
|
||||
},
|
||||
"feature6": {
|
||||
"title": "Sichere Konferenzen",
|
||||
"content": "Privatsphäre gewünscht? __app__ Konferenzen können mit einem Passwort geschützt werden um ungebetene Gäste fernzuhalten und Unterbrechungen zu vermeiden."
|
||||
},
|
||||
"feature7": {
|
||||
"title": "Freigegebene Notizen",
|
||||
"content": "__app__ verwendent Etherpad, ein Editor zur kollaborativen Bearbeitung von Texten."
|
||||
},
|
||||
"feature8": {
|
||||
"title": "Benutzungsstatistiken",
|
||||
"content": "Die Verwendung kann durch die Integration mit Piwik, Google Analytics und anderen Überwachungs- und Statistikprogrammen protokolliert werden."
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
"mute": "Stummschaltung aktivieren / deaktivieren",
|
||||
"videomute": "Kamera starten / stoppen",
|
||||
"authenticate": "Anmelden",
|
||||
"record": "Aufnehmen",
|
||||
"lock": "Raum schützen / Schutz aufheben",
|
||||
"invite": "Andere einladen",
|
||||
"chat": "Chat öffnen / schliessen",
|
||||
"prezi": "Prezi freigeben",
|
||||
"etherpad": "Geteiltes Dokument",
|
||||
"sharescreen": "Bildschirm freigeben",
|
||||
"fullscreen": "Vollbildmodus aktivieren / deaktivieren",
|
||||
"sip": "SIP Nummer anrufen",
|
||||
"Settings": "Einstellungen",
|
||||
"hangup": "Auflegen",
|
||||
"login": "Anmelden",
|
||||
"logout": "Abmelden"
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Chat öffnen / schliessen",
|
||||
"filmstrip": "Videovorschauen anzeigen / verstecken",
|
||||
"contactlist": "Kontaktliste öffnen / schliessen"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "Nickname im Eingabefeld eingeben",
|
||||
"popover": "Einen Namen auswählen"
|
||||
},
|
||||
"messagebox": "Text eingeben..."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Einstellungen",
|
||||
"update": "Aktualisieren",
|
||||
"name": "Name"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "Klicken um den Anzeigenamen zu bearbeiten",
|
||||
"moderator": "Besitzer dieser Konferenz",
|
||||
"videomute": "Teilnehmer hat die Kamera pausiert.",
|
||||
"mute": "Teilnehmer ist stumm geschaltet",
|
||||
"kick": "Hinauswerfen",
|
||||
"muted": "Stummgeschaltet",
|
||||
"domute": "Stummschalten"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"bitrate": "Bitrate:",
|
||||
"packetloss": "Paketverlust:",
|
||||
"resolution": "Auflösung:",
|
||||
"less": "Weniger anzeigen",
|
||||
"more": "Mehr anzeigen",
|
||||
"address": "Adresse:",
|
||||
"remoteport": "Entfernter Port:",
|
||||
"remoteport_plural": "Entfernte Ports:",
|
||||
"localport": "Lokaler Port:",
|
||||
"localport_plural": "Lokale Ports:",
|
||||
"localaddress": "Lokale Adresse:",
|
||||
"localaddress_plural": "Lokale Adressen:",
|
||||
"remoteaddress": "Entfernte Adresse:",
|
||||
"remoteaddress_plural": "Entfernte Adressen:",
|
||||
"transport": "Protokoll:",
|
||||
"bandwidth": "Geschätzte Bandbreite:",
|
||||
"na": "Verbindungsdaten erneut anzeigen wenn die Konferenz begonnen hat"
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "getrennt",
|
||||
"moderator": "Moderatorenrechte vergeben",
|
||||
"connected": "verbunden",
|
||||
"somebody": "Jemand",
|
||||
"me": "Ich",
|
||||
"focus": "Konferenz-Organisator",
|
||||
"focusFail": "__component__ ist im Moment nicht verfügbar - wiederholen in __ms__ Sekunden",
|
||||
"grantedTo": "Moderatorenrechte an __to__ vergeben.",
|
||||
"grantedToUnknown": "Moderatorenrechte an $t(somebody) vergeben."
|
||||
},
|
||||
"dialog": {
|
||||
"kickMessage": "Oh! Sie wurden aus der Konferenz ausgeschlossen.",
|
||||
"popupError": "Ihr Browser blockiert Popups von dieser Webseite. Bitte erlauben Sie Popups in den Sicherheitseinstellungen und versuchen Sie es erneut.",
|
||||
"passwordError": "Diese Konferenz ist mit einem Paswort geschützt. Nur der Besitzer der Konferenz kann ein Passwort vergeben.",
|
||||
"passwordError2": "Diese Konferenzt ist nicht mit einem Passwort geschützt. Nur der Besitzer der Konferenz kann ein Passwort vergeben.",
|
||||
"joinError": "Oh! Der Konferenz konnte nicht beigetreten werden. Diese könnte ein Problem mit den Sicherheitseinstellungen sein. Bitte kontaktieren Sie den Administrator des Dienstes.",
|
||||
"connectError": "Oh! Es hat etwas nicht geklappt und der Konferenz konnte nicht beigetreten werden.",
|
||||
"error": "Fehler",
|
||||
"detectext": "Fehler bei der Erkennung der Bildschirmfreigabeerweiterung.",
|
||||
"failtoinstall": "Die Bildschirmfreigabeerweiterung konnte nicht installiert werden.",
|
||||
"failedpermissions": "Die Zugriffsberechtigungen auf das Mikrofon und/oder die Kamera konnte nicht eingeholt werden.",
|
||||
"bridgeUnavailable": "Die Jitsi Videobridge ist momentan nicht erreichbar. Bitte versuchen Sie es später noch einmal.",
|
||||
"lockTitle": "Sperren fehlgeschlagen",
|
||||
"lockMessage": "Die Konferenz konnte nicht gesperrt werden.",
|
||||
"warning": "Warnung",
|
||||
"passwordNotSupported": "Passwörter für Räume werden nicht unterstützt.",
|
||||
"sorry": "Entschuldigung",
|
||||
"internalError": "Interner Anwendungsfehler [setRemoteDescription]",
|
||||
"unableToSwitch": "Der Videodatenstrom kann nicht gewechselt werden.",
|
||||
"SLDFailure": "Oh! Die Stummschaltung konnte nicht aktiviert werden. (SLD Fehler)",
|
||||
"SRDFailure": "Oh! Das Video konnte nicht gestoppt werden. (SRD Fehler)",
|
||||
"oops": "Oh!",
|
||||
"defaultError": "Es ist ein Fehler aufgetreten",
|
||||
"passwordRequired": "Passwort erforderlich",
|
||||
"Ok": "OK",
|
||||
"removePreziTitle": "Prezi entfernen",
|
||||
"removePreziMsg": "Sind Sie sich sicher dass sie Prezi entfernen möchten?",
|
||||
"sharePreziTitle": "Ein Prezi teilen",
|
||||
"sharePreziMsg": "Ein anderer Teilnehmer teilt bereits ein Prezi. Diese Konferenz kann nur eine Prezi auf einmal anzeigen.",
|
||||
"Remove": "Entfernen",
|
||||
"Stop": "Stopp",
|
||||
"AuthMsg": "Für die Erstellung des Raums ist eine Authentifizierung erforderlich. <br/><b>__room__</b><br/>Sie können sich entweder anmelden oder warten bis jemand anderes die Authentifizierung vornimmt.",
|
||||
"Authenticate": "Anmelden",
|
||||
"Cancel": "Abbrechen",
|
||||
"logoutTitle": "Abmelden",
|
||||
"logoutQuestion": "Sind Sie sicher dass Sie sich abmelden und die Konferenz verlassen möchten?",
|
||||
"sessTerminated": "Sitzung beendet",
|
||||
"hungUp": "Anruf beendet",
|
||||
"joinAgain": "Erneut beitreten",
|
||||
"Share": "Teilen",
|
||||
"preziLinkError": "Bitte einen gültigen Prezi-Link angeben.",
|
||||
"Save": "Speichern",
|
||||
"recordingToken": "Aufnahme-Token eingeben",
|
||||
"Dial": "Wählen",
|
||||
"sipMsg": "Geben Sie eine SIP Nummer ein",
|
||||
"passwordCheck": "Sind Sie sicher dass Sie das Passwort entfernen möchten?",
|
||||
"passwordMsg": "Passwort setzen um den Raum zu schützen",
|
||||
"Invite": "Einladen",
|
||||
"shareLink": "Teilen Sie diesen Link mit jedem den Sie einladen möchten",
|
||||
"settings1": "Konferenz einrichten",
|
||||
"settings2": "Teilnehmer treten stummgeschaltet bei",
|
||||
"settings3": "Nickname erforderlich<br/><br/>Setzen Sie ein Passwort um den Raum zu schützen:",
|
||||
"yourPassword": "Ihr Passwort",
|
||||
"Back": "Zurück",
|
||||
"serviceUnavailable": "Dienst nicht verfügbar",
|
||||
"gracefulShutdown": "Der Dienst steht momentan wegen Wartungsarbeiten nicht zur Verfügung. Bitte versuchen Sie es später noch einmal.",
|
||||
"Yes": "Ja",
|
||||
"reservationError": "Fehler im Reservationssystem",
|
||||
"reservationErrorMsg": "Fehler, Nummer: __code__, Nachricht: __msg__",
|
||||
"password": "Passwort",
|
||||
"userPassword": "Benutzerpasswort",
|
||||
"token": "Token"
|
||||
},
|
||||
"email": {
|
||||
"sharedKeyThis conference is password protected. Please use the following pin when joining:\n\n __sharedKey__ \n\n": [
|
||||
"Diese Konferenz ist Passwortgeschützt. Bitte verwenden Sie diesen PIN zum Beitreten:",
|
||||
"",
|
||||
" __sharedKey__",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"subject": "Einladung zu einer __appName__ (__conferenceName__)",
|
||||
"bodyHey there, I%27d like to invite you to a __appName__ conference I%27ve just set up.\n\nPlease click on the following link in order to join the conference.\n\n __roomUrl__\n\n__sharedKeyText__ Note that __appName__ is currently only supported by Chromium, Google Chrome and Opera, so you need to be using one of these browsers.\n\nTalk to you in a sec!": [
|
||||
"Hallo!",
|
||||
"Ich möchte dich zu einer eben erstellten __appName__-Konferenz einladen.",
|
||||
"",
|
||||
"Bitte klicke auf den folgenden Link um der Konferenz ebenfalls beizutreten:\\",
|
||||
" __roomUrl__",
|
||||
"__sharedKeyText__ Bitte beachte, dass __appName__ momentan nur mit einem der Browser Chromium, Google und Opera verwendet werden kann.",
|
||||
"",
|
||||
"Bis gleich!"
|
||||
]
|
||||
}
|
||||
}
|
||||
173
lang/main-tr.json
Normal file
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"contactlist": "KİŞİ LİSTESİ",
|
||||
"connectionsettings": "Bağlantı Ayarları",
|
||||
"poweredby": "Gücünün kaynağı",
|
||||
"downloadlogs": "Günlükleri indir",
|
||||
"welcomepage": {
|
||||
"go": "GİT",
|
||||
"roomname": "Oda adı girin",
|
||||
"disable": "Sonraki girişimde bu sayfayı gösterme",
|
||||
"feature1": {
|
||||
"title": "Kullanımı kolay",
|
||||
"content": "İndirmeye gerek yok. __app__ tarayıcınızda doğrudan çalışır. Başlamak için görüşme bağlantısını URL diğerleri ile paylaşın."
|
||||
},
|
||||
"feature2": {
|
||||
"title": "Düşük bant genişliği ihtiyacı",
|
||||
"content": "Ekran paylaşımı ve sadece ses ile çok katılımcılı video görüşmeleri, 128Kbps bağlantı ile mümkündür."
|
||||
},
|
||||
"feature3": {
|
||||
"title": "Açık kaynak kodlu",
|
||||
"content": "__app__ MIT ile lisanslanmıştır. Bu lisansa uygun olarak indirmek, kullanmak, değiştirmek ve paylaşmakta özgürsün."
|
||||
},
|
||||
"feature4": {
|
||||
"title": "Sınırsız sayıda kullanıcı",
|
||||
"content": "Kullanıcılar veya konferans katılımcılarının sayısında hiçbir yapay kısıtlama yoktur. Sadece sunucun güç ve bant genişliği, sınırlayıcı unsurdur."
|
||||
},
|
||||
"feature5": {
|
||||
"title": "Ekran paylaşımı",
|
||||
"content": "Diğerlerinle ekranınızı kolayca paylaşın. __app__ çevrimiçi sunumlar, dersler ve teknik destek oturumları için idealdir."
|
||||
},
|
||||
"feature6": {
|
||||
"title": "Güvenli odalar",
|
||||
"content": "Biraz gizliliğe ihtiyacınız var? __app__ görüşme odaları, istemeyen misafirleri uzak tutmak ve kesinleri önlemek için bir parola ile güvence altına alınabilir."
|
||||
},
|
||||
"feature7": {
|
||||
"title": "Paylaşımlı notlar",
|
||||
"content": "__app__ Etherpad içerir, gerçek zamanlı bir ortak çalışma metin düzenleyicisidir. Görüşme tutanakları, makale yazımı ve daha fazlası için biçilmiş kaftandır."
|
||||
},
|
||||
"feature8": {
|
||||
"title": "Kullanım istatistikleri",
|
||||
"content": "Piwik, Google Analytics ve diğer kullanım izleme ve istatistik sistemleri ile kolay tümleştirmeyle kullanıcılar hakkında bilgi edinin."
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
"mute": "Sessiz / Sesli",
|
||||
"videomute": "Kamera başlat / durdur",
|
||||
"authenticate": "",
|
||||
"record": "Kaydet",
|
||||
"lock": "Odayı kilitle / kilit aç",
|
||||
"invite": "Arkadaşlarını davet et",
|
||||
"chat": "",
|
||||
"prezi": "Prezi paylaş",
|
||||
"etherpad": "Paylaşımlı belge",
|
||||
"sharescreen": "Ekran paylaş",
|
||||
"fullscreen": "Tam Ekrana Gir / Çık",
|
||||
"sip": "SIP numara ara",
|
||||
"Settings": "Ayarlar",
|
||||
"hangup": "Kapat",
|
||||
"login": "Oturum aç",
|
||||
"logout": ""
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Sohbeti aç / kapa",
|
||||
"filmstrip": "Kişi listesi aç / kapa",
|
||||
"contactlist": "Film şeridini göster / gizle"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "Aşağıdaki kutuya bir takma ad girin",
|
||||
"popover": "Bir takma ad seçin"
|
||||
},
|
||||
"messagebox": "Metin girin..."
|
||||
},
|
||||
"settings": {
|
||||
"title": "AYARLAR",
|
||||
"update": "Güncelle",
|
||||
"name": "Ad"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "Görünür adınızı değiştirmek<br/>için tıkla",
|
||||
"moderator": "Bu görüşmenin<br/>sahibi",
|
||||
"videomute": "Katılımcı<br/>kamera durdurdu.",
|
||||
"mute": "Katılımcı sessiz",
|
||||
"kick": "Kovuldu",
|
||||
"muted": "Sessiz",
|
||||
"domute": "Sustur"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"bitrate": "Bit hızı:",
|
||||
"packetloss": "Paket kaybı:",
|
||||
"resolution": "Çözünürlük:",
|
||||
"less": "Daha az göster",
|
||||
"more": "Daha fazla göster",
|
||||
"address": "Adres:",
|
||||
"remoteport": "Uzak port:Uzak portlar:",
|
||||
"localport": "Yerel port:Yerel portlar:",
|
||||
"localaddress": "Yerel adres:Yerel adresler:",
|
||||
"remoteaddress": "Uzak adres:Uzak adresler:",
|
||||
"transport": "Transport:",
|
||||
"bandwidth": "Tahmini bant genişliği:",
|
||||
"na": "Görüşme başladıktan sonra bağlantı bilgileri için buraya gel"
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "bağlantı kesildi",
|
||||
"moderator": "Görüşme yöneticisi hakları verildi!",
|
||||
"connected": "bağlandı",
|
||||
"somebody": "Birisi",
|
||||
"me": "Bana",
|
||||
"focus": "Görüşme odağı",
|
||||
"focusFail": "__component__ uygun değil - __ms__ saniye içinde tekrar deneyin",
|
||||
"grantedTo": "__to__, görüşme yöneticisi hakları verildi!",
|
||||
"grantedToUnknown": "$t(somebody), görüşme yöneticisi hakları verildi!"
|
||||
},
|
||||
"dialog": {
|
||||
"kickMessage": "Ahhh! Görüşmeden, kavuldun!",
|
||||
"popupError": "Tarayıcınız bu siteden açılır pencereleri engelliyor. Lütfen, tarayıcınızın güvenlik ayarlarında pop-up etkinleştirin ve tekrar deneyin.",
|
||||
"passwordError": "Bu görüşme şu anda bir parola ile korunmaktadır. Sadece görüşmenin sahibi bir parola ayarlayabilir.",
|
||||
"passwordError2": "Bu görüşme şu anda bir parola ile korunmamaktadır. Sadece görüşmenin sahibi bir parola ayarlayabilir.",
|
||||
"joinError": "Amanin boo! Görüşmeye katılamadık. Güvenlik yapılandırması ile ilgili bir sorun olabilir. Hizmet yöneticisi ile bağlantı kurun.",
|
||||
"connectError": "Amanin boo! Birşeyler ters gitti ve görüşmeye bağlanamadık.",
|
||||
"error": "Hata",
|
||||
"detectext": "Ekran paylaşımı eklentisi tespit edilirken hata.",
|
||||
"failtoinstall": "Masaüstü paylaşım eklentisi yüklenemedi",
|
||||
"failedpermissions": "Yerel mikrofon ve/veya kamerayı kullanmak için izinler alınamadı.",
|
||||
"bridgeUnavailable": "Jitsi Videobridge şu anda kullanılamıyor. Daha sonra tekrar deneyiniz!",
|
||||
"lockTitle": "Kilitlenemedi",
|
||||
"lockMessage": "Görüşme kilitlenemedi.",
|
||||
"warning": "Uyarı",
|
||||
"passwordNotSupported": "Oda parolaları şu anda desteklenmemekte.",
|
||||
"sorry": "Üzgünüz",
|
||||
"internalError": "İç uygulama hatası [setRemoteDescription]",
|
||||
"unableToSwitch": "Video akışı açılamıyor.",
|
||||
"SLDFailure": "Amanin boo! Birşeyler ters gitti ve sessize alamadık! (SLD Başarısız)",
|
||||
"SRDFailure": "Amanin boo! Birşeyler ters gitti ve videoyu durduramadık! (SRD Başarısız)",
|
||||
"oops": "Amanin boo!",
|
||||
"defaultError": "Bir tür hata var",
|
||||
"passwordRequired": "Parola gerekli",
|
||||
"Ok": "Tamam",
|
||||
"removePreziTitle": "Prezi kaldır",
|
||||
"removePreziMsg": "Prezi kaldırmak istediğinizden emin misiniz?",
|
||||
"sharePreziTitle": "Bir Prezi paylaşın",
|
||||
"sharePreziMsg": "Diğer katılımcı hala bir Prezi paylaşıyor.Bu görüşme aynı zamanda sadece bir Prezi izin verir.",
|
||||
"Remove": "Kaldır",
|
||||
"Stop": "Durdur",
|
||||
"AuthMsg": "Oda oluşturmak için kimlik doğrulama gerekli:<br/><b>__room__ </b></br> Oda oluşturmak için ya kimlik doğrulamalı ya da bunu yapması için bir başkasını beklemelisiniz.",
|
||||
"Authenticate": "Kimlik doğrula",
|
||||
"Cancel": "İptal",
|
||||
"logoutTitle": "Oturum kapat",
|
||||
"logoutQuestion": "Oturumu ve görüşmeyi sonlandırmak istediğinizden emin misiniz?",
|
||||
"sessTerminated": "Oturum sonlandırıldı",
|
||||
"hungUp": "Görüşmeyi bitirdiniz",
|
||||
"joinAgain": "Yeniden katıl",
|
||||
"Share": "Paylaş",
|
||||
"preziLinkError": "Lütfen doğru prezi bağlantısı verin.",
|
||||
"Save": "Kaydet",
|
||||
"recordingToken": "Kayıt jetonu girin",
|
||||
"Dial": "Ara",
|
||||
"sipMsg": "SIP numarası gir",
|
||||
"passwordCheck": "Parolanızı kaldırmak istediğinizden emin misiniz?",
|
||||
"passwordMsg": "Odanızı kilitlemek için bir parola koyun",
|
||||
"Invite": "Davet et",
|
||||
"shareLink": "Davet etmek istediğiniz herkesle bu bağlantıyı paylaşın",
|
||||
"settings1": "Görüşmenizi yapılandır",
|
||||
"settings2": "Katılımcılar sessiz katılsın",
|
||||
"settings3": "Takma adlar gerekli<br/><br/>Odanızı kitlemek için bir parola ayarlayın:",
|
||||
"yourPassword": "parolanız",
|
||||
"Back": "Geri",
|
||||
"serviceUnavailable": "Hizmet kullanım dışı",
|
||||
"gracefulShutdown": "Hizmetimiz bakıp için durduruldu. Daha sonra tekrar deneyiniz.",
|
||||
"Yes": "Evet",
|
||||
"reservationError": "Rezervasyon sistemi hatası",
|
||||
"reservationErrorMsg": "Hata kodu: __code__, mesaj: __msg__"
|
||||
}
|
||||
}
|
||||
220
lang/main.json
Normal file
@@ -0,0 +1,220 @@
|
||||
{
|
||||
"contactlist": "CONTACT LIST",
|
||||
"connectionsettings": "Connection Settings",
|
||||
"poweredby": "powered by",
|
||||
"downloadlogs": "Download logs",
|
||||
"roomUrlDefaultMsg": "Your conference is currently being created...",
|
||||
"participant": "Participant",
|
||||
"me": "me",
|
||||
"speaker": "Speaker",
|
||||
"defaultNickname": "ex. __name__",
|
||||
"defaultPreziLink": "e.g. __url__",
|
||||
"welcomepage":{
|
||||
"go": "GO",
|
||||
"roomname": "Enter room name",
|
||||
"disable": "Don't show this page next time I enter",
|
||||
"feature1": {
|
||||
"title": "Simple to use",
|
||||
"content": "No downloads required. __app__ works directly within your browser. Simply share your conference URL with others to get started."
|
||||
},
|
||||
"feature2": {
|
||||
"title": "Low bandwidth",
|
||||
"content": "Multi-party video conferences work with as little as 128Kbps. Screen-sharing and audio-only conferences are possible with far less."
|
||||
},
|
||||
"feature3": {
|
||||
"title": "Open source",
|
||||
"content": "__app__ is licensed under MIT. You are free to download, use, modify, and share them as per these licenses."
|
||||
},
|
||||
"feature4": {
|
||||
"title": "Unlimited users",
|
||||
"content": "There are no artificial restrictions on the number of users or conference participants. Server power and bandwidth are the only limiting factors."
|
||||
},
|
||||
"feature5": {
|
||||
"title": "Screen sharing",
|
||||
"content": "It's easy to share your screen with others. __app__ is ideal for on-line presentations, lectures, and tech support sessions."
|
||||
},
|
||||
"feature6": {
|
||||
"title": "Secure rooms",
|
||||
"content": "Need some privacy? __app__ conference rooms can be secured with a password in order to exclude unwanted guests and prevent interruptions."
|
||||
},
|
||||
"feature7": {
|
||||
"title": "Shared notes",
|
||||
"content": "__app__ features Etherpad, a real-time collaborative text editor that's great for meeting minutes, writing articles, and more."
|
||||
},
|
||||
"feature8": {
|
||||
"title": "Usage statistics",
|
||||
"content": "Learn about your users through easy integration with Piwik, Google Analytics, and other usage monitoring and statistics systems."
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
"mute": "Mute / Unmute",
|
||||
"videomute": "Start / stop camera",
|
||||
"authenticate": "Authenticate",
|
||||
"record": "Record",
|
||||
"lock": "Lock / unlock room",
|
||||
"invite": "Invite others",
|
||||
"chat": "Open / close chat",
|
||||
"prezi": "Share Prezi",
|
||||
"etherpad": "Shared document",
|
||||
"sharescreen": "Share screen",
|
||||
"fullscreen": "Enter / Exit Full Screen",
|
||||
"sip": "Call SIP number",
|
||||
"Settings": "Settings",
|
||||
"hangup": "Hang Up",
|
||||
"login": "Login",
|
||||
"logout": "Logout"
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Open / close chat",
|
||||
"filmstrip": "Show / hide film strip",
|
||||
"contactlist": "Open / close contact list"
|
||||
},
|
||||
"chat":{
|
||||
"nickname": {
|
||||
"title": "Enter a nickname in the box below",
|
||||
"popover": "Choose a nickname"
|
||||
},
|
||||
"messagebox": "Enter text..."
|
||||
},
|
||||
"settings":
|
||||
{
|
||||
"title": "SETTINGS",
|
||||
"update": "Update",
|
||||
"name": "Name"
|
||||
},
|
||||
"videothumbnail":
|
||||
{
|
||||
"editnickname": "Click to edit your<br/>display name",
|
||||
"moderator": "The owner of<br/>this conference",
|
||||
"videomute": "Participant has<br/>stopped the camera.",
|
||||
"mute": "Participant is muted",
|
||||
"kick": "Kick out",
|
||||
"muted": "Muted",
|
||||
"domute": "Mute"
|
||||
|
||||
},
|
||||
"connectionindicator":
|
||||
{
|
||||
"bitrate": "Bitrate:",
|
||||
"packetloss": "Packet loss:",
|
||||
"resolution": "Resolution:",
|
||||
"less": "Show less",
|
||||
"more": "Show more",
|
||||
"address": "Address:",
|
||||
"remoteport_plural": "Remote ports:",
|
||||
"localport_plural": "Local ports:",
|
||||
"remoteport": "Remote port:",
|
||||
"localport": "Local port:",
|
||||
"localaddress": "Local address:",
|
||||
"localaddress_plural": "Local addresses:",
|
||||
"remoteaddress": "Remote address:",
|
||||
"remoteaddress_plural": "Remote addresses:",
|
||||
"transport": "Transport:",
|
||||
"bandwidth": "Estimated bandwidth:",
|
||||
"na": "Come back here for connection information once the conference starts"
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "disconnected",
|
||||
"moderator": "Moderator rights granted!",
|
||||
"connected": "connected",
|
||||
"somebody": "Somebody",
|
||||
"me": "Me",
|
||||
"focus": "Conference focus",
|
||||
"focusFail": "__component__ not available - retry in __ms__ sec",
|
||||
"grantedTo": "Moderator rights granted to __to__!",
|
||||
"grantedToUnknown": "Moderator rights granted to $t(somebody)!"
|
||||
|
||||
},
|
||||
"dialog": {
|
||||
"kickMessage": "Ouch! You have been kicked out of the meet!",
|
||||
"popupError": "Your browser is blocking popup windows from this site. Please enable popups in your browser security settings and try again.",
|
||||
"passwordError": "This conversation is currently protected by a password. Only the owner of the conference could set a password.",
|
||||
"passwordError2": "This conversation isn't currently protected by a password. Only the owner of the conference could set a password.",
|
||||
"joinError": "Oops! We couldn't join the conference. There might be some problem with security configuration. Please contact service administrator.",
|
||||
"connectError": "Oops! Something went wrong and we couldn't connect to the conference.",
|
||||
"error": "Error",
|
||||
"detectext": "Error when trying to detect desktopsharing extension.",
|
||||
"failtoinstall": "Failed to install desktop sharing extension",
|
||||
"failedpermissions": "Failed to obtain permissions to use the local microphone and/or camera.",
|
||||
"bridgeUnavailable": "Jitsi Videobridge is currently unavailable. Please try again later!",
|
||||
"lockTitle": "Lock failed",
|
||||
"lockMessage": "Failed to lock conference.",
|
||||
"warning": "Warning",
|
||||
"passwordNotSupported": "Room passwords are currently not supported.",
|
||||
"sorry": "Sorry",
|
||||
"internalError": "Internal application error [setRemoteDescription]",
|
||||
"unableToSwitch": "Unable to switch video stream.",
|
||||
"SLDFailure": "Oops! Something went wrong and we failed to mute! (SLD Failure)",
|
||||
"SRDFailure": "Oops! Something went wrong and we failed to stop video! (SRD Failure)",
|
||||
"oops": "Oops!",
|
||||
"defaultError": "There was some kind of error",
|
||||
"passwordRequired": "Password required",
|
||||
"Ok": "Ok",
|
||||
"removePreziTitle": "Remove Prezi",
|
||||
"removePreziMsg": "Are you sure you would like to remove your Prezi?",
|
||||
"sharePreziTitle": "Share a Prezi",
|
||||
"sharePreziMsg": "Another participant is already sharing a Prezi.This conference allows only one Prezi at a time.",
|
||||
"Remove": "Remove",
|
||||
"Stop": "Stop",
|
||||
"AuthMsg": "Authentication is required to create room:<br/><b>__room__ </b></br> You can either authenticate to create the room or just wait for someone else to do so.",
|
||||
"Authenticate": "Authenticate",
|
||||
"Cancel": "Cancel",
|
||||
"logoutTitle" : "Logout",
|
||||
"logoutQuestion" : "Are you sure you want to logout and stop the conference?",
|
||||
"sessTerminated": "Session Terminated",
|
||||
"hungUp": "You hung up the call",
|
||||
"joinAgain": "Join again",
|
||||
"Share": "Share",
|
||||
"preziLinkError": "Please provide a correct prezi link.",
|
||||
"Save": "Save",
|
||||
"recordingToken": "Enter recording token",
|
||||
"Dial": "Dial",
|
||||
"sipMsg": "Enter SIP number",
|
||||
"passwordCheck": "Are you sure you would like to remove your password?",
|
||||
"Remove": "Remove",
|
||||
"passwordMsg": "Set a password to lock your room",
|
||||
"Invite": "Invite",
|
||||
"shareLink": "Share this link with everyone you want to invite",
|
||||
"settings1": "Configure your conference",
|
||||
"settings2": "Participants join muted",
|
||||
"settings3": "Require nicknames<br/><br/>Set a password to lock your room:",
|
||||
"yourPassword": "your password",
|
||||
"Back": "Back",
|
||||
"serviceUnavailable": "Service unavailable",
|
||||
"gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
|
||||
"Yes": "Yes",
|
||||
"reservationError": "Reservation system error",
|
||||
"reservationErrorMsg": "Error code: __code__, message: __msg__",
|
||||
"password": "password",
|
||||
"userPassword": "user password",
|
||||
"token": "token"
|
||||
},
|
||||
"email":
|
||||
{
|
||||
"sharedKey": [
|
||||
"This conference is password protected. Please use the following pin when joining:",
|
||||
"",
|
||||
"",
|
||||
"__sharedKey__",
|
||||
"",
|
||||
""],
|
||||
"subject": "Invitation to a __appName__ (__conferenceName__)",
|
||||
"body": [
|
||||
"Hey there, I%27d like to invite you to a __appName__ conference I%27ve just set up.",
|
||||
"",
|
||||
"",
|
||||
"Please click on the following link in order to join the conference.",
|
||||
"",
|
||||
"",
|
||||
"__roomUrl__",
|
||||
"",
|
||||
"",
|
||||
"__sharedKeyText__",
|
||||
" Note that __appName__ is currently only supported by __supportedBrowsers__, so you need to be using one of these browsers.",
|
||||
"",
|
||||
"",
|
||||
"Talk to you in a sec!"
|
||||
],
|
||||
"and": "and"
|
||||
}
|
||||
}
|
||||
26947
libs/app.bundle.js
Normal file
3747
libs/pako.bundle.js
94
libs/rayo.js
@@ -1,94 +0,0 @@
|
||||
/* jshint -W117 */
|
||||
Strophe.addConnectionPlugin('rayo',
|
||||
{
|
||||
RAYO_XMLNS: 'urn:xmpp:rayo:1',
|
||||
connection: null,
|
||||
init: function (conn)
|
||||
{
|
||||
this.connection = conn;
|
||||
if (this.connection.disco)
|
||||
{
|
||||
this.connection.disco.addFeature('urn:xmpp:rayo:client:1');
|
||||
}
|
||||
|
||||
this.connection.addHandler(
|
||||
this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null);
|
||||
},
|
||||
onRayo: function (iq)
|
||||
{
|
||||
console.info("Rayo IQ", iq);
|
||||
},
|
||||
dial: function (to, from, roomName)
|
||||
{
|
||||
var self = this;
|
||||
var req = $iq(
|
||||
{
|
||||
type: 'set',
|
||||
to: config.hosts.call_control
|
||||
}
|
||||
);
|
||||
req.c('dial',
|
||||
{
|
||||
xmlns: this.RAYO_XMLNS,
|
||||
to: to,
|
||||
from: from
|
||||
});
|
||||
req.c('header',
|
||||
{
|
||||
name: 'JvbRoomName',
|
||||
value: roomName
|
||||
});
|
||||
|
||||
this.connection.sendIQ(
|
||||
req,
|
||||
function (result)
|
||||
{
|
||||
console.info('Dial result ', result);
|
||||
|
||||
var resource = $(result).find('ref').attr('uri');
|
||||
this.call_resource = resource.substr('xmpp:'.length);
|
||||
console.info(
|
||||
"Received call resource: " + this.call_resource);
|
||||
},
|
||||
function (error)
|
||||
{
|
||||
console.info('Dial error ', error);
|
||||
}
|
||||
);
|
||||
},
|
||||
hang_up: function ()
|
||||
{
|
||||
if (!this.call_resource)
|
||||
{
|
||||
console.warn("No call in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var req = $iq(
|
||||
{
|
||||
type: 'set',
|
||||
to: this.call_resource
|
||||
}
|
||||
);
|
||||
req.c('hangup',
|
||||
{
|
||||
xmlns: this.RAYO_XMLNS
|
||||
});
|
||||
|
||||
this.connection.sendIQ(
|
||||
req,
|
||||
function (result)
|
||||
{
|
||||
console.info('Hangup result ', result);
|
||||
self.call_resource = null;
|
||||
},
|
||||
function (error)
|
||||
{
|
||||
console.info('Hangup error ', error);
|
||||
self.call_resource = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1,306 +0,0 @@
|
||||
/* jshint -W117 */
|
||||
Strophe.addConnectionPlugin('jingle', {
|
||||
connection: null,
|
||||
sessions: {},
|
||||
jid2session: {},
|
||||
ice_config: {iceServers: []},
|
||||
pc_constraints: {},
|
||||
media_constraints: {
|
||||
mandatory: {
|
||||
'OfferToReceiveAudio': true,
|
||||
'OfferToReceiveVideo': true
|
||||
}
|
||||
// MozDontOfferDataChannel: true when this is firefox
|
||||
},
|
||||
localAudio: null,
|
||||
localVideo: null,
|
||||
|
||||
init: function (conn) {
|
||||
this.connection = conn;
|
||||
if (this.connection.disco) {
|
||||
// http://xmpp.org/extensions/xep-0167.html#support
|
||||
// http://xmpp.org/extensions/xep-0176.html#support
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:1');
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
|
||||
|
||||
|
||||
// this is dealt with by SDP O/A so we don't need to annouce this
|
||||
//this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
|
||||
//this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
|
||||
if (config.useRtcpMux) {
|
||||
this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
|
||||
}
|
||||
if (config.useBundle) {
|
||||
this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
|
||||
}
|
||||
//this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
|
||||
}
|
||||
this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
|
||||
},
|
||||
onJingle: function (iq) {
|
||||
var sid = $(iq).find('jingle').attr('sid');
|
||||
var action = $(iq).find('jingle').attr('action');
|
||||
var fromJid = iq.getAttribute('from');
|
||||
// send ack first
|
||||
var ack = $iq({type: 'result',
|
||||
to: fromJid,
|
||||
id: iq.getAttribute('id')
|
||||
});
|
||||
console.log('on jingle ' + action + ' from ' + fromJid, iq);
|
||||
var sess = this.sessions[sid];
|
||||
if ('session-initiate' != action) {
|
||||
if (sess === null) {
|
||||
ack.type = 'error';
|
||||
ack.c('error', {type: 'cancel'})
|
||||
.c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
|
||||
.c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
|
||||
this.connection.send(ack);
|
||||
return true;
|
||||
}
|
||||
// compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
|
||||
// local jid is not checked
|
||||
if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
|
||||
console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
|
||||
ack.type = 'error';
|
||||
ack.c('error', {type: 'cancel'})
|
||||
.c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
|
||||
.c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
|
||||
this.connection.send(ack);
|
||||
return true;
|
||||
}
|
||||
} else if (sess !== undefined) {
|
||||
// existing session with same session id
|
||||
// this might be out-of-order if the sess.peerjid is the same as from
|
||||
ack.type = 'error';
|
||||
ack.c('error', {type: 'cancel'})
|
||||
.c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
|
||||
console.warn('duplicate session id', sid);
|
||||
this.connection.send(ack);
|
||||
return true;
|
||||
}
|
||||
// FIXME: check for a defined action
|
||||
this.connection.send(ack);
|
||||
// see http://xmpp.org/extensions/xep-0166.html#concepts-session
|
||||
switch (action) {
|
||||
case 'session-initiate':
|
||||
sess = new JingleSession($(iq).attr('to'), $(iq).find('jingle').attr('sid'), this.connection);
|
||||
// configure session
|
||||
|
||||
//in firefox we have only one stream object
|
||||
if (this.localAudio != this.localVideo) {
|
||||
sess.localStreams.push(this.localAudio);
|
||||
}
|
||||
if (this.localVideo) {
|
||||
sess.localStreams.push(this.localVideo);
|
||||
}
|
||||
sess.media_constraints = this.media_constraints;
|
||||
sess.pc_constraints = this.pc_constraints;
|
||||
sess.ice_config = this.ice_config;
|
||||
|
||||
sess.initiate(fromJid, false);
|
||||
// FIXME: setRemoteDescription should only be done when this call is to be accepted
|
||||
sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
|
||||
|
||||
this.sessions[sess.sid] = sess;
|
||||
this.jid2session[sess.peerjid] = sess;
|
||||
|
||||
// the callback should either
|
||||
// .sendAnswer and .accept
|
||||
// or .sendTerminate -- not necessarily synchronus
|
||||
$(document).trigger('callincoming.jingle', [sess.sid]);
|
||||
break;
|
||||
case 'session-accept':
|
||||
sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
|
||||
sess.accept();
|
||||
$(document).trigger('callaccepted.jingle', [sess.sid]);
|
||||
break;
|
||||
case 'session-terminate':
|
||||
// If this is not the focus sending the terminate, we have
|
||||
// nothing more to do here.
|
||||
if (Object.keys(this.sessions).length < 1
|
||||
|| !(this.sessions[Object.keys(this.sessions)[0]]
|
||||
instanceof JingleSession))
|
||||
{
|
||||
break;
|
||||
}
|
||||
console.log('terminating...', sess.sid);
|
||||
sess.terminate();
|
||||
this.terminate(sess.sid);
|
||||
if ($(iq).find('>jingle>reason').length) {
|
||||
$(document).trigger('callterminated.jingle', [
|
||||
sess.sid,
|
||||
sess.peerjid,
|
||||
$(iq).find('>jingle>reason>:first')[0].tagName,
|
||||
$(iq).find('>jingle>reason>text').text()
|
||||
]);
|
||||
} else {
|
||||
$(document).trigger('callterminated.jingle',
|
||||
[sess.sid, sess.peerjid]);
|
||||
}
|
||||
break;
|
||||
case 'transport-info':
|
||||
sess.addIceCandidate($(iq).find('>jingle>content'));
|
||||
break;
|
||||
case 'session-info':
|
||||
var affected;
|
||||
if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
||||
$(document).trigger('ringing.jingle', [sess.sid]);
|
||||
} else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
||||
affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
|
||||
$(document).trigger('mute.jingle', [sess.sid, affected]);
|
||||
} else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
||||
affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
|
||||
$(document).trigger('unmute.jingle', [sess.sid, affected]);
|
||||
}
|
||||
break;
|
||||
case 'addsource': // FIXME: proprietary, un-jingleish
|
||||
case 'source-add': // FIXME: proprietary
|
||||
sess.addSource($(iq).find('>jingle>content'), fromJid);
|
||||
break;
|
||||
case 'removesource': // FIXME: proprietary, un-jingleish
|
||||
case 'source-remove': // FIXME: proprietary
|
||||
sess.removeSource($(iq).find('>jingle>content'), fromJid);
|
||||
break;
|
||||
default:
|
||||
console.warn('jingle action not implemented', action);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
|
||||
var sess = new JingleSession(myjid || this.connection.jid,
|
||||
Math.random().toString(36).substr(2, 12), // random string
|
||||
this.connection);
|
||||
// configure session
|
||||
|
||||
//in firefox we have only one stream
|
||||
if (this.localAudio != this.localVideo) {
|
||||
sess.localStreams.push(this.localAudio);
|
||||
}
|
||||
if (this.localVideo) {
|
||||
sess.localStreams.push(this.localVideo);
|
||||
}
|
||||
sess.media_constraints = this.media_constraints;
|
||||
sess.pc_constraints = this.pc_constraints;
|
||||
sess.ice_config = this.ice_config;
|
||||
|
||||
sess.initiate(peerjid, true);
|
||||
this.sessions[sess.sid] = sess;
|
||||
this.jid2session[sess.peerjid] = sess;
|
||||
sess.sendOffer();
|
||||
return sess;
|
||||
},
|
||||
terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
|
||||
if (sid === null || sid === undefined) {
|
||||
for (sid in this.sessions) {
|
||||
if (this.sessions[sid].state != 'ended') {
|
||||
this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
|
||||
this.sessions[sid].terminate();
|
||||
}
|
||||
delete this.jid2session[this.sessions[sid].peerjid];
|
||||
delete this.sessions[sid];
|
||||
}
|
||||
} else if (this.sessions.hasOwnProperty(sid)) {
|
||||
if (this.sessions[sid].state != 'ended') {
|
||||
this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
|
||||
this.sessions[sid].terminate();
|
||||
}
|
||||
delete this.jid2session[this.sessions[sid].peerjid];
|
||||
delete this.sessions[sid];
|
||||
}
|
||||
},
|
||||
// Used to terminate a session when an unavailable presence is received.
|
||||
terminateByJid: function (jid) {
|
||||
if (this.jid2session.hasOwnProperty(jid)) {
|
||||
var sess = this.jid2session[jid];
|
||||
if (sess) {
|
||||
sess.terminate();
|
||||
console.log('peer went away silently', jid);
|
||||
delete this.sessions[sess.sid];
|
||||
delete this.jid2session[jid];
|
||||
$(document).trigger('callterminated.jingle',
|
||||
[sess.sid, jid], 'gone');
|
||||
}
|
||||
}
|
||||
},
|
||||
terminateRemoteByJid: function (jid, reason) {
|
||||
if (this.jid2session.hasOwnProperty(jid)) {
|
||||
var sess = this.jid2session[jid];
|
||||
if (sess) {
|
||||
sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
|
||||
sess.terminate();
|
||||
console.log('terminate peer with jid', sess.sid, jid);
|
||||
delete this.sessions[sess.sid];
|
||||
delete this.jid2session[jid];
|
||||
$(document).trigger('callterminated.jingle',
|
||||
[sess.sid, jid, 'kicked']);
|
||||
}
|
||||
}
|
||||
},
|
||||
getStunAndTurnCredentials: function () {
|
||||
// get stun and turn configuration from server via xep-0215
|
||||
// uses time-limited credentials as described in
|
||||
// http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
|
||||
//
|
||||
// see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
|
||||
// for a prosody module which implements this
|
||||
//
|
||||
// currently, this doesn't work with updateIce and therefore credentials with a long
|
||||
// validity have to be fetched before creating the peerconnection
|
||||
// TODO: implement refresh via updateIce as described in
|
||||
// https://code.google.com/p/webrtc/issues/detail?id=1650
|
||||
var self = this;
|
||||
this.connection.sendIQ(
|
||||
$iq({type: 'get', to: this.connection.domain})
|
||||
.c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
|
||||
function (res) {
|
||||
var iceservers = [];
|
||||
$(res).find('>services>service').each(function (idx, el) {
|
||||
el = $(el);
|
||||
var dict = {};
|
||||
var type = el.attr('type');
|
||||
switch (type) {
|
||||
case 'stun':
|
||||
dict.url = 'stun:' + el.attr('host');
|
||||
if (el.attr('port')) {
|
||||
dict.url += ':' + el.attr('port');
|
||||
}
|
||||
iceservers.push(dict);
|
||||
break;
|
||||
case 'turn':
|
||||
case 'turns':
|
||||
dict.url = type + ':';
|
||||
if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
|
||||
if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
|
||||
dict.url += el.attr('username') + '@';
|
||||
} else {
|
||||
dict.username = el.attr('username'); // only works in M28
|
||||
}
|
||||
}
|
||||
dict.url += el.attr('host');
|
||||
if (el.attr('port') && el.attr('port') != '3478') {
|
||||
dict.url += ':' + el.attr('port');
|
||||
}
|
||||
if (el.attr('transport') && el.attr('transport') != 'udp') {
|
||||
dict.url += '?transport=' + el.attr('transport');
|
||||
}
|
||||
if (el.attr('password')) {
|
||||
dict.credential = el.attr('password');
|
||||
}
|
||||
iceservers.push(dict);
|
||||
break;
|
||||
}
|
||||
});
|
||||
self.ice_config.iceServers = iceservers;
|
||||
},
|
||||
function (err) {
|
||||
console.warn('getting turn credentials failed', err);
|
||||
console.warn('is mod_turncredentials or similar installed?');
|
||||
}
|
||||
);
|
||||
// implement push?
|
||||
}
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Strophe logger implementation. Logs from level WARN and above.
|
||||
*/
|
||||
Strophe.log = function (level, msg) {
|
||||
switch(level) {
|
||||
case Strophe.LogLevel.WARN:
|
||||
console.warn("Strophe: "+msg);
|
||||
break;
|
||||
case Strophe.LogLevel.ERROR:
|
||||
case Strophe.LogLevel.FATAL:
|
||||
console.error("Strophe: "+msg);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Strophe.getStatusString = function(status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case Strophe.Status.ERROR:
|
||||
return "ERROR";
|
||||
case Strophe.Status.CONNECTING:
|
||||
return "CONNECTING";
|
||||
case Strophe.Status.CONNFAIL:
|
||||
return "CONNFAIL";
|
||||
case Strophe.Status.AUTHENTICATING:
|
||||
return "AUTHENTICATING";
|
||||
case Strophe.Status.AUTHFAIL:
|
||||
return "AUTHFAIL";
|
||||
case Strophe.Status.CONNECTED:
|
||||
return "CONNECTED";
|
||||
case Strophe.Status.DISCONNECTED:
|
||||
return "DISCONNECTED";
|
||||
case Strophe.Status.DISCONNECTING:
|
||||
return "DISCONNECTING";
|
||||
case Strophe.Status.ATTACHED:
|
||||
return "ATTACHED";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Provides a wrapper class for the MediaStream.
|
||||
*
|
||||
* TODO : Add here the src from the video element and other related properties
|
||||
* and get rid of some of the mappings that we use throughout the UI.
|
||||
*/
|
||||
var MediaStream = (function() {
|
||||
/**
|
||||
* Creates a MediaStream object for the given data, session id and ssrc.
|
||||
*
|
||||
* @param data the data object from which we obtain the stream,
|
||||
* the peerjid, etc.
|
||||
* @param sid the session id
|
||||
* @param ssrc the ssrc corresponding to this MediaStream
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function MediaStreamProto(data, sid, ssrc) {
|
||||
this.stream = data.stream;
|
||||
this.peerjid = data.peerjid;
|
||||
this.ssrc = ssrc;
|
||||
this.session = connection.jingle.sessions[sid];
|
||||
this.type = (this.stream.getVideoTracks().length > 0)
|
||||
? MediaStream.VIDEO_TYPE : MediaStream.AUDIO_TYPE;
|
||||
this.muted = false;
|
||||
}
|
||||
|
||||
return MediaStreamProto;
|
||||
})();
|
||||
|
||||
MediaStream.VIDEO_TYPE = 'Video';
|
||||
MediaStream.AUDIO_TYPE = 'Audio';
|
||||
@@ -1,60 +0,0 @@
|
||||
/* global $, $iq, config, connection, focusMucJid, forceMuted, messageHandler,
|
||||
setAudioMuted, Strophe, toggleAudio */
|
||||
/**
|
||||
* Moderate connection plugin.
|
||||
*/
|
||||
Strophe.addConnectionPlugin('moderate', {
|
||||
connection: null,
|
||||
init: function (conn) {
|
||||
this.connection = conn;
|
||||
|
||||
this.connection.addHandler(this.onMute.bind(this),
|
||||
'http://jitsi.org/jitmeet/audio',
|
||||
'iq',
|
||||
'set',
|
||||
null,
|
||||
null);
|
||||
},
|
||||
setMute: function (jid, mute) {
|
||||
console.info("set mute", mute);
|
||||
var iqToFocus = $iq({to: focusMucJid, type: 'set'})
|
||||
.c('mute', {
|
||||
xmlns: 'http://jitsi.org/jitmeet/audio',
|
||||
jid: jid
|
||||
})
|
||||
.t(mute.toString())
|
||||
.up();
|
||||
|
||||
this.connection.sendIQ(
|
||||
iqToFocus,
|
||||
function (result) {
|
||||
console.log('set mute', result);
|
||||
},
|
||||
function (error) {
|
||||
console.log('set mute error', error);
|
||||
// FIXME: this causes an exception
|
||||
//messageHandler.openReportDialog(null, 'Failed to mute ' +
|
||||
// $("#participant_" + jid).find(".displayname").text() ||
|
||||
//"participant" + '.', error);
|
||||
});
|
||||
},
|
||||
onMute: function (iq) {
|
||||
var from = iq.getAttribute('from');
|
||||
if (from !== focusMucJid) {
|
||||
console.warn("Ignored mute from non focus peer");
|
||||
return false;
|
||||
}
|
||||
var mute = $(iq).find('mute');
|
||||
if (mute.length) {
|
||||
var doMuteAudio = mute.text() === "true";
|
||||
setAudioMuted(doMuteAudio);
|
||||
forceMuted = doMuteAudio;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
eject: function (jid) {
|
||||
// We're not the focus, so can't terminate
|
||||
//connection.jingle.terminateRemoteByJid(jid, 'kick');
|
||||
connection.emuc.kick(jid);
|
||||
}
|
||||
});
|
||||
236
moderator.js
@@ -1,236 +0,0 @@
|
||||
/* global $, $iq, config, connection, Etherpad, hangUp, messageHandler,
|
||||
roomName, sessionTerminated, Strophe, Toolbar, Util, VideoLayout */
|
||||
/**
|
||||
* Contains logic responsible for enabling/disabling functionality available
|
||||
* only to moderator users.
|
||||
*/
|
||||
var Moderator = (function (my) {
|
||||
|
||||
var focusUserJid;
|
||||
var getNextTimeout = Util.createExpBackoffTimer(1000);
|
||||
var getNextErrorTimeout = Util.createExpBackoffTimer(1000);
|
||||
// External authentication stuff
|
||||
var externalAuthEnabled = false;
|
||||
|
||||
my.isModerator = function () {
|
||||
return connection.emuc.isModerator();
|
||||
};
|
||||
|
||||
my.isPeerModerator = function (peerJid) {
|
||||
return connection.emuc.getMemberRole(peerJid) === 'moderator';
|
||||
};
|
||||
|
||||
my.isExternalAuthEnabled = function () {
|
||||
return externalAuthEnabled;
|
||||
};
|
||||
|
||||
my.onModeratorStatusChanged = function (isModerator) {
|
||||
|
||||
Toolbar.showSipCallButton(isModerator);
|
||||
Toolbar.showRecordingButton(
|
||||
isModerator); //&&
|
||||
// FIXME:
|
||||
// Recording visible if
|
||||
// there are at least 2(+ 1 focus) participants
|
||||
//Object.keys(connection.emuc.members).length >= 3);
|
||||
|
||||
if (isModerator && config.etherpad_base) {
|
||||
Etherpad.init();
|
||||
}
|
||||
};
|
||||
|
||||
my.init = function () {
|
||||
$(document).bind(
|
||||
'local.role.changed.muc',
|
||||
function (event, jid, info, pres) {
|
||||
Moderator.onModeratorStatusChanged(Moderator.isModerator());
|
||||
}
|
||||
);
|
||||
|
||||
$(document).bind(
|
||||
'left.muc',
|
||||
function (event, jid) {
|
||||
console.info("Someone left is it focus ? " + jid);
|
||||
var resource = Strophe.getResourceFromJid(jid);
|
||||
if (resource === 'focus' && !sessionTerminated) {
|
||||
console.info(
|
||||
"Focus has left the room - leaving conference");
|
||||
//hangUp();
|
||||
// We'd rather reload to have everything re-initialized
|
||||
// FIXME: show some message before reload
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
my.setFocusUserJid = function (focusJid) {
|
||||
if (!focusUserJid) {
|
||||
focusUserJid = focusJid;
|
||||
console.info("Focus jid set to: " + focusUserJid);
|
||||
}
|
||||
};
|
||||
|
||||
my.getFocusUserJid = function () {
|
||||
return focusUserJid;
|
||||
};
|
||||
|
||||
my.getFocusComponent = function () {
|
||||
// Get focus component address
|
||||
var focusComponent = config.hosts.focus;
|
||||
// If not specified use default: 'focus.domain'
|
||||
if (!focusComponent) {
|
||||
focusComponent = 'focus.' + config.hosts.domain;
|
||||
}
|
||||
return focusComponent;
|
||||
};
|
||||
|
||||
my.createConferenceIq = function () {
|
||||
// Generate create conference IQ
|
||||
var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'});
|
||||
elem.c('conference', {
|
||||
xmlns: 'http://jitsi.org/protocol/focus',
|
||||
room: roomName
|
||||
});
|
||||
if (config.hosts.bridge !== undefined)
|
||||
{
|
||||
elem.c(
|
||||
'property',
|
||||
{ name: 'bridge', value: config.hosts.bridge})
|
||||
.up();
|
||||
}
|
||||
if (config.channelLastN !== undefined)
|
||||
{
|
||||
elem.c(
|
||||
'property',
|
||||
{ name: 'channelLastN', value: config.channelLastN})
|
||||
.up();
|
||||
}
|
||||
if (config.adaptiveLastN !== undefined)
|
||||
{
|
||||
elem.c(
|
||||
'property',
|
||||
{ name: 'adaptiveLastN', value: config.adaptiveLastN})
|
||||
.up();
|
||||
}
|
||||
if (config.adaptiveSimulcast !== undefined)
|
||||
{
|
||||
elem.c(
|
||||
'property',
|
||||
{ name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
|
||||
.up();
|
||||
}
|
||||
if (config.openSctp !== undefined)
|
||||
{
|
||||
elem.c(
|
||||
'property',
|
||||
{ name: 'openSctp', value: config.openSctp})
|
||||
.up();
|
||||
}
|
||||
if (config.enableFirefoxSupport !== undefined)
|
||||
{
|
||||
elem.c(
|
||||
'property',
|
||||
{ name: 'enableFirefoxHacks', value: config.enableFirefoxSupport})
|
||||
.up();
|
||||
}
|
||||
elem.up();
|
||||
return elem;
|
||||
};
|
||||
|
||||
my.parseConfigOptions = function (resultIq) {
|
||||
|
||||
Moderator.setFocusUserJid(
|
||||
$(resultIq).find('conference').attr('focusjid'));
|
||||
|
||||
var extAuthParam
|
||||
= $(resultIq).find('>conference>property[name=\'externalAuth\']');
|
||||
if (extAuthParam.length) {
|
||||
externalAuthEnabled = extAuthParam.attr('value') === 'true';
|
||||
}
|
||||
console.info("External authentication enabled: " + externalAuthEnabled);
|
||||
};
|
||||
|
||||
// FIXME: we need to show the fact that we're waiting for the focus
|
||||
// to the user(or that focus is not available)
|
||||
my.allocateConferenceFocus = function (roomName, callback) {
|
||||
// Try to use focus user JID from the config
|
||||
Moderator.setFocusUserJid(config.focusUserJid);
|
||||
// Send create conference IQ
|
||||
var iq = Moderator.createConferenceIq();
|
||||
connection.sendIQ(
|
||||
iq,
|
||||
function (result) {
|
||||
if ('true' === $(result).find('conference').attr('ready')) {
|
||||
// Reset both timers
|
||||
getNextTimeout(true);
|
||||
getNextErrorTimeout(true);
|
||||
// Setup config options
|
||||
Moderator.parseConfigOptions(result);
|
||||
// Exec callback
|
||||
callback();
|
||||
} else {
|
||||
var waitMs = getNextTimeout();
|
||||
console.info("Waiting for the focus... " + waitMs);
|
||||
// Reset error timeout
|
||||
getNextErrorTimeout(true);
|
||||
window.setTimeout(
|
||||
function () {
|
||||
Moderator.allocateConferenceFocus(
|
||||
roomName, callback);
|
||||
}, waitMs);
|
||||
}
|
||||
},
|
||||
function (error) {
|
||||
// Not authorized to create new room
|
||||
if ($(error).find('>error>not-authorized').length) {
|
||||
console.warn("Unauthorized to start the conference");
|
||||
$(document).trigger('auth_required.moderator');
|
||||
return;
|
||||
}
|
||||
var waitMs = getNextErrorTimeout();
|
||||
console.error("Focus error, retry after " + waitMs, error);
|
||||
// Show message
|
||||
messageHandler.notify(
|
||||
'Conference focus', 'disconnected',
|
||||
Moderator.getFocusComponent() +
|
||||
' not available - retry in ' + (waitMs / 1000) + ' sec');
|
||||
// Reset response timeout
|
||||
getNextTimeout(true);
|
||||
window.setTimeout(
|
||||
function () {
|
||||
Moderator.allocateConferenceFocus(roomName, callback);
|
||||
}, waitMs);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
my.getAuthUrl = function (urlCallback) {
|
||||
var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
|
||||
iq.c('auth-url', {
|
||||
xmlns: 'http://jitsi.org/protocol/focus',
|
||||
room: roomName
|
||||
});
|
||||
connection.sendIQ(
|
||||
iq,
|
||||
function (result) {
|
||||
var url = $(result).find('auth-url').attr('url');
|
||||
if (url) {
|
||||
console.info("Got auth url: " + url);
|
||||
urlCallback(url);
|
||||
} else {
|
||||
console.error(
|
||||
"Failed to get auth url fro mthe focus", result);
|
||||
}
|
||||
},
|
||||
function (error) {
|
||||
console.error("Get auth url error", error);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return my;
|
||||
}(Moderator || {}));
|
||||
|
||||
|
||||
|
||||
231
modules/API/API.js
Normal file
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* Implements API class that communicates with external api class
|
||||
* and provides interface to access Jitsi Meet features by external
|
||||
* applications that embed Jitsi Meet
|
||||
*/
|
||||
|
||||
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
|
||||
|
||||
/**
|
||||
* List of the available commands.
|
||||
* @type {{
|
||||
* displayName: inputDisplayNameHandler,
|
||||
* muteAudio: toggleAudio,
|
||||
* muteVideo: toggleVideo,
|
||||
* filmStrip: toggleFilmStrip
|
||||
* }}
|
||||
*/
|
||||
var commands =
|
||||
{
|
||||
displayName: APP.UI.inputDisplayNameHandler,
|
||||
muteAudio: APP.UI.toggleAudio,
|
||||
muteVideo: APP.UI.toggleVideo,
|
||||
toggleFilmStrip: APP.UI.toggleFilmStrip,
|
||||
toggleChat: APP.UI.toggleChat,
|
||||
toggleContactList: APP.UI.toggleContactList
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Maps the supported events and their status
|
||||
* (true it the event is enabled and false if it is disabled)
|
||||
* @type {{
|
||||
* incomingMessage: boolean,
|
||||
* outgoingMessage: boolean,
|
||||
* displayNameChange: boolean,
|
||||
* participantJoined: boolean,
|
||||
* participantLeft: boolean
|
||||
* }}
|
||||
*/
|
||||
var events =
|
||||
{
|
||||
incomingMessage: false,
|
||||
outgoingMessage:false,
|
||||
displayNameChange: false,
|
||||
participantJoined: false,
|
||||
participantLeft: false
|
||||
};
|
||||
|
||||
var displayName = {};
|
||||
|
||||
/**
|
||||
* Processes commands from external applicaiton.
|
||||
* @param message the object with the command
|
||||
*/
|
||||
function processCommand(message)
|
||||
{
|
||||
if(message.action != "execute")
|
||||
{
|
||||
console.error("Unknown action of the message");
|
||||
return;
|
||||
}
|
||||
for(var key in message)
|
||||
{
|
||||
if(commands[key])
|
||||
commands[key].apply(null, message[key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes events objects from external applications
|
||||
* @param event the event
|
||||
*/
|
||||
function processEvent(event) {
|
||||
if(!event.action)
|
||||
{
|
||||
console.error("Event with no action is received.");
|
||||
return;
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
switch(event.action)
|
||||
{
|
||||
case "add":
|
||||
for(; i < event.events.length; i++)
|
||||
{
|
||||
events[event.events[i]] = true;
|
||||
}
|
||||
break;
|
||||
case "remove":
|
||||
for(; i < event.events.length; i++)
|
||||
{
|
||||
events[event.events[i]] = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown action for event.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends message to the external application.
|
||||
* @param object
|
||||
*/
|
||||
function sendMessage(object) {
|
||||
window.parent.postMessage(JSON.stringify(object), "*");
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a message event from the external application
|
||||
* @param event the message event
|
||||
*/
|
||||
function processMessage(event)
|
||||
{
|
||||
var message;
|
||||
try {
|
||||
message = JSON.parse(event.data);
|
||||
} catch (e) {}
|
||||
|
||||
if(!message.type)
|
||||
return;
|
||||
switch (message.type)
|
||||
{
|
||||
case "command":
|
||||
processCommand(message);
|
||||
break;
|
||||
case "event":
|
||||
processEvent(message);
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown type of the message");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setupListeners() {
|
||||
APP.xmpp.addListener(XMPPEvents.MUC_ENTER, function (from) {
|
||||
API.triggerEvent("participantJoined", {jid: from});
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, function (from, nick, txt, myjid) {
|
||||
if (from != myjid)
|
||||
API.triggerEvent("incomingMessage",
|
||||
{"from": from, "nick": nick, "message": txt});
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.MUC_LEFT, function (jid) {
|
||||
API.triggerEvent("participantLeft", {jid: jid});
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, function (jid, newDisplayName) {
|
||||
name = displayName[jid];
|
||||
if(!name || name != newDisplayName) {
|
||||
API.triggerEvent("displayNameChange", {jid: jid, displayname: newDisplayName});
|
||||
displayName[jid] = newDisplayName;
|
||||
}
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.SENDING_CHAT_MESSAGE, function (body) {
|
||||
APP.API.triggerEvent("outgoingMessage", {"message": body});
|
||||
});
|
||||
}
|
||||
|
||||
var API = {
|
||||
/**
|
||||
* Check whether the API should be enabled or not.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isEnabled: function () {
|
||||
var hash = location.hash;
|
||||
if(hash && hash.indexOf("external") > -1 && window.postMessage)
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* Initializes the APIConnector. Setups message event listeners that will
|
||||
* receive information from external applications that embed Jitsi Meet.
|
||||
* It also sends a message to the external application that APIConnector
|
||||
* is initialized.
|
||||
*/
|
||||
init: function () {
|
||||
if (window.addEventListener)
|
||||
{
|
||||
window.addEventListener('message',
|
||||
processMessage, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.attachEvent('onmessage', processMessage);
|
||||
}
|
||||
sendMessage({type: "system", loaded: true});
|
||||
setupListeners();
|
||||
},
|
||||
/**
|
||||
* Checks whether the event is enabled ot not.
|
||||
* @param name the name of the event.
|
||||
* @returns {*}
|
||||
*/
|
||||
isEventEnabled: function (name) {
|
||||
return events[name];
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends event object to the external application that has been subscribed
|
||||
* for that event.
|
||||
* @param name the name event
|
||||
* @param object data associated with the event
|
||||
*/
|
||||
triggerEvent: function (name, object) {
|
||||
if(this.isEnabled() && this.isEventEnabled(name))
|
||||
sendMessage({
|
||||
type: "event", action: "result", event: name, result: object});
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the listeners.
|
||||
*/
|
||||
dispose: function () {
|
||||
if(window.removeEventListener)
|
||||
{
|
||||
window.removeEventListener("message",
|
||||
processMessage, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.detachEvent('onmessage', processMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
module.exports = API;
|
||||
@@ -1,8 +1,12 @@
|
||||
/* global connection, Strophe, updateLargeVideo, focusedVideoSrc*/
|
||||
/* global Strophe, updateLargeVideo, focusedVideoSrc*/
|
||||
|
||||
// cache datachannels to avoid garbage collection
|
||||
// https://code.google.com/p/chromium/issues/detail?id=405545
|
||||
var RTCEvents = require("../../service/RTC/RTCEvents");
|
||||
|
||||
var _dataChannels = [];
|
||||
var eventEmitter = null;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -32,7 +36,7 @@ var DataChannels =
|
||||
// selections so that it can do adaptive simulcast,
|
||||
// we want the notification to trigger even if userJid is undefined,
|
||||
// or null.
|
||||
var userJid = VideoLayout.getLargeVideoState().userJid;
|
||||
var userJid = APP.UI.getLargeVideoState().userResourceJid;
|
||||
// we want the notification to trigger even if userJid is undefined,
|
||||
// or null.
|
||||
onSelectedEndpointChanged(userJid);
|
||||
@@ -66,9 +70,7 @@ var DataChannels =
|
||||
console.info(
|
||||
"Data channel new dominant speaker event: ",
|
||||
dominantSpeakerEndpoint);
|
||||
$(document).trigger(
|
||||
'dominantspeakerchanged',
|
||||
[dominantSpeakerEndpoint]);
|
||||
eventEmitter.emit(RTCEvents.DOMINANTSPEAKER_CHANGED, dominantSpeakerEndpoint);
|
||||
}
|
||||
else if ("InLastNChangeEvent" === colibriClass)
|
||||
{
|
||||
@@ -91,7 +93,8 @@ var DataChannels =
|
||||
newValue = new Boolean(newValue).valueOf();
|
||||
}
|
||||
}
|
||||
$(document).trigger('inlastnchanged', [oldValue, newValue]);
|
||||
|
||||
eventEmitter.emit(RTCEvents.LASTN_CHANGED, oldValue, newValue);
|
||||
}
|
||||
else if ("LastNEndpointsChangeEvent" === colibriClass)
|
||||
{
|
||||
@@ -106,29 +109,26 @@ var DataChannels =
|
||||
console.log(
|
||||
"Data channel new last-n event: ",
|
||||
lastNEndpoints, endpointsEnteringLastN, obj);
|
||||
$(document).trigger(
|
||||
'lastnchanged',
|
||||
[lastNEndpoints, endpointsEnteringLastN, stream]);
|
||||
eventEmitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED,
|
||||
lastNEndpoints, endpointsEnteringLastN, obj);
|
||||
}
|
||||
else if ("SimulcastLayersChangedEvent" === colibriClass)
|
||||
{
|
||||
$(document).trigger(
|
||||
'simulcastlayerschanged',
|
||||
[obj.endpointSimulcastLayers]);
|
||||
eventEmitter.emit(RTCEvents.SIMULCAST_LAYER_CHANGED,
|
||||
obj.endpointSimulcastLayers);
|
||||
}
|
||||
else if ("SimulcastLayersChangingEvent" === colibriClass)
|
||||
{
|
||||
$(document).trigger(
|
||||
'simulcastlayerschanging',
|
||||
[obj.endpointSimulcastLayers]);
|
||||
eventEmitter.emit(RTCEvents.SIMULCAST_LAYER_CHANGING,
|
||||
obj.endpointSimulcastLayers);
|
||||
}
|
||||
else if ("StartSimulcastLayerEvent" === colibriClass)
|
||||
{
|
||||
$(document).trigger('startsimulcastlayer', obj.simulcastLayer);
|
||||
eventEmitter.emit(RTCEvents.SIMULCAST_START, obj.simulcastLayer);
|
||||
}
|
||||
else if ("StopSimulcastLayerEvent" === colibriClass)
|
||||
{
|
||||
$(document).trigger('stopsimulcastlayer', obj.simulcastLayer);
|
||||
eventEmitter.emit(RTCEvents.SIMULCAST_STOP, obj.simulcastLayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -151,11 +151,12 @@ var DataChannels =
|
||||
* Binds "ondatachannel" event listener to given PeerConnection instance.
|
||||
* @param peerConnection WebRTC peer connection instance.
|
||||
*/
|
||||
bindDataChannelListener: function (peerConnection) {
|
||||
init: function (peerConnection, emitter) {
|
||||
if(!config.openSctp)
|
||||
retrun;
|
||||
return;
|
||||
|
||||
peerConnection.ondatachannel = this.onDataChannel;
|
||||
eventEmitter = emitter;
|
||||
|
||||
// Sample code for opening new data channel from Jitsi Meet to the bridge.
|
||||
// Although it's not a requirement to open separate channels from both bridge
|
||||
@@ -179,22 +180,27 @@ var DataChannels =
|
||||
var msgData = event.data;
|
||||
console.info("Got My Data Channel Message:", msgData, dataChannel);
|
||||
};*/
|
||||
}
|
||||
},
|
||||
handleSelectedEndpointEvent: onSelectedEndpointChanged,
|
||||
handlePinnedEndpointEvent: onPinnedEndpointChanged
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
function onSelectedEndpointChanged(userJid)
|
||||
function onSelectedEndpointChanged(userResource)
|
||||
{
|
||||
console.log('selected endpoint changed: ', userJid);
|
||||
console.log('selected endpoint changed: ', userResource);
|
||||
if (_dataChannels && _dataChannels.length != 0)
|
||||
{
|
||||
_dataChannels.some(function (dataChannel) {
|
||||
if (dataChannel.readyState == 'open')
|
||||
{
|
||||
console.log('sending selected endpoint changed '
|
||||
+ 'notification to the bridge: ', userResource);
|
||||
dataChannel.send(JSON.stringify({
|
||||
'colibriClass': 'SelectedEndpointChangedEvent',
|
||||
'selectedEndpoint': (!userJid || userJid == null)
|
||||
? null : userJid
|
||||
'selectedEndpoint':
|
||||
(!userResource || userResource === null)?
|
||||
null : userResource
|
||||
}));
|
||||
|
||||
return true;
|
||||
@@ -203,13 +209,9 @@ function onSelectedEndpointChanged(userJid)
|
||||
}
|
||||
}
|
||||
|
||||
$(document).bind("selectedendpointchanged", function(event, userJid) {
|
||||
onSelectedEndpointChanged(userJid);
|
||||
});
|
||||
|
||||
function onPinnedEndpointChanged(userJid)
|
||||
function onPinnedEndpointChanged(userResource)
|
||||
{
|
||||
console.log('pinned endpoint changed: ', userJid);
|
||||
console.log('pinned endpoint changed: ', userResource);
|
||||
if (_dataChannels && _dataChannels.length != 0)
|
||||
{
|
||||
_dataChannels.some(function (dataChannel) {
|
||||
@@ -217,8 +219,9 @@ function onPinnedEndpointChanged(userJid)
|
||||
{
|
||||
dataChannel.send(JSON.stringify({
|
||||
'colibriClass': 'PinnedEndpointChangedEvent',
|
||||
'pinnedEndpoint': (!userJid || userJid == null)
|
||||
? null : Strophe.getResourceFromJid(userJid)
|
||||
'pinnedEndpoint':
|
||||
(!userResource || userResource == null)?
|
||||
null : userResource
|
||||
}));
|
||||
|
||||
return true;
|
||||
@@ -227,9 +230,5 @@ function onPinnedEndpointChanged(userJid)
|
||||
}
|
||||
}
|
||||
|
||||
$(document).bind("pinnedendpointchanged", function(event, userJid) {
|
||||
onPinnedEndpointChanged(userJid);
|
||||
});
|
||||
|
||||
module.exports = DataChannels;
|
||||
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
//var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
|
||||
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
|
||||
|
||||
function LocalStream(stream, type, eventEmitter)
|
||||
function LocalStream(stream, type, eventEmitter, videoType)
|
||||
{
|
||||
this.stream = stream;
|
||||
this.eventEmitter = eventEmitter;
|
||||
this.type = type;
|
||||
|
||||
this.videoType = videoType;
|
||||
var self = this;
|
||||
if(type == "audio")
|
||||
{
|
||||
this.getTracks = function () {
|
||||
return self.stream.getAudioTracks();
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
this.getTracks = function () {
|
||||
return self.stream.getVideoTracks();
|
||||
};
|
||||
}
|
||||
|
||||
this.stream.onended = function()
|
||||
{
|
||||
self.streamEnded();
|
||||
@@ -24,31 +37,32 @@ LocalStream.prototype.getOriginalStream = function()
|
||||
|
||||
LocalStream.prototype.isAudioStream = function () {
|
||||
return (this.stream.getAudioTracks() && this.stream.getAudioTracks().length > 0);
|
||||
}
|
||||
};
|
||||
|
||||
LocalStream.prototype.mute = function()
|
||||
{
|
||||
var ismuted = false;
|
||||
var tracks = [];
|
||||
if(this.type = "audio")
|
||||
{
|
||||
tracks = this.stream.getAudioTracks();
|
||||
}
|
||||
else
|
||||
{
|
||||
tracks = this.stream.getVideoTracks();
|
||||
}
|
||||
var tracks = this.getTracks();
|
||||
|
||||
for (var idx = 0; idx < tracks.length; idx++) {
|
||||
ismuted = !tracks[idx].enabled;
|
||||
tracks[idx].enabled = !tracks[idx].enabled;
|
||||
tracks[idx].enabled = ismuted;
|
||||
}
|
||||
return ismuted;
|
||||
}
|
||||
};
|
||||
|
||||
LocalStream.prototype.setMute = function(mute)
|
||||
{
|
||||
var tracks = this.getTracks();
|
||||
|
||||
for (var idx = 0; idx < tracks.length; idx++) {
|
||||
tracks[idx].enabled = mute;
|
||||
}
|
||||
};
|
||||
|
||||
LocalStream.prototype.isMuted = function () {
|
||||
var tracks = [];
|
||||
if(this.type = "audio")
|
||||
if(this.type == "audio")
|
||||
{
|
||||
tracks = this.stream.getAudioTracks();
|
||||
}
|
||||
@@ -63,4 +77,10 @@ LocalStream.prototype.isMuted = function () {
|
||||
return true;
|
||||
}
|
||||
|
||||
LocalStream.prototype.getId = function () {
|
||||
return this.stream.getTracks()[0].id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = LocalStream;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
////These lines should be uncommented when require works in app.js
|
||||
//var RTCBrowserType = require("../../service/RTC/RTCBrowserType.js");
|
||||
//var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
|
||||
//var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
|
||||
var RTCBrowserType = require("../../service/RTC/RTCBrowserType.js");
|
||||
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
|
||||
|
||||
/**
|
||||
* Creates a MediaStream object for the given data, session id and ssrc.
|
||||
@@ -14,7 +13,7 @@
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function MediaStream(data, sid, ssrc, eventEmmiter, browser) {
|
||||
function MediaStream(data, sid, ssrc, browser) {
|
||||
|
||||
// XXX(gp) to minimize headaches in the future, we should build our
|
||||
// abstractions around tracks and not streams. ORTC is track based API.
|
||||
@@ -32,15 +31,8 @@ function MediaStream(data, sid, ssrc, eventEmmiter, browser) {
|
||||
this.ssrc = ssrc;
|
||||
this.type = (this.stream.getVideoTracks().length > 0)?
|
||||
MediaStreamType.VIDEO_TYPE : MediaStreamType.AUDIO_TYPE;
|
||||
this.videoType = null;
|
||||
this.muted = false;
|
||||
eventEmmiter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, this);
|
||||
if(browser == RTCBrowserType.RTC_BROWSER_FIREFOX)
|
||||
{
|
||||
if (!this.getVideoTracks)
|
||||
this.getVideoTracks = function () { return []; };
|
||||
if (!this.getAudioTracks)
|
||||
this.getAudioTracks = function () { return []; };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,9 +3,12 @@ var RTCUtils = require("./RTCUtils.js");
|
||||
var LocalStream = require("./LocalStream.js");
|
||||
var DataChannels = require("./DataChannels");
|
||||
var MediaStream = require("./MediaStream.js");
|
||||
//These lines should be uncommented when require works in app.js
|
||||
//var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
|
||||
//var XMPPEvents = require("../service/xmpp/XMPPEvents");
|
||||
var DesktopSharingEventTypes
|
||||
= require("../../service/desktopsharing/DesktopSharingEventTypes");
|
||||
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
|
||||
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
|
||||
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
|
||||
var UIEvents = require("../../service/UI/UIEvents");
|
||||
|
||||
var eventEmitter = new EventEmitter();
|
||||
|
||||
@@ -18,16 +21,22 @@ var RTC = {
|
||||
addStreamListener: function (listener, eventType) {
|
||||
eventEmitter.on(eventType, listener);
|
||||
},
|
||||
addListener: function (type, listener) {
|
||||
eventEmitter.on(type, listener);
|
||||
},
|
||||
removeStreamListener: function (listener, eventType) {
|
||||
if(!(eventType instanceof StreamEventTypes))
|
||||
throw "Illegal argument";
|
||||
|
||||
eventEmitter.removeListener(eventType, listener);
|
||||
},
|
||||
createLocalStream: function (stream, type) {
|
||||
createLocalStream: function (stream, type, change) {
|
||||
|
||||
var localStream = new LocalStream(stream, type, eventEmitter);
|
||||
this.localStreams.push(localStream);
|
||||
//in firefox we have only one stream object
|
||||
if(this.localStreams.length == 0 ||
|
||||
this.localStreams[0].getOriginalStream() != stream)
|
||||
this.localStreams.push(localStream);
|
||||
if(type == "audio")
|
||||
{
|
||||
this.localAudio = localStream;
|
||||
@@ -36,8 +45,11 @@ var RTC = {
|
||||
{
|
||||
this.localVideo = localStream;
|
||||
}
|
||||
eventEmitter.emit(StreamEventTypes.EVENT_TYPE_LOCAL_CREATED,
|
||||
localStream);
|
||||
var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED;
|
||||
if(change)
|
||||
eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED;
|
||||
|
||||
eventEmitter.emit(eventType, localStream);
|
||||
return localStream;
|
||||
},
|
||||
removeLocalStream: function (stream) {
|
||||
@@ -50,13 +62,15 @@ var RTC = {
|
||||
}
|
||||
},
|
||||
createRemoteStream: function (data, sid, thessrc) {
|
||||
var remoteStream = new MediaStream(data, sid, thessrc, eventEmitter,
|
||||
var remoteStream = new MediaStream(data, sid, thessrc,
|
||||
this.getBrowserType());
|
||||
var jid = data.peerjid || connection.emuc.myroomjid;
|
||||
var jid = data.peerjid || APP.xmpp.myJid();
|
||||
if(!this.remoteStreams[jid]) {
|
||||
this.remoteStreams[jid] = {};
|
||||
}
|
||||
this.remoteStreams[jid][remoteStream.type]= remoteStream;
|
||||
eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, remoteStream);
|
||||
console.debug("ADD remote stream ", remoteStream.type, " ", jid, " ", thessrc);
|
||||
return remoteStream;
|
||||
},
|
||||
getBrowserType: function () {
|
||||
@@ -93,12 +107,35 @@ var RTC = {
|
||||
this.dispose();
|
||||
},
|
||||
start: function () {
|
||||
var self = this;
|
||||
APP.desktopsharing.addListener(
|
||||
function (stream, isUsingScreenStream, callback) {
|
||||
self.changeLocalVideo(stream, isUsingScreenStream, callback);
|
||||
}, DesktopSharingEventTypes.NEW_STREAM_CREATED);
|
||||
APP.xmpp.addListener(XMPPEvents.CHANGED_STREAMS, function (jid, changedStreams) {
|
||||
for(var i = 0; i < changedStreams.length; i++) {
|
||||
var type = changedStreams[i].type;
|
||||
if (type != "audio") {
|
||||
var peerStreams = self.remoteStreams[jid];
|
||||
if(!peerStreams)
|
||||
continue;
|
||||
var videoStream = peerStreams[MediaStreamType.VIDEO_TYPE];
|
||||
if(!videoStream)
|
||||
continue;
|
||||
videoStream.videoType = changedStreams[i].type;
|
||||
}
|
||||
}
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function(event) {
|
||||
DataChannels.init(event.peerconnection, eventEmitter);
|
||||
});
|
||||
APP.UI.addListener(UIEvents.SELECTED_ENDPOINT,
|
||||
DataChannels.handleSelectedEndpointEvent);
|
||||
APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
|
||||
DataChannels.handlePinnedEndpointEvent);
|
||||
this.rtcUtils = new RTCUtils(this);
|
||||
this.rtcUtils.obtainAudioAndVideoPermissions();
|
||||
},
|
||||
onConferenceCreated: function(event) {
|
||||
DataChannels.bindDataChannelListener(event.peerconnection);
|
||||
},
|
||||
muteRemoteVideoStream: function (jid, value) {
|
||||
var stream;
|
||||
|
||||
@@ -111,14 +148,57 @@ var RTC = {
|
||||
if(!stream)
|
||||
return false;
|
||||
|
||||
var isMuted = (value === "true");
|
||||
if (isMuted != stream.muted) {
|
||||
stream.setMute(isMuted);
|
||||
if (value != stream.muted) {
|
||||
stream.setMute(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
switchVideoStreams: function (new_stream) {
|
||||
this.localVideo.stream = new_stream;
|
||||
|
||||
this.localStreams = [];
|
||||
|
||||
//in firefox we have only one stream object
|
||||
if (this.localAudio.getOriginalStream() != new_stream)
|
||||
this.localStreams.push(this.localAudio);
|
||||
this.localStreams.push(this.localVideo);
|
||||
},
|
||||
changeLocalVideo: function (stream, isUsingScreenStream, callback) {
|
||||
var oldStream = this.localVideo.getOriginalStream();
|
||||
var type = (isUsingScreenStream? "screen" : "video");
|
||||
this.localVideo = this.createLocalStream(stream, "video", true, type);
|
||||
// Stop the stream to trigger onended event for old stream
|
||||
oldStream.stop();
|
||||
APP.xmpp.switchStreams(stream, oldStream,callback);
|
||||
},
|
||||
/**
|
||||
* Checks if video identified by given src is desktop stream.
|
||||
* @param videoSrc eg.
|
||||
* blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isVideoSrcDesktop: function (jid) {
|
||||
if(!jid)
|
||||
return false;
|
||||
var isDesktop = false;
|
||||
var stream = null;
|
||||
if (APP.xmpp.myJid() &&
|
||||
APP.xmpp.myResource() === jid) {
|
||||
// local video
|
||||
stream = this.localVideo;
|
||||
} else {
|
||||
var peerStreams = this.remoteStreams[jid];
|
||||
if(!peerStreams)
|
||||
return false;
|
||||
stream = peerStreams[MediaStreamType.VIDEO_TYPE];
|
||||
}
|
||||
|
||||
if(stream)
|
||||
isDesktop = (stream.videoType === "screen");
|
||||
|
||||
return isDesktop;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = RTC;
|
||||
|
||||
@@ -1,61 +1,52 @@
|
||||
//This should be uncommented when app.js supports require
|
||||
//var RTCBrowserType = require("../../service/RTC/RTCBrowserType.js");
|
||||
var RTCBrowserType = require("../../service/RTC/RTCBrowserType.js");
|
||||
var Resolutions = require("../../service/RTC/Resolutions");
|
||||
|
||||
var currentResolution = null;
|
||||
|
||||
function getPreviousResolution(resolution) {
|
||||
if(!Resolutions[resolution])
|
||||
return null;
|
||||
var order = Resolutions[resolution].order;
|
||||
var res = null;
|
||||
var resName = null;
|
||||
for(var i in Resolutions)
|
||||
{
|
||||
var tmp = Resolutions[i];
|
||||
if(res == null || (res.order < tmp.order && tmp.order < order))
|
||||
{
|
||||
resName = i;
|
||||
res = tmp;
|
||||
}
|
||||
}
|
||||
return resName;
|
||||
}
|
||||
|
||||
function setResolutionConstraints(constraints, resolution, isAndroid)
|
||||
{
|
||||
if (resolution && !constraints.video || isAndroid) {
|
||||
constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
|
||||
}
|
||||
// see https://code.google.com/p/chromium/issues/detail?id=143631#c9 for list of supported resolutions
|
||||
switch (resolution) {
|
||||
// 16:9 first
|
||||
case '1080':
|
||||
case 'fullhd':
|
||||
constraints.video.mandatory.minWidth = 1920;
|
||||
constraints.video.mandatory.minHeight = 1080;
|
||||
break;
|
||||
case '720':
|
||||
case 'hd':
|
||||
constraints.video.mandatory.minWidth = 1280;
|
||||
constraints.video.mandatory.minHeight = 720;
|
||||
break;
|
||||
case '360':
|
||||
constraints.video.mandatory.minWidth = 640;
|
||||
constraints.video.mandatory.minHeight = 360;
|
||||
break;
|
||||
case '180':
|
||||
constraints.video.mandatory.minWidth = 320;
|
||||
constraints.video.mandatory.minHeight = 180;
|
||||
break;
|
||||
// 4:3
|
||||
case '960':
|
||||
constraints.video.mandatory.minWidth = 960;
|
||||
constraints.video.mandatory.minHeight = 720;
|
||||
break;
|
||||
case '640':
|
||||
case 'vga':
|
||||
constraints.video.mandatory.minWidth = 640;
|
||||
constraints.video.mandatory.minHeight = 480;
|
||||
break;
|
||||
case '320':
|
||||
|
||||
if(Resolutions[resolution])
|
||||
{
|
||||
constraints.video.mandatory.minWidth = Resolutions[resolution].width;
|
||||
constraints.video.mandatory.minHeight = Resolutions[resolution].height;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isAndroid) {
|
||||
constraints.video.mandatory.minWidth = 320;
|
||||
constraints.video.mandatory.minHeight = 240;
|
||||
break;
|
||||
default:
|
||||
if (isAndroid) {
|
||||
constraints.video.mandatory.minWidth = 320;
|
||||
constraints.video.mandatory.minHeight = 240;
|
||||
constraints.video.mandatory.maxFrameRate = 15;
|
||||
}
|
||||
break;
|
||||
constraints.video.mandatory.maxFrameRate = 15;
|
||||
}
|
||||
}
|
||||
|
||||
if (constraints.video.mandatory.minWidth)
|
||||
constraints.video.mandatory.maxWidth = constraints.video.mandatory.minWidth;
|
||||
if (constraints.video.mandatory.minHeight)
|
||||
constraints.video.mandatory.maxHeight = constraints.video.mandatory.minHeight;
|
||||
}
|
||||
|
||||
|
||||
function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid)
|
||||
{
|
||||
var constraints = {audio: false, video: false};
|
||||
@@ -115,7 +106,9 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid
|
||||
}
|
||||
}
|
||||
|
||||
setResolutionConstraints(constraints, resolution, isAndroid);
|
||||
if (um.indexOf('video') >= 0) {
|
||||
setResolutionConstraints(constraints, resolution, isAndroid);
|
||||
}
|
||||
|
||||
if (bandwidth) { // doesn't work currently, see webrtc issue 1846
|
||||
if (!constraints.video) constraints.video = {mandatory: {}, optional: []};//same behaviour as true
|
||||
@@ -143,6 +136,13 @@ function RTCUtils(RTCService)
|
||||
this.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
|
||||
this.pc_constraints = {};
|
||||
this.attachMediaStream = function (element, stream) {
|
||||
// srcObject is being standardized and FF will eventually
|
||||
// support that unprefixed. FF also supports the
|
||||
// "element.src = URL.createObjectURL(...)" combo, but that
|
||||
// will be deprecated in favour of srcObject.
|
||||
//
|
||||
// https://groups.google.com/forum/#!topic/mozilla.dev.media/pKOiioXonJg
|
||||
// https://github.com/webrtc/samples/issues/302
|
||||
element[0].mozSrcObject = stream;
|
||||
element[0].play();
|
||||
};
|
||||
@@ -202,13 +202,13 @@ function RTCUtils(RTCService)
|
||||
{
|
||||
try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { }
|
||||
|
||||
window.location.href = 'webrtcrequired.html';
|
||||
window.location.href = 'unsupported_browser.html';
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.browser !== RTCBrowserType.RTC_BROWSER_CHROME &&
|
||||
config.enableFirefoxSupport !== true) {
|
||||
window.location.href = 'chromeonly.html';
|
||||
window.location.href = 'unsupported_browser.html';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -219,6 +219,7 @@ RTCUtils.prototype.getUserMediaWithConstraints = function(
|
||||
um, success_callback, failure_callback, resolution,bandwidth, fps,
|
||||
desktopStream)
|
||||
{
|
||||
currentResolution = resolution;
|
||||
// Check if we are running on Android device
|
||||
var isAndroid = navigator.userAgent.indexOf('Android') != -1;
|
||||
|
||||
@@ -236,7 +237,7 @@ RTCUtils.prototype.getUserMediaWithConstraints = function(
|
||||
|
||||
// We currently do not support FF, as it doesn't have multistream support.
|
||||
&& !isFF) {
|
||||
simulcast.getUserMedia(constraints, function (stream) {
|
||||
APP.simulcast.getUserMedia(constraints, function (stream) {
|
||||
console.log('onUserMediaSuccess');
|
||||
success_callback(stream);
|
||||
},
|
||||
@@ -277,36 +278,58 @@ RTCUtils.prototype.getUserMediaWithConstraints = function(
|
||||
RTCUtils.prototype.obtainAudioAndVideoPermissions = function() {
|
||||
var self = this;
|
||||
// Get AV
|
||||
var cb = function (stream) {
|
||||
console.log('got', stream, stream.getAudioTracks().length, stream.getVideoTracks().length);
|
||||
self.handleLocalStream(stream);
|
||||
trackUsage('localMedia', {
|
||||
audio: stream.getAudioTracks().length,
|
||||
video: stream.getVideoTracks().length
|
||||
});
|
||||
};
|
||||
var self = this;
|
||||
|
||||
this.getUserMediaWithConstraints(
|
||||
['audio', 'video'],
|
||||
cb,
|
||||
function (error) {
|
||||
console.error('failed to obtain audio/video stream - trying audio only', error);
|
||||
self.getUserMediaWithConstraints(
|
||||
['audio'],
|
||||
cb,
|
||||
function (error) {
|
||||
console.error('failed to obtain audio/video stream - stop', error);
|
||||
trackUsage('localMediaError', {
|
||||
media: error.media || 'video',
|
||||
name : error.name
|
||||
});
|
||||
messageHandler.showError("Error",
|
||||
"Failed to obtain permissions to use the local microphone" +
|
||||
"and/or camera.");
|
||||
}
|
||||
);
|
||||
function (stream) {
|
||||
self.successCallback(stream);
|
||||
},
|
||||
config.resolution || '360');
|
||||
function (error) {
|
||||
self.errorCallback(error);
|
||||
},
|
||||
config.resolution || '360');
|
||||
}
|
||||
|
||||
RTCUtils.prototype.successCallback = function (stream) {
|
||||
console.log('got', stream, stream.getAudioTracks().length,
|
||||
stream.getVideoTracks().length);
|
||||
this.handleLocalStream(stream);
|
||||
};
|
||||
|
||||
RTCUtils.prototype.errorCallback = function (error) {
|
||||
var self = this;
|
||||
console.error('failed to obtain audio/video stream - trying audio only', error);
|
||||
var resolution = getPreviousResolution(currentResolution);
|
||||
if(typeof error == "object" && error.constraintName && error.name
|
||||
&& (error.name == "ConstraintNotSatisfiedError" ||
|
||||
error.name == "OverconstrainedError") &&
|
||||
(error.constraintName == "minWidth" || error.constraintName == "maxWidth" ||
|
||||
error.constraintName == "minHeight" || error.constraintName == "maxHeight")
|
||||
&& resolution != null)
|
||||
{
|
||||
self.getUserMediaWithConstraints(['audio', 'video'],
|
||||
function (stream) {
|
||||
return self.successCallback(stream);
|
||||
}, function (error) {
|
||||
return self.errorCallback(error);
|
||||
}, resolution);
|
||||
}
|
||||
else
|
||||
{
|
||||
self.getUserMediaWithConstraints(
|
||||
['audio'],
|
||||
function (stream) {
|
||||
return self.successCallback(stream);
|
||||
},
|
||||
function (error) {
|
||||
console.error('failed to obtain audio/video stream - stop',
|
||||
error);
|
||||
APP.UI.messageHandler.showError("dialog.error",
|
||||
"dialog.failedpermissions");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RTCUtils.prototype.handleLocalStream = function(stream)
|
||||
@@ -339,4 +362,4 @@ RTCUtils.prototype.handleLocalStream = function(stream)
|
||||
|
||||
|
||||
|
||||
module.exports = RTCUtils;
|
||||
module.exports = RTCUtils;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "RTC",
|
||||
"version": "0.0.1",
|
||||
"main": "RTC.js",
|
||||
"description": "Provedes media streams and data channels utilities for Jitsi Meet",
|
||||
"dependencies": {
|
||||
}
|
||||
}
|
||||
761
modules/UI/UI.js
Normal file
@@ -0,0 +1,761 @@
|
||||
var UI = {};
|
||||
|
||||
var VideoLayout = require("./videolayout/VideoLayout.js");
|
||||
var AudioLevels = require("./audio_levels/AudioLevels.js");
|
||||
var Prezi = require("./prezi/Prezi.js");
|
||||
var Etherpad = require("./etherpad/Etherpad.js");
|
||||
var Chat = require("./side_pannels/chat/Chat.js");
|
||||
var Toolbar = require("./toolbars/Toolbar");
|
||||
var ToolbarToggler = require("./toolbars/ToolbarToggler");
|
||||
var BottomToolbar = require("./toolbars/BottomToolbar");
|
||||
var ContactList = require("./side_pannels/contactlist/ContactList");
|
||||
var Avatar = require("./avatar/Avatar");
|
||||
var EventEmitter = require("events");
|
||||
var SettingsMenu = require("./side_pannels/settings/SettingsMenu");
|
||||
var Settings = require("./../settings/Settings");
|
||||
var PanelToggler = require("./side_pannels/SidePanelToggler");
|
||||
var RoomNameGenerator = require("./welcome_page/RoomnameGenerator");
|
||||
UI.messageHandler = require("./util/MessageHandler");
|
||||
var messageHandler = UI.messageHandler;
|
||||
var Authentication = require("./authentication/Authentication");
|
||||
var UIUtil = require("./util/UIUtil");
|
||||
var NicknameHandler = require("./util/NicknameHandler");
|
||||
var CQEvents = require("../../service/connectionquality/CQEvents");
|
||||
var DesktopSharingEventTypes
|
||||
= require("../../service/desktopsharing/DesktopSharingEventTypes");
|
||||
var RTCEvents = require("../../service/RTC/RTCEvents");
|
||||
var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
|
||||
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
|
||||
|
||||
var eventEmitter = new EventEmitter();
|
||||
var roomName = null;
|
||||
|
||||
|
||||
function setupPrezi()
|
||||
{
|
||||
$("#reloadPresentationLink").click(function()
|
||||
{
|
||||
Prezi.reloadPresentation();
|
||||
});
|
||||
}
|
||||
|
||||
function setupChat()
|
||||
{
|
||||
Chat.init();
|
||||
$("#toggle_smileys").click(function() {
|
||||
Chat.toggleSmileys();
|
||||
});
|
||||
}
|
||||
|
||||
function setupToolbars() {
|
||||
Toolbar.init(UI);
|
||||
Toolbar.setupButtonsFromConfig();
|
||||
BottomToolbar.init();
|
||||
}
|
||||
|
||||
function streamHandler(stream) {
|
||||
switch (stream.type)
|
||||
{
|
||||
case "audio":
|
||||
VideoLayout.changeLocalAudio(stream);
|
||||
break;
|
||||
case "video":
|
||||
VideoLayout.changeLocalVideo(stream);
|
||||
break;
|
||||
case "stream":
|
||||
VideoLayout.changeLocalStream(stream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function onDisposeConference(unload) {
|
||||
Toolbar.showAuthenticateButton(false);
|
||||
};
|
||||
|
||||
function onDisplayNameChanged(jid, displayName) {
|
||||
ContactList.onDisplayNameChange(jid, displayName);
|
||||
SettingsMenu.onDisplayNameChange(jid, displayName);
|
||||
VideoLayout.onDisplayNameChanged(jid, displayName);
|
||||
}
|
||||
|
||||
function registerListeners() {
|
||||
APP.RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
|
||||
|
||||
APP.RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED);
|
||||
APP.RTC.addStreamListener(function (stream) {
|
||||
VideoLayout.onRemoteStreamAdded(stream);
|
||||
}, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED);
|
||||
APP.RTC.addListener(RTCEvents.LASTN_CHANGED, onLastNChanged);
|
||||
APP.RTC.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED, function (resourceJid) {
|
||||
VideoLayout.onDominantSpeakerChanged(resourceJid);
|
||||
});
|
||||
APP.RTC.addListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
|
||||
function (lastNEndpoints, endpointsEnteringLastN, stream) {
|
||||
VideoLayout.onLastNEndpointsChanged(lastNEndpoints,
|
||||
endpointsEnteringLastN, stream);
|
||||
});
|
||||
APP.RTC.addListener(RTCEvents.SIMULCAST_LAYER_CHANGED,
|
||||
function (endpointSimulcastLayers) {
|
||||
VideoLayout.onSimulcastLayersChanged(endpointSimulcastLayers);
|
||||
});
|
||||
APP.RTC.addListener(RTCEvents.SIMULCAST_LAYER_CHANGING,
|
||||
function (endpointSimulcastLayers) {
|
||||
VideoLayout.onSimulcastLayersChanging(endpointSimulcastLayers);
|
||||
});
|
||||
|
||||
APP.statistics.addAudioLevelListener(function(jid, audioLevel)
|
||||
{
|
||||
var resourceJid;
|
||||
if(jid === APP.statistics.LOCAL_JID)
|
||||
{
|
||||
resourceJid = AudioLevels.LOCAL_LEVEL;
|
||||
if(APP.RTC.localAudio.isMuted())
|
||||
{
|
||||
audioLevel = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
resourceJid = Strophe.getResourceFromJid(jid);
|
||||
}
|
||||
|
||||
AudioLevels.updateAudioLevel(resourceJid, audioLevel,
|
||||
UI.getLargeVideoState().userResourceJid);
|
||||
});
|
||||
APP.desktopsharing.addListener(function () {
|
||||
ToolbarToggler.showDesktopSharingButton();
|
||||
}, DesktopSharingEventTypes.INIT);
|
||||
APP.desktopsharing.addListener(
|
||||
Toolbar.changeDesktopSharingButtonState,
|
||||
DesktopSharingEventTypes.SWITCHING_DONE);
|
||||
APP.connectionquality.addListener(CQEvents.LOCALSTATS_UPDATED,
|
||||
VideoLayout.updateLocalConnectionStats);
|
||||
APP.connectionquality.addListener(CQEvents.REMOTESTATS_UPDATED,
|
||||
VideoLayout.updateConnectionStats);
|
||||
APP.connectionquality.addListener(CQEvents.STOP,
|
||||
VideoLayout.onStatsStop);
|
||||
APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
|
||||
APP.xmpp.addListener(XMPPEvents.GRACEFUL_SHUTDOWN, function () {
|
||||
messageHandler.openMessageDialog(
|
||||
'dialog.serviceUnavailable',
|
||||
'dialog.gracefulShutdown'
|
||||
);
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.RESERVATION_ERROR, function (code, msg) {
|
||||
var title = APP.translation.generateTranslatonHTML(
|
||||
"dialog.reservationError");
|
||||
var message = APP.translation.generateTranslatonHTML(
|
||||
"dialog.reservationErrorMsg", {code: code, msg: msg});
|
||||
messageHandler.openDialog(
|
||||
title,
|
||||
message,
|
||||
true, {},
|
||||
function (event, value, message, formVals)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
);
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.KICKED, function () {
|
||||
messageHandler.openMessageDialog("dialog.sessTerminated",
|
||||
"dialog.kickMessage");
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.MUC_DESTROYED, function (reason) {
|
||||
//FIXME: use Session Terminated from translation, but
|
||||
// 'reason' text comes from XMPP packet and is not translated
|
||||
var title = APP.translation.generateTranslatonHTML("dialog.sessTerminated");
|
||||
messageHandler.openDialog(
|
||||
title, reason, true, {},
|
||||
function (event, value, message, formVals)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
);
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.BRIDGE_DOWN, function () {
|
||||
messageHandler.showError("dialog.error",
|
||||
"dialog.bridgeUnavailable");
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.USER_ID_CHANGED, function (from, id) {
|
||||
Avatar.setUserAvatar(from, id);
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.CHANGED_STREAMS, function (jid, changedStreams) {
|
||||
for(stream in changedStreams)
|
||||
{
|
||||
// might need to update the direction if participant just went from sendrecv to recvonly
|
||||
if (stream.type === 'video' || stream.type === 'screen') {
|
||||
var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>video');
|
||||
switch (stream.direction) {
|
||||
case 'sendrecv':
|
||||
el.show();
|
||||
break;
|
||||
case 'recvonly':
|
||||
el.hide();
|
||||
// FIXME: Check if we have to change large video
|
||||
//VideoLayout.updateLargeVideo(el);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, onDisplayNameChanged);
|
||||
APP.xmpp.addListener(XMPPEvents.MUC_JOINED, onMucJoined);
|
||||
APP.xmpp.addListener(XMPPEvents.LOCALROLE_CHANGED, onLocalRoleChange);
|
||||
APP.xmpp.addListener(XMPPEvents.MUC_ENTER, onMucEntered);
|
||||
APP.xmpp.addListener(XMPPEvents.MUC_ROLE_CHANGED, onMucRoleChanged);
|
||||
APP.xmpp.addListener(XMPPEvents.PRESENCE_STATUS, onMucPresenceStatus);
|
||||
APP.xmpp.addListener(XMPPEvents.SUBJECT_CHANGED, chatSetSubject);
|
||||
APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation);
|
||||
APP.xmpp.addListener(XMPPEvents.MUC_LEFT, onMucLeft);
|
||||
APP.xmpp.addListener(XMPPEvents.PASSWORD_REQUIRED, onPasswordReqiured);
|
||||
APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError);
|
||||
APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad);
|
||||
APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED, onAuthenticationRequired);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mutes/unmutes the local video.
|
||||
*
|
||||
* @param mute <tt>true</tt> to mute the local video; otherwise, <tt>false</tt>
|
||||
* @param options an object which specifies optional arguments such as the
|
||||
* <tt>boolean</tt> key <tt>byUser</tt> with default value <tt>true</tt> which
|
||||
* specifies whether the method was initiated in response to a user command (in
|
||||
* contrast to an automatic decision taken by the application logic)
|
||||
*/
|
||||
function setVideoMute(mute, options) {
|
||||
APP.xmpp.setVideoMute(
|
||||
mute,
|
||||
function (mute) {
|
||||
var video = $('#video');
|
||||
var communicativeClass = "icon-camera";
|
||||
var muteClass = "icon-camera icon-camera-disabled";
|
||||
|
||||
if (mute) {
|
||||
video.removeClass(communicativeClass);
|
||||
video.addClass(muteClass);
|
||||
} else {
|
||||
video.removeClass(muteClass);
|
||||
video.addClass(communicativeClass);
|
||||
}
|
||||
},
|
||||
options);
|
||||
}
|
||||
|
||||
|
||||
function bindEvents()
|
||||
{
|
||||
/**
|
||||
* Resizes and repositions videos in full screen mode.
|
||||
*/
|
||||
$(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange',
|
||||
function () {
|
||||
VideoLayout.resizeLargeVideoContainer();
|
||||
VideoLayout.positionLarge();
|
||||
}
|
||||
);
|
||||
|
||||
$(window).resize(function () {
|
||||
VideoLayout.resizeLargeVideoContainer();
|
||||
VideoLayout.positionLarge();
|
||||
});
|
||||
}
|
||||
|
||||
UI.start = function (init) {
|
||||
document.title = interfaceConfig.APP_NAME;
|
||||
if(config.enableWelcomePage && window.location.pathname == "/" &&
|
||||
(!window.localStorage.welcomePageDisabled || window.localStorage.welcomePageDisabled == "false"))
|
||||
{
|
||||
$("#videoconference_page").hide();
|
||||
var setupWelcomePage = require("./welcome_page/WelcomePage");
|
||||
setupWelcomePage();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (interfaceConfig.SHOW_JITSI_WATERMARK) {
|
||||
var leftWatermarkDiv
|
||||
= $("#largeVideoContainer div[class='watermark leftwatermark']");
|
||||
|
||||
leftWatermarkDiv.css({display: 'block'});
|
||||
leftWatermarkDiv.parent().get(0).href
|
||||
= interfaceConfig.JITSI_WATERMARK_LINK;
|
||||
}
|
||||
|
||||
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
|
||||
var rightWatermarkDiv
|
||||
= $("#largeVideoContainer div[class='watermark rightwatermark']");
|
||||
|
||||
rightWatermarkDiv.css({display: 'block'});
|
||||
rightWatermarkDiv.parent().get(0).href
|
||||
= interfaceConfig.BRAND_WATERMARK_LINK;
|
||||
rightWatermarkDiv.get(0).style.backgroundImage
|
||||
= "url(images/rightwatermark.png)";
|
||||
}
|
||||
|
||||
if (interfaceConfig.SHOW_POWERED_BY) {
|
||||
$("#largeVideoContainer>a[class='poweredby']").css({display: 'block'});
|
||||
}
|
||||
|
||||
$("#welcome_page").hide();
|
||||
|
||||
VideoLayout.resizeLargeVideoContainer();
|
||||
$("#videospace").mousemove(function () {
|
||||
return ToolbarToggler.showToolbar();
|
||||
});
|
||||
// Set the defaults for prompt dialogs.
|
||||
jQuery.prompt.setDefaults({persistent: false});
|
||||
|
||||
VideoLayout.init(eventEmitter);
|
||||
AudioLevels.init();
|
||||
NicknameHandler.init(eventEmitter);
|
||||
registerListeners();
|
||||
bindEvents();
|
||||
setupPrezi();
|
||||
setupToolbars();
|
||||
setupChat();
|
||||
|
||||
|
||||
document.title = interfaceConfig.APP_NAME;
|
||||
|
||||
$("#downloadlog").click(function (event) {
|
||||
dump(event.target);
|
||||
});
|
||||
|
||||
if(config.enableWelcomePage && window.location.pathname == "/" &&
|
||||
(!window.localStorage.welcomePageDisabled || window.localStorage.welcomePageDisabled == "false"))
|
||||
{
|
||||
$("#videoconference_page").hide();
|
||||
var setupWelcomePage = require("./welcome_page/WelcomePage");
|
||||
setupWelcomePage();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$("#welcome_page").hide();
|
||||
|
||||
document.getElementById('largeVideo').volume = 0;
|
||||
|
||||
if (!$('#settings').is(':visible')) {
|
||||
console.log('init');
|
||||
init();
|
||||
} else {
|
||||
loginInfo.onsubmit = function (e) {
|
||||
if (e.preventDefault) e.preventDefault();
|
||||
$('#settings').hide();
|
||||
init();
|
||||
};
|
||||
}
|
||||
|
||||
toastr.options = {
|
||||
"closeButton": true,
|
||||
"debug": false,
|
||||
"positionClass": "notification-bottom-right",
|
||||
"onclick": null,
|
||||
"showDuration": "300",
|
||||
"hideDuration": "1000",
|
||||
"timeOut": "2000",
|
||||
"extendedTimeOut": "1000",
|
||||
"showEasing": "swing",
|
||||
"hideEasing": "linear",
|
||||
"showMethod": "fadeIn",
|
||||
"hideMethod": "fadeOut",
|
||||
"reposition": function() {
|
||||
if(PanelToggler.isVisible()) {
|
||||
$("#toast-container").addClass("notification-bottom-right-center");
|
||||
} else {
|
||||
$("#toast-container").removeClass("notification-bottom-right-center");
|
||||
}
|
||||
},
|
||||
"newestOnTop": false
|
||||
};
|
||||
|
||||
SettingsMenu.init();
|
||||
|
||||
};
|
||||
|
||||
function chatAddError(errorMessage, originalText)
|
||||
{
|
||||
return Chat.chatAddError(errorMessage, originalText);
|
||||
};
|
||||
|
||||
function chatSetSubject(text)
|
||||
{
|
||||
return Chat.chatSetSubject(text);
|
||||
};
|
||||
|
||||
function updateChatConversation(from, displayName, message) {
|
||||
return Chat.updateChatConversation(from, displayName, message);
|
||||
};
|
||||
|
||||
function onMucJoined(jid, info) {
|
||||
Toolbar.updateRoomUrl(window.location.href);
|
||||
var meHTML = APP.translation.generateTranslatonHTML("me");
|
||||
$("#localNick").html(Strophe.getResourceFromJid(jid) + " (" + meHTML + ")");
|
||||
|
||||
var settings = Settings.getSettings();
|
||||
// Add myself to the contact list.
|
||||
ContactList.addContact(jid, settings.email || settings.uid);
|
||||
|
||||
// Once we've joined the muc show the toolbar
|
||||
ToolbarToggler.showToolbar();
|
||||
|
||||
var displayName = !config.displayJids
|
||||
? info.displayName : Strophe.getResourceFromJid(jid);
|
||||
|
||||
if (displayName)
|
||||
onDisplayNameChanged('localVideoContainer', displayName);
|
||||
}
|
||||
|
||||
function initEtherpad(name) {
|
||||
Etherpad.init(name);
|
||||
};
|
||||
|
||||
function onMucLeft(jid) {
|
||||
console.log('left.muc', jid);
|
||||
var displayName = $('#participant_' + Strophe.getResourceFromJid(jid) +
|
||||
'>.displayname').html();
|
||||
messageHandler.notify(displayName,'notify.somebody',
|
||||
'disconnected',
|
||||
'notify.disconnected');
|
||||
// Need to call this with a slight delay, otherwise the element couldn't be
|
||||
// found for some reason.
|
||||
// XXX(gp) it works fine without the timeout for me (with Chrome 38).
|
||||
window.setTimeout(function () {
|
||||
var container = document.getElementById(
|
||||
'participant_' + Strophe.getResourceFromJid(jid));
|
||||
if (container) {
|
||||
ContactList.removeContact(jid);
|
||||
VideoLayout.removeConnectionIndicator(jid);
|
||||
// hide here, wait for video to close before removing
|
||||
$(container).hide();
|
||||
VideoLayout.resizeThumbnails();
|
||||
}
|
||||
}, 10);
|
||||
|
||||
VideoLayout.participantLeft(jid);
|
||||
|
||||
};
|
||||
|
||||
|
||||
function onLocalRoleChange(jid, info, pres, isModerator)
|
||||
{
|
||||
|
||||
console.info("My role changed, new role: " + info.role);
|
||||
onModeratorStatusChanged(isModerator);
|
||||
VideoLayout.showModeratorIndicator();
|
||||
|
||||
if (isModerator) {
|
||||
Authentication.closeAuthenticationWindow();
|
||||
messageHandler.notify(null, "notify.me",
|
||||
'connected', "notify.moderator");
|
||||
}
|
||||
}
|
||||
|
||||
function onModeratorStatusChanged(isModerator) {
|
||||
|
||||
Toolbar.showSipCallButton(isModerator);
|
||||
Toolbar.showRecordingButton(
|
||||
isModerator); //&&
|
||||
// FIXME:
|
||||
// Recording visible if
|
||||
// there are at least 2(+ 1 focus) participants
|
||||
//Object.keys(connection.emuc.members).length >= 3);
|
||||
|
||||
if (isModerator && config.etherpad_base) {
|
||||
Etherpad.init();
|
||||
}
|
||||
};
|
||||
|
||||
function onPasswordReqiured(callback) {
|
||||
// password is required
|
||||
Toolbar.lockLockButton();
|
||||
var message = '<h2 data-i18n="dialog.passwordRequired">';
|
||||
message += APP.translation.translateString(
|
||||
"dialog.passwordRequired");
|
||||
message += '</h2>' +
|
||||
'<input id="lockKey" type="text" data-i18n=' +
|
||||
'"[placeholder]dialog.password" placeholder="' +
|
||||
APP.translation.translateString("dialog.password") +
|
||||
'" autofocus>';
|
||||
|
||||
messageHandler.openTwoButtonDialog(null, null, null, message,
|
||||
true,
|
||||
"dialog.Ok",
|
||||
function (e, v, m, f) {},
|
||||
function (event) {
|
||||
document.getElementById('lockKey').focus();
|
||||
},
|
||||
function (e, v, m, f) {
|
||||
if (v) {
|
||||
var lockKey = document.getElementById('lockKey');
|
||||
if (lockKey.value !== null) {
|
||||
Toolbar.setSharedKey(lockKey.value);
|
||||
callback(lockKey.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
function onMucEntered(jid, id, displayName) {
|
||||
messageHandler.notify(displayName,'notify.somebody',
|
||||
'connected',
|
||||
'notify.connected');
|
||||
|
||||
// Add Peer's container
|
||||
VideoLayout.ensurePeerContainerExists(jid,id);
|
||||
}
|
||||
|
||||
function onMucPresenceStatus( jid, info) {
|
||||
VideoLayout.setPresenceStatus(
|
||||
'participant_' + Strophe.getResourceFromJid(jid), info.status);
|
||||
}
|
||||
|
||||
function onMucRoleChanged(role, displayName) {
|
||||
VideoLayout.showModeratorIndicator();
|
||||
|
||||
if (role === 'moderator') {
|
||||
var messageKey, messageOptions = {};
|
||||
if (!displayName) {
|
||||
messageKey = "notify.grantedToUnknown";
|
||||
}
|
||||
else
|
||||
{
|
||||
messageKey = "notify.grantedTo";
|
||||
messageOptions = {to: displayName};
|
||||
}
|
||||
messageHandler.notify(
|
||||
displayName,'notify.somebody',
|
||||
'connected', messageKey,
|
||||
messageOptions);
|
||||
}
|
||||
}
|
||||
|
||||
function onAuthenticationRequired(intervalCallback) {
|
||||
Authentication.openAuthenticationDialog(
|
||||
roomName, intervalCallback, function () {
|
||||
Toolbar.authenticateClicked();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
function onLastNChanged(oldValue, newValue) {
|
||||
if (config.muteLocalVideoIfNotInLastN) {
|
||||
setVideoMute(!newValue, { 'byUser': false });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UI.toggleSmileys = function () {
|
||||
Chat.toggleSmileys();
|
||||
};
|
||||
|
||||
UI.getSettings = function () {
|
||||
return Settings.getSettings();
|
||||
};
|
||||
|
||||
UI.toggleFilmStrip = function () {
|
||||
return BottomToolbar.toggleFilmStrip();
|
||||
};
|
||||
|
||||
UI.toggleChat = function () {
|
||||
return BottomToolbar.toggleChat();
|
||||
};
|
||||
|
||||
UI.toggleContactList = function () {
|
||||
return BottomToolbar.toggleContactList();
|
||||
};
|
||||
|
||||
UI.inputDisplayNameHandler = function (value) {
|
||||
VideoLayout.inputDisplayNameHandler(value);
|
||||
};
|
||||
|
||||
|
||||
UI.getLargeVideoState = function()
|
||||
{
|
||||
return VideoLayout.getLargeVideoState();
|
||||
};
|
||||
|
||||
UI.generateRoomName = function() {
|
||||
if(roomName)
|
||||
return roomName;
|
||||
var roomnode = null;
|
||||
var path = window.location.pathname;
|
||||
|
||||
// determinde the room node from the url
|
||||
// TODO: just the roomnode or the whole bare jid?
|
||||
if (config.getroomnode && typeof config.getroomnode === 'function') {
|
||||
// custom function might be responsible for doing the pushstate
|
||||
roomnode = config.getroomnode(path);
|
||||
} else {
|
||||
/* fall back to default strategy
|
||||
* this is making assumptions about how the URL->room mapping happens.
|
||||
* It currently assumes deployment at root, with a rewrite like the
|
||||
* following one (for nginx):
|
||||
location ~ ^/([a-zA-Z0-9]+)$ {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
*/
|
||||
if (path.length > 1) {
|
||||
roomnode = path.substr(1).toLowerCase();
|
||||
} else {
|
||||
var word = RoomNameGenerator.generateRoomWithoutSeparator();
|
||||
roomnode = word.toLowerCase();
|
||||
|
||||
window.history.pushState('VideoChat',
|
||||
'Room: ' + word, window.location.pathname + word);
|
||||
}
|
||||
}
|
||||
|
||||
roomName = roomnode + '@' + config.hosts.muc;
|
||||
return roomName;
|
||||
};
|
||||
|
||||
|
||||
UI.connectionIndicatorShowMore = function(id)
|
||||
{
|
||||
return VideoLayout.connectionIndicators[id].showMore();
|
||||
};
|
||||
|
||||
UI.getCredentials = function () {
|
||||
var settings = this.getSettings();
|
||||
return {
|
||||
bosh: document.getElementById('boshURL').value,
|
||||
password: document.getElementById('password').value,
|
||||
jid: document.getElementById('jid').value,
|
||||
email: settings.email,
|
||||
displayName: settings.displayName,
|
||||
uid: settings.uid
|
||||
};
|
||||
};
|
||||
|
||||
UI.disableConnect = function () {
|
||||
document.getElementById('connect').disabled = true;
|
||||
};
|
||||
|
||||
UI.showLoginPopup = function(callback)
|
||||
{
|
||||
console.log('password is required');
|
||||
var message = '<h2 data-i18n="dialog.passwordRequired">';
|
||||
message += APP.translation.translateString(
|
||||
"dialog.passwordRequired");
|
||||
message += '</h2>' +
|
||||
'<input id="passwordrequired.username" type="text" ' +
|
||||
'placeholder="user@domain.net" autofocus>' +
|
||||
'<input id="passwordrequired.password" ' +
|
||||
'type="password" data-i18n="[placeholder]dialog.userPassword"' +
|
||||
' placeholder="user password">';
|
||||
UI.messageHandler.openTwoButtonDialog(null, null, null, message,
|
||||
true,
|
||||
"dialog.Ok",
|
||||
function (e, v, m, f) {
|
||||
if (v) {
|
||||
var username = document.getElementById('passwordrequired.username');
|
||||
var password = document.getElementById('passwordrequired.password');
|
||||
|
||||
if (username.value !== null && password.value != null) {
|
||||
callback(username.value, password.value);
|
||||
}
|
||||
}
|
||||
},
|
||||
function (event) {
|
||||
document.getElementById('passwordrequired.username').focus();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
UI.checkForNicknameAndJoin = function () {
|
||||
|
||||
Authentication.closeAuthenticationDialog();
|
||||
Authentication.stopInterval();
|
||||
|
||||
var nick = null;
|
||||
if (config.useNicks) {
|
||||
nick = window.prompt('Your nickname (optional)');
|
||||
}
|
||||
APP.xmpp.joinRoom(roomName, config.useNicks, nick);
|
||||
};
|
||||
|
||||
|
||||
function dump(elem, filename) {
|
||||
elem = elem.parentNode;
|
||||
elem.download = filename || 'meetlog.json';
|
||||
elem.href = 'data:application/json;charset=utf-8,\n';
|
||||
var data = APP.xmpp.populateData();
|
||||
var metadata = {};
|
||||
metadata.time = new Date();
|
||||
metadata.url = window.location.href;
|
||||
metadata.ua = navigator.userAgent;
|
||||
var log = APP.xmpp.getLogger();
|
||||
if (log) {
|
||||
metadata.xmpp = log;
|
||||
}
|
||||
data.metadata = metadata;
|
||||
elem.href += encodeURIComponent(JSON.stringify(data, null, ' '));
|
||||
return false;
|
||||
}
|
||||
|
||||
UI.getRoomName = function () {
|
||||
return roomName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutes/unmutes the local video.
|
||||
*/
|
||||
UI.toggleVideo = function () {
|
||||
UIUtil.buttonClick("#video", "icon-camera icon-camera-disabled");
|
||||
|
||||
setVideoMute(!APP.RTC.localVideo.isMuted());
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutes / unmutes audio for the local participant.
|
||||
*/
|
||||
UI.toggleAudio = function() {
|
||||
UI.setAudioMuted(!APP.RTC.localAudio.isMuted());
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets muted audio state for the local participant.
|
||||
*/
|
||||
UI.setAudioMuted = function (mute) {
|
||||
|
||||
if(!APP.xmpp.setAudioMute(mute, function () {
|
||||
VideoLayout.showLocalAudioIndicator(mute);
|
||||
|
||||
UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
|
||||
}))
|
||||
{
|
||||
// We still click the button.
|
||||
UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
UI.addListener = function (type, listener) {
|
||||
eventEmitter.on(type, listener);
|
||||
}
|
||||
|
||||
UI.clickOnVideo = function (videoNumber) {
|
||||
var remoteVideos = $(".videocontainer:not(#mixedstream)");
|
||||
if (remoteVideos.length > videoNumber) {
|
||||
remoteVideos[videoNumber].click();
|
||||
}
|
||||
}
|
||||
|
||||
//Used by torture
|
||||
UI.showToolbar = function () {
|
||||
return ToolbarToggler.showToolbar();
|
||||
}
|
||||
|
||||
//Used by torture
|
||||
UI.dockToolbar = function (isDock) {
|
||||
return ToolbarToggler.dockToolbar(isDock);
|
||||
}
|
||||
|
||||
module.exports = UI;
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
var CanvasUtil = require("./CanvasUtils");
|
||||
|
||||
var ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
|
||||
|
||||
function initActiveSpeakerAudioLevels() {
|
||||
var ASRadius = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2;
|
||||
var ASCenter = (interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE + ASRadius) / 2;
|
||||
|
||||
// Draw a circle.
|
||||
ASDrawContext.arc(ASCenter, ASCenter, ASRadius, 0, 2 * Math.PI);
|
||||
|
||||
// Add a shadow around the circle
|
||||
ASDrawContext.shadowColor = interfaceConfig.SHADOW_COLOR;
|
||||
ASDrawContext.shadowOffsetX = 0;
|
||||
ASDrawContext.shadowOffsetY = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The audio Levels plugin.
|
||||
*/
|
||||
@@ -6,11 +23,15 @@ var AudioLevels = (function(my) {
|
||||
|
||||
my.LOCAL_LEVEL = 'local';
|
||||
|
||||
my.init = function () {
|
||||
initActiveSpeakerAudioLevels();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the audio level canvas for the given peerJid. If the canvas
|
||||
* didn't exist we create it.
|
||||
*/
|
||||
my.updateAudioLevelCanvas = function (peerJid) {
|
||||
my.updateAudioLevelCanvas = function (peerJid, VideoLayout) {
|
||||
var resourceJid = null;
|
||||
var videoSpanId = null;
|
||||
if (!peerJid)
|
||||
@@ -66,7 +87,7 @@ var AudioLevels = (function(my) {
|
||||
* which we draw the audio level
|
||||
* @param audioLevel the newAudio level to render
|
||||
*/
|
||||
my.updateAudioLevel = function (resourceJid, audioLevel) {
|
||||
my.updateAudioLevel = function (resourceJid, audioLevel, largeVideoResourceJid) {
|
||||
drawAudioLevelCanvas(resourceJid, audioLevel);
|
||||
|
||||
var videoSpanId = getVideoSpanId(resourceJid);
|
||||
@@ -85,51 +106,33 @@ var AudioLevels = (function(my) {
|
||||
drawContext.drawImage(canvasCache, 0, 0);
|
||||
|
||||
if(resourceJid === AudioLevels.LOCAL_LEVEL) {
|
||||
if(!connection.emuc.myroomjid) {
|
||||
if(!APP.xmpp.myJid()) {
|
||||
return;
|
||||
}
|
||||
resourceJid = Strophe.getResourceFromJid(connection.emuc.myroomjid);
|
||||
resourceJid = APP.xmpp.myResource();
|
||||
}
|
||||
|
||||
if(resourceJid === VideoLayout.getLargeVideoState().userResourceJid) {
|
||||
AudioLevels.updateActiveSpeakerAudioLevel(audioLevel);
|
||||
if(resourceJid === largeVideoResourceJid) {
|
||||
window.requestAnimationFrame(function () {
|
||||
AudioLevels.updateActiveSpeakerAudioLevel(audioLevel);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
my.updateActiveSpeakerAudioLevel = function(audioLevel) {
|
||||
var drawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
|
||||
var r = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2;
|
||||
var center = (interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE + r) / 2;
|
||||
if($("#activeSpeaker").css("visibility") == "hidden")
|
||||
return;
|
||||
|
||||
// Save the previous state of the context.
|
||||
drawContext.save();
|
||||
|
||||
drawContext.clearRect(0, 0, 300, 300);
|
||||
ASDrawContext.clearRect(0, 0, 300, 300);
|
||||
if(audioLevel == 0)
|
||||
return;
|
||||
|
||||
// Draw a circle.
|
||||
drawContext.arc(center, center, r, 0, 2 * Math.PI);
|
||||
ASDrawContext.shadowBlur = getShadowLevel(audioLevel);
|
||||
|
||||
// Add a shadow around the circle
|
||||
drawContext.shadowColor = interfaceConfig.SHADOW_COLOR;
|
||||
drawContext.shadowBlur = getShadowLevel(audioLevel);
|
||||
drawContext.shadowOffsetX = 0;
|
||||
drawContext.shadowOffsetY = 0;
|
||||
|
||||
// Fill the shape.
|
||||
drawContext.fill();
|
||||
|
||||
drawContext.save();
|
||||
|
||||
drawContext.restore();
|
||||
|
||||
|
||||
drawContext.arc(center, center, r, 0, 2 * Math.PI);
|
||||
|
||||
drawContext.clip();
|
||||
drawContext.clearRect(0, 0, 277, 200);
|
||||
|
||||
// Restore the previous context state.
|
||||
drawContext.restore();
|
||||
ASDrawContext.fill();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -219,8 +222,8 @@ var AudioLevels = (function(my) {
|
||||
function getVideoSpanId(resourceJid) {
|
||||
var videoSpanId = null;
|
||||
if (resourceJid === AudioLevels.LOCAL_LEVEL
|
||||
|| (connection.emuc.myroomjid && resourceJid
|
||||
=== Strophe.getResourceFromJid(connection.emuc.myroomjid)))
|
||||
|| (APP.xmpp.myResource() && resourceJid
|
||||
=== APP.xmpp.myResource()))
|
||||
videoSpanId = 'localVideoContainer';
|
||||
else
|
||||
videoSpanId = 'participant_' + resourceJid;
|
||||
@@ -258,3 +261,5 @@ var AudioLevels = (function(my) {
|
||||
return my;
|
||||
|
||||
})(AudioLevels || {});
|
||||
|
||||
module.exports = AudioLevels;
|
||||
@@ -107,3 +107,5 @@ var CanvasUtil = (function(my) {
|
||||
|
||||
return my;
|
||||
})(CanvasUtil || {});
|
||||
|
||||
module.exports = CanvasUtil;
|
||||
88
modules/UI/authentication/Authentication.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/* Initial "authentication required" dialog */
|
||||
var authDialog = null;
|
||||
/* Loop retry ID that wits for other user to create the room */
|
||||
var authRetryId = null;
|
||||
var authenticationWindow = null;
|
||||
|
||||
var Authentication = {
|
||||
openAuthenticationDialog: function (roomName, intervalCallback, callback) {
|
||||
// This is the loop that will wait for the room to be created by
|
||||
// someone else. 'auth_required.moderator' will bring us back here.
|
||||
authRetryId = window.setTimeout(intervalCallback , 5000);
|
||||
// Show prompt only if it's not open
|
||||
if (authDialog !== null) {
|
||||
return;
|
||||
}
|
||||
// extract room name from 'room@muc.server.net'
|
||||
var room = roomName.substr(0, roomName.indexOf('@'));
|
||||
|
||||
var title = APP.translation.generateTranslatonHTML("dialog.Stop");
|
||||
var msg = APP.translation.generateTranslatonHTML("dialog.AuthMsg",
|
||||
{room: room});
|
||||
var button = APP.translation.generateTranslatonHTML(
|
||||
"dialog.Authenticate");
|
||||
var buttons = {};
|
||||
buttons.authenticate = {title: button, value: "authNow"};
|
||||
|
||||
authDialog = APP.UI.messageHandler.openDialog(
|
||||
title,
|
||||
msg,
|
||||
true,
|
||||
buttons,
|
||||
function (onSubmitEvent, submitValue) {
|
||||
|
||||
// Do not close the dialog yet
|
||||
onSubmitEvent.preventDefault();
|
||||
|
||||
// Open login popup
|
||||
if (submitValue === 'authNow') {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
closeAuthenticationWindow:function () {
|
||||
if (authenticationWindow) {
|
||||
authenticationWindow.close();
|
||||
authenticationWindow = null;
|
||||
}
|
||||
},
|
||||
focusAuthenticationWindow: function () {
|
||||
// If auth window exists just bring it to the front
|
||||
if (authenticationWindow) {
|
||||
authenticationWindow.focus();
|
||||
return;
|
||||
}
|
||||
},
|
||||
closeAuthenticationDialog: function () {
|
||||
// Close authentication dialog if opened
|
||||
if (authDialog) {
|
||||
APP.UI.messageHandler.closeDialog();
|
||||
authDialog = null;
|
||||
}
|
||||
},
|
||||
createAuthenticationWindow: function (callback, url) {
|
||||
authenticationWindow = APP.UI.messageHandler.openCenteredPopup(
|
||||
url, 910, 660,
|
||||
// On closed
|
||||
function () {
|
||||
// Close authentication dialog if opened
|
||||
if (authDialog) {
|
||||
messageHandler.closeDialog();
|
||||
authDialog = null;
|
||||
}
|
||||
callback();
|
||||
authenticationWindow = null;
|
||||
});
|
||||
return authenticationWindow;
|
||||
},
|
||||
stopInterval: function () {
|
||||
// Clear retry interval, so that we don't call 'doJoinAfterFocus' twice
|
||||
if (authRetryId) {
|
||||
window.clearTimeout(authRetryId);
|
||||
authRetryId = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Authentication;
|
||||
@@ -1,15 +1,51 @@
|
||||
var Avatar = (function(my) {
|
||||
var users = {};
|
||||
var activeSpeakerJid;
|
||||
var Settings = require("../../settings/Settings");
|
||||
var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
|
||||
|
||||
var users = {};
|
||||
var activeSpeakerJid;
|
||||
|
||||
function setVisibility(selector, show) {
|
||||
if (selector && selector.length > 0) {
|
||||
selector.css("visibility", show ? "visible" : "hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function isUserMuted(jid) {
|
||||
// XXX(gp) we may want to rename this method to something like
|
||||
// isUserStreaming, for example.
|
||||
if (jid && jid != APP.xmpp.myJid()) {
|
||||
var resource = Strophe.getResourceFromJid(jid);
|
||||
if (!require("../videolayout/VideoLayout").isInLastN(resource)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!APP.RTC.remoteStreams[jid] || !APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
|
||||
return null;
|
||||
}
|
||||
return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted;
|
||||
}
|
||||
|
||||
function getGravatarUrl(id, size) {
|
||||
if(id === APP.xmpp.myJid() || !id) {
|
||||
id = Settings.getSettings().uid;
|
||||
}
|
||||
return 'https://www.gravatar.com/avatar/' +
|
||||
MD5.hexdigest(id.trim().toLowerCase()) +
|
||||
"?d=wavatar&size=" + (size || "30");
|
||||
}
|
||||
|
||||
var Avatar = {
|
||||
|
||||
/**
|
||||
* Sets the user's avatar in the settings menu(if local user), contact list
|
||||
* and thumbnail
|
||||
* @param jid jid of the user
|
||||
* @param id email or userID to be used as a hash
|
||||
*/
|
||||
my.setUserAvatar = function(jid, id) {
|
||||
if(id) {
|
||||
if(users[jid] === id) {
|
||||
setUserAvatar: function (jid, id) {
|
||||
if (id) {
|
||||
if (users[jid] === id) {
|
||||
return;
|
||||
}
|
||||
users[jid] = id;
|
||||
@@ -22,19 +58,19 @@ var Avatar = (function(my) {
|
||||
|
||||
// set the avatar in the settings menu if it is local user and get the
|
||||
// local video container
|
||||
if(jid === connection.emuc.myroomjid) {
|
||||
if (jid === APP.xmpp.myJid()) {
|
||||
$('#avatar').get(0).src = thumbUrl;
|
||||
thumbnail = $('#localVideoContainer');
|
||||
}
|
||||
|
||||
// set the avatar in the contact list
|
||||
var contact = $('#' + resourceJid + '>img');
|
||||
if(contact && contact.length > 0) {
|
||||
if (contact && contact.length > 0) {
|
||||
contact.get(0).src = contactListUrl;
|
||||
}
|
||||
|
||||
// set the avatar in the thumbnail
|
||||
if(avatar && avatar.length > 0) {
|
||||
if (avatar && avatar.length > 0) {
|
||||
avatar[0].src = thumbUrl;
|
||||
} else {
|
||||
if (thumbnail && thumbnail.length > 0) {
|
||||
@@ -48,10 +84,10 @@ var Avatar = (function(my) {
|
||||
|
||||
//if the user is the current active speaker - update the active speaker
|
||||
// avatar
|
||||
if(jid === activeSpeakerJid) {
|
||||
Avatar.updateActiveSpeakerAvatarSrc(jid);
|
||||
if (jid === activeSpeakerJid) {
|
||||
this.updateActiveSpeakerAvatarSrc(jid);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides or shows the user's avatar
|
||||
@@ -59,23 +95,23 @@ var Avatar = (function(my) {
|
||||
* @param show whether we should show the avatar or not
|
||||
* video because there is no dominant speaker and no focused speaker
|
||||
*/
|
||||
my.showUserAvatar = function(jid, show) {
|
||||
if(users[jid]) {
|
||||
showUserAvatar: function (jid, show) {
|
||||
if (users[jid]) {
|
||||
var resourceJid = Strophe.getResourceFromJid(jid);
|
||||
var video = $('#participant_' + resourceJid + '>video');
|
||||
var avatar = $('#avatar_' + resourceJid);
|
||||
|
||||
if(jid === connection.emuc.myroomjid) {
|
||||
if (jid === APP.xmpp.myJid()) {
|
||||
video = $('#localVideoWrapper>video');
|
||||
}
|
||||
if(show === undefined || show === null) {
|
||||
if (show === undefined || show === null) {
|
||||
show = isUserMuted(jid);
|
||||
}
|
||||
|
||||
//if the user is the currently focused, the dominant speaker or if
|
||||
//there is no focused and no dominant speaker and the large video is
|
||||
//currently shown
|
||||
if (activeSpeakerJid === jid && VideoLayout.isLargeVideoOnTop()) {
|
||||
if (activeSpeakerJid === jid && require("../videolayout/VideoLayout").isLargeVideoOnTop()) {
|
||||
setVisibility($("#largeVideo"), !show);
|
||||
setVisibility($('#activeSpeaker'), show);
|
||||
setVisibility(avatar, false);
|
||||
@@ -87,62 +123,33 @@ var Avatar = (function(my) {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the src of the active speaker avatar
|
||||
* @param jid of the current active speaker
|
||||
*/
|
||||
my.updateActiveSpeakerAvatarSrc = function(jid) {
|
||||
if(!jid) {
|
||||
jid = connection.emuc.findJidFromResource(
|
||||
VideoLayout.getLargeVideoState().userResourceJid);
|
||||
updateActiveSpeakerAvatarSrc: function (jid) {
|
||||
if (!jid) {
|
||||
jid = APP.xmpp.findJidFromResource(
|
||||
require("../videolayout/VideoLayout").getLargeVideoState().userResourceJid);
|
||||
}
|
||||
var avatar = $("#activeSpeakerAvatar")[0];
|
||||
var url = getGravatarUrl(users[jid],
|
||||
interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE);
|
||||
if(jid === activeSpeakerJid && avatar.src === url) {
|
||||
if (jid === activeSpeakerJid && avatar.src === url) {
|
||||
return;
|
||||
}
|
||||
activeSpeakerJid = jid;
|
||||
var isMuted = isUserMuted(jid);
|
||||
if(jid && isMuted !== null) {
|
||||
if (jid && isMuted !== null) {
|
||||
avatar.src = url;
|
||||
setVisibility($("#largeVideo"), !isMuted);
|
||||
Avatar.showUserAvatar(jid, isMuted);
|
||||
}
|
||||
};
|
||||
|
||||
function setVisibility(selector, show) {
|
||||
if (selector && selector.length > 0) {
|
||||
selector.css("visibility", show ? "visible" : "hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function isUserMuted(jid) {
|
||||
// XXX(gp) we may want to rename this method to something like
|
||||
// isUserStreaming, for example.
|
||||
if (jid && jid != connection.emuc.myroomjid) {
|
||||
var resource = Strophe.getResourceFromJid(jid);
|
||||
if (!VideoLayout.isInLastN(resource)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!RTC.remoteStreams[jid] || !RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
|
||||
return null;
|
||||
}
|
||||
return RTC.remoteStreams[jid][MediaStream.VIDEO_TYPE].muted;
|
||||
}
|
||||
|
||||
function getGravatarUrl(id, size) {
|
||||
if(id === connection.emuc.myroomjid || !id) {
|
||||
id = SettingsMenu.getUID();
|
||||
}
|
||||
return 'https://www.gravatar.com/avatar/' +
|
||||
MD5.hexdigest(id.trim().toLowerCase()) +
|
||||
"?d=wavatar&size=" + (size || "30");
|
||||
}
|
||||
|
||||
return my;
|
||||
}(Avatar || {}));
|
||||
module.exports = Avatar;
|
||||
194
modules/UI/etherpad/Etherpad.js
Normal file
@@ -0,0 +1,194 @@
|
||||
/* global $, config,
|
||||
setLargeVideoVisible, Util */
|
||||
|
||||
var VideoLayout = require("../videolayout/VideoLayout");
|
||||
var Prezi = require("../prezi/Prezi");
|
||||
var UIUtil = require("../util/UIUtil");
|
||||
|
||||
var etherpadName = null;
|
||||
var etherpadIFrame = null;
|
||||
var domain = null;
|
||||
var options = "?showControls=true&showChat=false&showLineNumbers=true&useMonospaceFont=false";
|
||||
|
||||
|
||||
/**
|
||||
* Resizes the etherpad.
|
||||
*/
|
||||
function resize() {
|
||||
if ($('#etherpad>iframe').length) {
|
||||
var remoteVideos = $('#remoteVideos');
|
||||
var availableHeight
|
||||
= window.innerHeight - remoteVideos.outerHeight();
|
||||
var availableWidth = UIUtil.getAvailableVideoWidth();
|
||||
|
||||
$('#etherpad>iframe').width(availableWidth);
|
||||
$('#etherpad>iframe').height(availableHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares the Etherpad name with other participants.
|
||||
*/
|
||||
function shareEtherpad() {
|
||||
APP.xmpp.addToPresence("etherpad", etherpadName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Etherpad button and adds it to the toolbar.
|
||||
*/
|
||||
function enableEtherpadButton() {
|
||||
if (!$('#etherpadButton').is(":visible"))
|
||||
$('#etherpadButton').css({display: 'inline-block'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the IFrame for the etherpad.
|
||||
*/
|
||||
function createIFrame() {
|
||||
etherpadIFrame = document.createElement('iframe');
|
||||
etherpadIFrame.src = domain + etherpadName + options;
|
||||
etherpadIFrame.frameBorder = 0;
|
||||
etherpadIFrame.scrolling = "no";
|
||||
etherpadIFrame.width = $('#largeVideoContainer').width() || 640;
|
||||
etherpadIFrame.height = $('#largeVideoContainer').height() || 480;
|
||||
etherpadIFrame.setAttribute('style', 'visibility: hidden;');
|
||||
|
||||
document.getElementById('etherpad').appendChild(etherpadIFrame);
|
||||
|
||||
etherpadIFrame.onload = function() {
|
||||
|
||||
document.domain = document.domain;
|
||||
bubbleIframeMouseMove(etherpadIFrame);
|
||||
setTimeout(function() {
|
||||
// the iframes inside of the etherpad are
|
||||
// not yet loaded when the etherpad iframe is loaded
|
||||
var outer = etherpadIFrame.
|
||||
contentDocument.getElementsByName("ace_outer")[0];
|
||||
bubbleIframeMouseMove(outer);
|
||||
var inner = outer.
|
||||
contentDocument.getElementsByName("ace_inner")[0];
|
||||
bubbleIframeMouseMove(inner);
|
||||
}, 2000);
|
||||
};
|
||||
}
|
||||
|
||||
function bubbleIframeMouseMove(iframe){
|
||||
var existingOnMouseMove = iframe.contentWindow.onmousemove;
|
||||
iframe.contentWindow.onmousemove = function(e){
|
||||
if(existingOnMouseMove) existingOnMouseMove(e);
|
||||
var evt = document.createEvent("MouseEvents");
|
||||
var boundingClientRect = iframe.getBoundingClientRect();
|
||||
evt.initMouseEvent(
|
||||
"mousemove",
|
||||
true, // bubbles
|
||||
false, // not cancelable
|
||||
window,
|
||||
e.detail,
|
||||
e.screenX,
|
||||
e.screenY,
|
||||
e.clientX + boundingClientRect.left,
|
||||
e.clientY + boundingClientRect.top,
|
||||
e.ctrlKey,
|
||||
e.altKey,
|
||||
e.shiftKey,
|
||||
e.metaKey,
|
||||
e.button,
|
||||
null // no related element
|
||||
);
|
||||
iframe.dispatchEvent(evt);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* On video selected event.
|
||||
*/
|
||||
$(document).bind('video.selected', function (event, isPresentation) {
|
||||
if (config.etherpad_base && etherpadIFrame && etherpadIFrame.style.visibility !== 'hidden')
|
||||
Etherpad.toggleEtherpad(isPresentation);
|
||||
});
|
||||
|
||||
|
||||
var Etherpad = {
|
||||
/**
|
||||
* Initializes the etherpad.
|
||||
*/
|
||||
init: function (name) {
|
||||
|
||||
if (config.etherpad_base && !etherpadName) {
|
||||
|
||||
domain = config.etherpad_base;
|
||||
|
||||
if (!name) {
|
||||
// In case we're the focus we generate the name.
|
||||
etherpadName = Math.random().toString(36).substring(7) +
|
||||
'_' + (new Date().getTime()).toString();
|
||||
shareEtherpad();
|
||||
}
|
||||
else
|
||||
etherpadName = name;
|
||||
|
||||
enableEtherpadButton();
|
||||
|
||||
/**
|
||||
* Resizes the etherpad, when the window is resized.
|
||||
*/
|
||||
$(window).resize(function () {
|
||||
resize();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens/hides the Etherpad.
|
||||
*/
|
||||
toggleEtherpad: function (isPresentation) {
|
||||
if (!etherpadIFrame)
|
||||
createIFrame();
|
||||
|
||||
var largeVideo = null;
|
||||
if (Prezi.isPresentationVisible())
|
||||
largeVideo = $('#presentation>iframe');
|
||||
else
|
||||
largeVideo = $('#largeVideo');
|
||||
|
||||
if ($('#etherpad>iframe').css('visibility') === 'hidden') {
|
||||
$('#activeSpeaker').css('visibility', 'hidden');
|
||||
largeVideo.fadeOut(300, function () {
|
||||
if (Prezi.isPresentationVisible()) {
|
||||
largeVideo.css({opacity: '0'});
|
||||
} else {
|
||||
VideoLayout.setLargeVideoVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
$('#etherpad>iframe').fadeIn(300, function () {
|
||||
document.body.style.background = '#eeeeee';
|
||||
$('#etherpad>iframe').css({visibility: 'visible'});
|
||||
$('#etherpad').css({zIndex: 2});
|
||||
});
|
||||
}
|
||||
else if ($('#etherpad>iframe')) {
|
||||
$('#etherpad>iframe').fadeOut(300, function () {
|
||||
$('#etherpad>iframe').css({visibility: 'hidden'});
|
||||
$('#etherpad').css({zIndex: 0});
|
||||
document.body.style.background = 'black';
|
||||
});
|
||||
|
||||
if (!isPresentation) {
|
||||
$('#largeVideo').fadeIn(300, function () {
|
||||
VideoLayout.setLargeVideoVisible(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
resize();
|
||||
},
|
||||
|
||||
isVisible: function() {
|
||||
var etherpadIframe = $('#etherpad>iframe');
|
||||
return etherpadIframe && etherpadIframe.is(':visible');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = Etherpad;
|
||||
371
modules/UI/prezi/Prezi.js
Normal file
@@ -0,0 +1,371 @@
|
||||
var ToolbarToggler = require("../toolbars/ToolbarToggler");
|
||||
var UIUtil = require("../util/UIUtil");
|
||||
var VideoLayout = require("../videolayout/VideoLayout");
|
||||
var messageHandler = require("../util/MessageHandler");
|
||||
var PreziPlayer = require("./PreziPlayer");
|
||||
|
||||
var preziPlayer = null;
|
||||
|
||||
var Prezi = {
|
||||
|
||||
|
||||
/**
|
||||
* Reloads the current presentation.
|
||||
*/
|
||||
reloadPresentation: function() {
|
||||
var iframe = document.getElementById(preziPlayer.options.preziId);
|
||||
iframe.src = iframe.src;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns <tt>true</tt> if the presentation is visible, <tt>false</tt> -
|
||||
* otherwise.
|
||||
*/
|
||||
isPresentationVisible: function () {
|
||||
return ($('#presentation>iframe') != null
|
||||
&& $('#presentation>iframe').css('opacity') == 1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the Prezi dialog, from which the user could choose a presentation
|
||||
* to load.
|
||||
*/
|
||||
openPreziDialog: function() {
|
||||
var myprezi = APP.xmpp.getPrezi();
|
||||
if (myprezi) {
|
||||
messageHandler.openTwoButtonDialog("dialog.removePreziTitle",
|
||||
null,
|
||||
"dialog.removePreziMsg",
|
||||
null,
|
||||
false,
|
||||
"dialog.Remove",
|
||||
function(e,v,m,f) {
|
||||
if(v) {
|
||||
APP.xmpp.removePreziFromPresence();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
else if (preziPlayer != null) {
|
||||
messageHandler.openTwoButtonDialog("dialog.sharePreziTitle",
|
||||
null, "dialog.sharePreziMsg",
|
||||
null,
|
||||
false,
|
||||
"dialog.Ok",
|
||||
function(e,v,m,f) {
|
||||
$.prompt.close();
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
var html = APP.translation.generateTranslatonHTML(
|
||||
"dialog.sharePreziTitle");
|
||||
var cancelButton = APP.translation.generateTranslatonHTML(
|
||||
"dialog.Cancel");
|
||||
var shareButton = APP.translation.generateTranslatonHTML(
|
||||
"dialog.Share");
|
||||
var backButton = APP.translation.generateTranslatonHTML(
|
||||
"dialog.Back");
|
||||
var buttons = {};
|
||||
var buttons1 = {};
|
||||
buttons1.button1 = buttons.button1 = {title: cancelButton, value: false};
|
||||
buttons.button2 = {title: shareButton, value: true};
|
||||
buttons1.button2 = {title: backButton, value: true};
|
||||
var linkError = APP.translation.generateTranslatonHTML(
|
||||
"dialog.preziLinkError");
|
||||
var defaultUrl = APP.translation.translateString("defaultPreziLink",
|
||||
{url: "http://prezi.com/wz7vhjycl7e6/my-prezi"});
|
||||
var openPreziState = {
|
||||
state0: {
|
||||
html: '<h2>' + html + '</h2>' +
|
||||
'<input id="preziUrl" type="text" ' +
|
||||
'data-i18n="[placeholder]defaultPreziLink" data-i18n-options=\'' +
|
||||
JSON.stringify({"url": "http://prezi.com/wz7vhjycl7e6/my-prezi"}) +
|
||||
'\' placeholder="' + defaultUrl + '" autofocus>',
|
||||
persistent: false,
|
||||
buttons: buttons,
|
||||
defaultButton: 1,
|
||||
submit: function(e,v,m,f){
|
||||
e.preventDefault();
|
||||
if(v)
|
||||
{
|
||||
var preziUrl = document.getElementById('preziUrl');
|
||||
|
||||
if (preziUrl.value)
|
||||
{
|
||||
var urlValue
|
||||
= encodeURI(UIUtil.escapeHtml(preziUrl.value));
|
||||
|
||||
if (urlValue.indexOf('http://prezi.com/') != 0
|
||||
&& urlValue.indexOf('https://prezi.com/') != 0)
|
||||
{
|
||||
$.prompt.goToState('state1');
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
var presIdTmp = urlValue.substring(
|
||||
urlValue.indexOf("prezi.com/") + 10);
|
||||
if (!isAlphanumeric(presIdTmp)
|
||||
|| presIdTmp.indexOf('/') < 2) {
|
||||
$.prompt.goToState('state1');
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
APP.xmpp.addToPresence("prezi", urlValue);
|
||||
$.prompt.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
$.prompt.close();
|
||||
}
|
||||
},
|
||||
state1: {
|
||||
html: '<h2>' + html + '</h2>' +
|
||||
linkError,
|
||||
persistent: false,
|
||||
buttons: buttons1,
|
||||
defaultButton: 1,
|
||||
submit:function(e,v,m,f) {
|
||||
e.preventDefault();
|
||||
if(v==0)
|
||||
$.prompt.close();
|
||||
else
|
||||
$.prompt.goToState('state0');
|
||||
}
|
||||
}
|
||||
};
|
||||
var focusPreziUrl = function(e) {
|
||||
document.getElementById('preziUrl').focus();
|
||||
};
|
||||
messageHandler.openDialogWithStates(openPreziState, focusPreziUrl, focusPreziUrl);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* A new presentation has been added.
|
||||
*
|
||||
* @param event the event indicating the add of a presentation
|
||||
* @param jid the jid from which the presentation was added
|
||||
* @param presUrl url of the presentation
|
||||
* @param currentSlide the current slide to which we should move
|
||||
*/
|
||||
function presentationAdded(event, jid, presUrl, currentSlide) {
|
||||
console.log("presentation added", presUrl);
|
||||
|
||||
var presId = getPresentationId(presUrl);
|
||||
|
||||
var elementId = 'participant_'
|
||||
+ Strophe.getResourceFromJid(jid)
|
||||
+ '_' + presId;
|
||||
|
||||
// We explicitly don't specify the peer jid here, because we don't want
|
||||
// this video to be dealt with as a peer related one (for example we
|
||||
// don't want to show a mute/kick menu for this one, etc.).
|
||||
VideoLayout.addRemoteVideoContainer(null, elementId);
|
||||
VideoLayout.resizeThumbnails();
|
||||
|
||||
var controlsEnabled = false;
|
||||
if (jid === APP.xmpp.myJid())
|
||||
controlsEnabled = true;
|
||||
|
||||
setPresentationVisible(true);
|
||||
$('#largeVideoContainer').hover(
|
||||
function (event) {
|
||||
if (Prezi.isPresentationVisible()) {
|
||||
var reloadButtonRight = window.innerWidth
|
||||
- $('#presentation>iframe').offset().left
|
||||
- $('#presentation>iframe').width();
|
||||
|
||||
$('#reloadPresentation').css({ right: reloadButtonRight,
|
||||
display:'inline-block'});
|
||||
}
|
||||
},
|
||||
function (event) {
|
||||
if (!Prezi.isPresentationVisible())
|
||||
$('#reloadPresentation').css({display:'none'});
|
||||
else {
|
||||
var e = event.toElement || event.relatedTarget;
|
||||
|
||||
if (e && e.id != 'reloadPresentation' && e.id != 'header')
|
||||
$('#reloadPresentation').css({display:'none'});
|
||||
}
|
||||
});
|
||||
|
||||
preziPlayer = new PreziPlayer(
|
||||
'presentation',
|
||||
{preziId: presId,
|
||||
width: getPresentationWidth(),
|
||||
height: getPresentationHeihgt(),
|
||||
controls: controlsEnabled,
|
||||
debug: true
|
||||
});
|
||||
|
||||
$('#presentation>iframe').attr('id', preziPlayer.options.preziId);
|
||||
|
||||
preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) {
|
||||
console.log("prezi status", event.value);
|
||||
if (event.value == PreziPlayer.STATUS_CONTENT_READY) {
|
||||
if (jid != APP.xmpp.myJid())
|
||||
preziPlayer.flyToStep(currentSlide);
|
||||
}
|
||||
});
|
||||
|
||||
preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) {
|
||||
console.log("event value", event.value);
|
||||
APP.xmpp.addToPresence("preziSlide", event.value);
|
||||
});
|
||||
|
||||
$("#" + elementId).css( 'background-image',
|
||||
'url(../images/avatarprezi.png)');
|
||||
$("#" + elementId).click(
|
||||
function () {
|
||||
setPresentationVisible(true);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A presentation has been removed.
|
||||
*
|
||||
* @param event the event indicating the remove of a presentation
|
||||
* @param jid the jid for which the presentation was removed
|
||||
* @param the url of the presentation
|
||||
*/
|
||||
function presentationRemoved(event, jid, presUrl) {
|
||||
console.log('presentation removed', presUrl);
|
||||
var presId = getPresentationId(presUrl);
|
||||
setPresentationVisible(false);
|
||||
$('#participant_'
|
||||
+ Strophe.getResourceFromJid(jid)
|
||||
+ '_' + presId).remove();
|
||||
$('#presentation>iframe').remove();
|
||||
if (preziPlayer != null) {
|
||||
preziPlayer.destroy();
|
||||
preziPlayer = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates if the given string is an alphanumeric string.
|
||||
* Note that some special characters are also allowed (-, _ , /, &, ?, =, ;) for the
|
||||
* purpose of checking URIs.
|
||||
*/
|
||||
function isAlphanumeric(unsafeText) {
|
||||
var regex = /^[a-z0-9-_\/&\?=;]+$/i;
|
||||
return regex.test(unsafeText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the presentation id from the given url.
|
||||
*/
|
||||
function getPresentationId (presUrl) {
|
||||
var presIdTmp = presUrl.substring(presUrl.indexOf("prezi.com/") + 10);
|
||||
return presIdTmp.substring(0, presIdTmp.indexOf('/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the presentation width.
|
||||
*/
|
||||
function getPresentationWidth() {
|
||||
var availableWidth = UIUtil.getAvailableVideoWidth();
|
||||
var availableHeight = getPresentationHeihgt();
|
||||
|
||||
var aspectRatio = 16.0 / 9.0;
|
||||
if (availableHeight < availableWidth / aspectRatio) {
|
||||
availableWidth = Math.floor(availableHeight * aspectRatio);
|
||||
}
|
||||
return availableWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the presentation height.
|
||||
*/
|
||||
function getPresentationHeihgt() {
|
||||
var remoteVideos = $('#remoteVideos');
|
||||
return window.innerHeight - remoteVideos.outerHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the presentation iframe.
|
||||
*/
|
||||
function resize() {
|
||||
if ($('#presentation>iframe')) {
|
||||
$('#presentation>iframe').width(getPresentationWidth());
|
||||
$('#presentation>iframe').height(getPresentationHeihgt());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides a presentation.
|
||||
*/
|
||||
function setPresentationVisible(visible) {
|
||||
var prezi = $('#presentation>iframe');
|
||||
if (visible) {
|
||||
// Trigger the video.selected event to indicate a change in the
|
||||
// large video.
|
||||
$(document).trigger("video.selected", [true]);
|
||||
|
||||
$('#largeVideo').fadeOut(300);
|
||||
prezi.fadeIn(300, function() {
|
||||
prezi.css({opacity:'1'});
|
||||
ToolbarToggler.dockToolbar(true);
|
||||
VideoLayout.setLargeVideoVisible(false);
|
||||
});
|
||||
$('#activeSpeaker').css('visibility', 'hidden');
|
||||
}
|
||||
else {
|
||||
if (prezi.css('opacity') == '1') {
|
||||
prezi.fadeOut(300, function () {
|
||||
prezi.css({opacity:'0'});
|
||||
$('#reloadPresentation').css({display:'none'});
|
||||
$('#largeVideo').fadeIn(300, function() {
|
||||
VideoLayout.setLargeVideoVisible(true);
|
||||
ToolbarToggler.dockToolbar(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Presentation has been removed.
|
||||
*/
|
||||
$(document).bind('presentationremoved.muc', presentationRemoved);
|
||||
|
||||
/**
|
||||
* Presentation has been added.
|
||||
*/
|
||||
$(document).bind('presentationadded.muc', presentationAdded);
|
||||
|
||||
/*
|
||||
* Indicates presentation slide change.
|
||||
*/
|
||||
$(document).bind('gotoslide.muc', function (event, jid, presUrl, current) {
|
||||
if (preziPlayer && preziPlayer.getCurrentStep() != current) {
|
||||
preziPlayer.flyToStep(current);
|
||||
|
||||
var animationStepsArray = preziPlayer.getAnimationCountOnSteps();
|
||||
for (var i = 0; i < parseInt(animationStepsArray[current]); i++) {
|
||||
preziPlayer.flyToStep(current, i);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* On video selected event.
|
||||
*/
|
||||
$(document).bind('video.selected', function (event, isPresentation) {
|
||||
if (!isPresentation && $('#presentation>iframe')) {
|
||||
setPresentationVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
$(window).resize(function () {
|
||||
resize();
|
||||
});
|
||||
|
||||
module.exports = Prezi;
|
||||
@@ -33,21 +33,22 @@
|
||||
var message, item, player;
|
||||
try {
|
||||
message = JSON.parse(event.data);
|
||||
} catch (e) {}
|
||||
if (message.id && (player = PreziPlayer.players[message.id])){
|
||||
if (player.options.debug === true) {
|
||||
if (console && console.log) console.log('received', message);
|
||||
}
|
||||
if (message.type === "changes"){
|
||||
player.changesHandler(message);
|
||||
}
|
||||
for (var i=0; i<player.callbacks.length; i++) {
|
||||
item = player.callbacks[i];
|
||||
if (item && message.type === item.event){
|
||||
item.callback(message);
|
||||
if (message.id && (player = PreziPlayer.players[message.id])) {
|
||||
if (player.options.debug === true) {
|
||||
if (console && console.log)
|
||||
console.log('received', message);
|
||||
}
|
||||
if (message.type === "changes") {
|
||||
player.changesHandler(message);
|
||||
}
|
||||
for (var i = 0; i < player.callbacks.length; i++) {
|
||||
item = player.callbacks[i];
|
||||
if (item && message.type === item.event) {
|
||||
item.callback(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) { }
|
||||
};
|
||||
|
||||
function PreziPlayer(id, options) {
|
||||
@@ -289,3 +290,5 @@
|
||||
})();
|
||||
|
||||
})();
|
||||
|
||||
module.exports = PreziPlayer;
|
||||
@@ -1,3 +1,11 @@
|
||||
var Chat = require("./chat/Chat");
|
||||
var ContactList = require("./contactlist/ContactList");
|
||||
var Settings = require("./../../settings/Settings");
|
||||
var SettingsMenu = require("./settings/SettingsMenu");
|
||||
var VideoLayout = require("../videolayout/VideoLayout");
|
||||
var ToolbarToggler = require("../toolbars/ToolbarToggler");
|
||||
var UIUtil = require("../util/UIUtil");
|
||||
|
||||
/**
|
||||
* Toggler for the chat, contact list, settings menu, etc..
|
||||
*/
|
||||
@@ -22,10 +30,10 @@ var PanelToggler = (function(my) {
|
||||
var videospaceWidth = window.innerWidth - panelSize[0];
|
||||
var videospaceHeight = window.innerHeight;
|
||||
var videoSize
|
||||
= getVideoSize(null, null, videospaceWidth, videospaceHeight);
|
||||
= VideoLayout.getVideoSize(null, null, videospaceWidth, videospaceHeight);
|
||||
var videoWidth = videoSize[0];
|
||||
var videoHeight = videoSize[1];
|
||||
var videoPosition = getVideoPosition(videoWidth,
|
||||
var videoPosition = VideoLayout.getVideoPosition(videoWidth,
|
||||
videoHeight,
|
||||
videospaceWidth,
|
||||
videospaceHeight);
|
||||
@@ -103,7 +111,7 @@ var PanelToggler = (function(my) {
|
||||
* @param onClose function to be called if the window is going to be closed
|
||||
*/
|
||||
var toggle = function(object, selector, onOpenComplete, onOpen, onClose) {
|
||||
buttonClick(buttons[selector], "active");
|
||||
UIUtil.buttonClick(buttons[selector], "active");
|
||||
|
||||
if (object.isVisible()) {
|
||||
$("#toast-container").animate({
|
||||
@@ -133,7 +141,7 @@ var PanelToggler = (function(my) {
|
||||
|
||||
if(currentlyOpen) {
|
||||
var current = $(currentlyOpen);
|
||||
buttonClick(buttons[currentlyOpen], "active");
|
||||
UIUtil.buttonClick(buttons[currentlyOpen], "active");
|
||||
current.css('z-index', 4);
|
||||
setTimeout(function () {
|
||||
current.css('display', 'none');
|
||||
@@ -215,8 +223,9 @@ var PanelToggler = (function(my) {
|
||||
'#settingsmenu',
|
||||
null,
|
||||
function() {
|
||||
$('#setDisplayName').get(0).value = SettingsMenu.getDisplayName();
|
||||
$('#setEmail').get(0).value = SettingsMenu.getEmail();
|
||||
var settings = Settings.getSettings();
|
||||
$('#setDisplayName').get(0).value = settings.displayName;
|
||||
$('#setEmail').get(0).value = settings.email;
|
||||
},
|
||||
null);
|
||||
};
|
||||
@@ -243,3 +252,5 @@ var PanelToggler = (function(my) {
|
||||
return my;
|
||||
|
||||
}(PanelToggler || {}));
|
||||
|
||||
module.exports = PanelToggler;
|
||||
357
modules/UI/side_pannels/chat/Chat.js
Normal file
@@ -0,0 +1,357 @@
|
||||
/* global $, Util, nickname:true */
|
||||
var Replacement = require("./Replacement");
|
||||
var CommandsProcessor = require("./Commands");
|
||||
var ToolbarToggler = require("../../toolbars/ToolbarToggler");
|
||||
var smileys = require("./smileys.json").smileys;
|
||||
var NicknameHandler = require("../../util/NicknameHandler");
|
||||
var UIUtil = require("../../util/UIUtil");
|
||||
var UIEvents = require("../../../../service/UI/UIEvents");
|
||||
|
||||
var notificationInterval = false;
|
||||
var unreadMessages = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Shows/hides a visual notification, indicating that a message has arrived.
|
||||
*/
|
||||
function setVisualNotification(show) {
|
||||
var unreadMsgElement = document.getElementById('unreadMessages');
|
||||
var unreadMsgBottomElement
|
||||
= document.getElementById('bottomUnreadMessages');
|
||||
|
||||
var glower = $('#chatButton');
|
||||
var bottomGlower = $('#chatBottomButton');
|
||||
|
||||
if (unreadMessages) {
|
||||
unreadMsgElement.innerHTML = unreadMessages.toString();
|
||||
unreadMsgBottomElement.innerHTML = unreadMessages.toString();
|
||||
|
||||
ToolbarToggler.dockToolbar(true);
|
||||
|
||||
var chatButtonElement
|
||||
= document.getElementById('chatButton').parentNode;
|
||||
var leftIndent = (UIUtil.getTextWidth(chatButtonElement) -
|
||||
UIUtil.getTextWidth(unreadMsgElement)) / 2;
|
||||
var topIndent = (UIUtil.getTextHeight(chatButtonElement) -
|
||||
UIUtil.getTextHeight(unreadMsgElement)) / 2 - 3;
|
||||
|
||||
unreadMsgElement.setAttribute(
|
||||
'style',
|
||||
'top:' + topIndent +
|
||||
'; left:' + leftIndent + ';');
|
||||
|
||||
var chatBottomButtonElement
|
||||
= document.getElementById('chatBottomButton').parentNode;
|
||||
var bottomLeftIndent = (UIUtil.getTextWidth(chatBottomButtonElement) -
|
||||
UIUtil.getTextWidth(unreadMsgBottomElement)) / 2;
|
||||
var bottomTopIndent = (UIUtil.getTextHeight(chatBottomButtonElement) -
|
||||
UIUtil.getTextHeight(unreadMsgBottomElement)) / 2 - 2;
|
||||
|
||||
unreadMsgBottomElement.setAttribute(
|
||||
'style',
|
||||
'top:' + bottomTopIndent +
|
||||
'; left:' + bottomLeftIndent + ';');
|
||||
|
||||
|
||||
if (!glower.hasClass('icon-chat-simple')) {
|
||||
glower.removeClass('icon-chat');
|
||||
glower.addClass('icon-chat-simple');
|
||||
}
|
||||
}
|
||||
else {
|
||||
unreadMsgElement.innerHTML = '';
|
||||
unreadMsgBottomElement.innerHTML = '';
|
||||
glower.removeClass('icon-chat-simple');
|
||||
glower.addClass('icon-chat');
|
||||
}
|
||||
|
||||
if (show && !notificationInterval) {
|
||||
notificationInterval = window.setInterval(function () {
|
||||
glower.toggleClass('active');
|
||||
bottomGlower.toggleClass('active glowing');
|
||||
}, 800);
|
||||
}
|
||||
else if (!show && notificationInterval) {
|
||||
window.clearInterval(notificationInterval);
|
||||
notificationInterval = false;
|
||||
glower.removeClass('active');
|
||||
bottomGlower.removeClass('glowing');
|
||||
bottomGlower.addClass('active');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current time in the format it is shown to the user
|
||||
* @returns {string}
|
||||
*/
|
||||
function getCurrentTime() {
|
||||
var now = new Date();
|
||||
var hour = now.getHours();
|
||||
var minute = now.getMinutes();
|
||||
var second = now.getSeconds();
|
||||
if(hour.toString().length === 1) {
|
||||
hour = '0'+hour;
|
||||
}
|
||||
if(minute.toString().length === 1) {
|
||||
minute = '0'+minute;
|
||||
}
|
||||
if(second.toString().length === 1) {
|
||||
second = '0'+second;
|
||||
}
|
||||
return hour+':'+minute+':'+second;
|
||||
}
|
||||
|
||||
function toggleSmileys()
|
||||
{
|
||||
var smileys = $('#smileysContainer');
|
||||
if(!smileys.is(':visible')) {
|
||||
smileys.show("slide", { direction: "down", duration: 300});
|
||||
} else {
|
||||
smileys.hide("slide", { direction: "down", duration: 300});
|
||||
}
|
||||
$('#usermsg').focus();
|
||||
}
|
||||
|
||||
function addClickFunction(smiley, number) {
|
||||
smiley.onclick = function addSmileyToMessage() {
|
||||
var usermsg = $('#usermsg');
|
||||
var message = usermsg.val();
|
||||
message += smileys['smiley' + number];
|
||||
usermsg.val(message);
|
||||
usermsg.get(0).setSelectionRange(message.length, message.length);
|
||||
toggleSmileys();
|
||||
usermsg.focus();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the smileys container to the chat
|
||||
*/
|
||||
function addSmileys() {
|
||||
var smileysContainer = document.createElement('div');
|
||||
smileysContainer.id = 'smileysContainer';
|
||||
for(var i = 1; i <= 21; i++) {
|
||||
var smileyContainer = document.createElement('div');
|
||||
smileyContainer.id = 'smiley' + i;
|
||||
smileyContainer.className = 'smileyContainer';
|
||||
var smiley = document.createElement('img');
|
||||
smiley.src = 'images/smileys/smiley' + i + '.svg';
|
||||
smiley.className = 'smiley';
|
||||
addClickFunction(smiley, i);
|
||||
smileyContainer.appendChild(smiley);
|
||||
smileysContainer.appendChild(smileyContainer);
|
||||
}
|
||||
|
||||
$("#chatspace").append(smileysContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the chat conversation.
|
||||
*/
|
||||
function resizeChatConversation() {
|
||||
var msgareaHeight = $('#usermsg').outerHeight();
|
||||
var chatspace = $('#chatspace');
|
||||
var width = chatspace.width();
|
||||
var chat = $('#chatconversation');
|
||||
var smileys = $('#smileysarea');
|
||||
|
||||
smileys.height(msgareaHeight);
|
||||
$("#smileys").css('bottom', (msgareaHeight - 26) / 2);
|
||||
$('#smileysContainer').css('bottom', msgareaHeight);
|
||||
chat.width(width - 10);
|
||||
chat.height(window.innerHeight - 15 - msgareaHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat related user interface.
|
||||
*/
|
||||
var Chat = (function (my) {
|
||||
/**
|
||||
* Initializes chat related interface.
|
||||
*/
|
||||
my.init = function () {
|
||||
if(NicknameHandler.getNickname())
|
||||
Chat.setChatConversationMode(true);
|
||||
NicknameHandler.addListener(UIEvents.NICKNAME_CHANGED,
|
||||
function (nickname) {
|
||||
Chat.setChatConversationMode(true);
|
||||
});
|
||||
|
||||
$('#nickinput').keydown(function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
var val = UIUtil.escapeHtml(this.value);
|
||||
this.value = '';
|
||||
if (!NicknameHandler.getNickname()) {
|
||||
NicknameHandler.setNickname(val);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#usermsg').keydown(function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
var value = this.value;
|
||||
$('#usermsg').val('').trigger('autosize.resize');
|
||||
this.focus();
|
||||
var command = new CommandsProcessor(value);
|
||||
if(command.isCommand())
|
||||
{
|
||||
command.processCommand();
|
||||
}
|
||||
else
|
||||
{
|
||||
var message = UIUtil.escapeHtml(value);
|
||||
APP.xmpp.sendChatMessage(message, NicknameHandler.getNickname());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var onTextAreaResize = function () {
|
||||
resizeChatConversation();
|
||||
Chat.scrollChatToBottom();
|
||||
};
|
||||
$('#usermsg').autosize({callback: onTextAreaResize});
|
||||
|
||||
$("#chatspace").bind("shown",
|
||||
function () {
|
||||
unreadMessages = 0;
|
||||
setVisualNotification(false);
|
||||
});
|
||||
|
||||
addSmileys();
|
||||
};
|
||||
|
||||
/**
|
||||
* Appends the given message to the chat conversation.
|
||||
*/
|
||||
my.updateChatConversation = function (from, displayName, message) {
|
||||
var divClassName = '';
|
||||
|
||||
if (APP.xmpp.myJid() === from) {
|
||||
divClassName = "localuser";
|
||||
}
|
||||
else {
|
||||
divClassName = "remoteuser";
|
||||
|
||||
if (!Chat.isVisible()) {
|
||||
unreadMessages++;
|
||||
UIUtil.playSoundNotification('chatNotification');
|
||||
setVisualNotification(true);
|
||||
}
|
||||
}
|
||||
|
||||
// replace links and smileys
|
||||
// Strophe already escapes special symbols on sending,
|
||||
// so we escape here only tags to avoid double &
|
||||
var escMessage = message.replace(/</g, '<').
|
||||
replace(/>/g, '>').replace(/\n/g, '<br/>');
|
||||
var escDisplayName = UIUtil.escapeHtml(displayName);
|
||||
message = Replacement.processReplacements(escMessage);
|
||||
|
||||
var messageContainer =
|
||||
'<div class="chatmessage">'+
|
||||
'<img src="../images/chatArrow.svg" class="chatArrow">' +
|
||||
'<div class="username ' + divClassName +'">' + escDisplayName +
|
||||
'</div>' + '<div class="timestamp">' + getCurrentTime() +
|
||||
'</div>' + '<div class="usermessage">' + message + '</div>' +
|
||||
'</div>';
|
||||
|
||||
$('#chatconversation').append(messageContainer);
|
||||
$('#chatconversation').animate(
|
||||
{ scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
|
||||
};
|
||||
|
||||
/**
|
||||
* Appends error message to the conversation
|
||||
* @param errorMessage the received error message.
|
||||
* @param originalText the original message.
|
||||
*/
|
||||
my.chatAddError = function(errorMessage, originalText)
|
||||
{
|
||||
errorMessage = UIUtil.escapeHtml(errorMessage);
|
||||
originalText = UIUtil.escapeHtml(originalText);
|
||||
|
||||
$('#chatconversation').append(
|
||||
'<div class="errorMessage"><b>Error: </b>' + 'Your message' +
|
||||
(originalText? (' \"'+ originalText + '\"') : "") +
|
||||
' was not sent.' +
|
||||
(errorMessage? (' Reason: ' + errorMessage) : '') + '</div>');
|
||||
$('#chatconversation').animate(
|
||||
{ scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the subject to the UI
|
||||
* @param subject the subject
|
||||
*/
|
||||
my.chatSetSubject = function(subject)
|
||||
{
|
||||
if(subject)
|
||||
subject = subject.trim();
|
||||
$('#subject').html(Replacement.linkify(UIUtil.escapeHtml(subject)));
|
||||
if(subject === "")
|
||||
{
|
||||
$("#subject").css({display: "none"});
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#subject").css({display: "block"});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets the chat conversation mode.
|
||||
*/
|
||||
my.setChatConversationMode = function (isConversationMode) {
|
||||
if (isConversationMode) {
|
||||
$('#nickname').css({visibility: 'hidden'});
|
||||
$('#chatconversation').css({visibility: 'visible'});
|
||||
$('#usermsg').css({visibility: 'visible'});
|
||||
$('#smileysarea').css({visibility: 'visible'});
|
||||
$('#usermsg').focus();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizes the chat area.
|
||||
*/
|
||||
my.resizeChat = function () {
|
||||
var chatSize = require("../SidePanelToggler").getPanelSize();
|
||||
|
||||
$('#chatspace').width(chatSize[0]);
|
||||
$('#chatspace').height(chatSize[1]);
|
||||
|
||||
resizeChatConversation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates if the chat is currently visible.
|
||||
*/
|
||||
my.isVisible = function () {
|
||||
return $('#chatspace').is(":visible");
|
||||
};
|
||||
/**
|
||||
* Shows and hides the window with the smileys
|
||||
*/
|
||||
my.toggleSmileys = toggleSmileys;
|
||||
|
||||
/**
|
||||
* Scrolls chat to the bottom.
|
||||
*/
|
||||
my.scrollChatToBottom = function() {
|
||||
setTimeout(function () {
|
||||
$('#chatconversation').scrollTop(
|
||||
$('#chatconversation')[0].scrollHeight);
|
||||
}, 5);
|
||||
};
|
||||
|
||||
|
||||
return my;
|
||||
}(Chat || {}));
|
||||
module.exports = Chat;
|
||||
97
modules/UI/side_pannels/chat/Commands.js
Normal file
@@ -0,0 +1,97 @@
|
||||
var UIUtil = require("../../util/UIUtil");
|
||||
|
||||
/**
|
||||
* List with supported commands. The keys are the names of the commands and
|
||||
* the value is the function that processes the message.
|
||||
* @type {{String: function}}
|
||||
*/
|
||||
var commands = {
|
||||
"topic" : processTopic
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the command from the message.
|
||||
* @param message the received message
|
||||
* @returns {string} the command
|
||||
*/
|
||||
function getCommand(message)
|
||||
{
|
||||
if(message)
|
||||
{
|
||||
for(var command in commands)
|
||||
{
|
||||
if(message.indexOf("/" + command) == 0)
|
||||
return command;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes the data for topic command.
|
||||
* @param commandArguments the arguments of the topic command.
|
||||
*/
|
||||
function processTopic(commandArguments)
|
||||
{
|
||||
var topic = UIUtil.escapeHtml(commandArguments);
|
||||
APP.xmpp.setSubject(topic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new CommandProccessor instance from a message that
|
||||
* handles commands received via chat messages.
|
||||
* @param message the message
|
||||
* @constructor
|
||||
*/
|
||||
function CommandsProcessor(message)
|
||||
{
|
||||
|
||||
|
||||
var command = getCommand(message);
|
||||
|
||||
/**
|
||||
* Returns the name of the command.
|
||||
* @returns {String} the command
|
||||
*/
|
||||
this.getCommand = function()
|
||||
{
|
||||
return command;
|
||||
};
|
||||
|
||||
|
||||
var messageArgument = message.substr(command.length + 2);
|
||||
|
||||
/**
|
||||
* Returns the arguments of the command.
|
||||
* @returns {string}
|
||||
*/
|
||||
this.getArgument = function()
|
||||
{
|
||||
return messageArgument;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this instance is valid command or not.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
CommandsProcessor.prototype.isCommand = function()
|
||||
{
|
||||
if(this.getCommand())
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes the command.
|
||||
*/
|
||||
CommandsProcessor.prototype.processCommand = function()
|
||||
{
|
||||
if(!this.isCommand())
|
||||
return;
|
||||
|
||||
commands[this.getCommand()](this.getArgument());
|
||||
|
||||
};
|
||||
|
||||
module.exports = CommandsProcessor;
|
||||
@@ -1,3 +1,4 @@
|
||||
var Smileys = require("./smileys.json");
|
||||
/**
|
||||
* Processes links and smileys in "body"
|
||||
*/
|
||||
@@ -44,6 +45,7 @@ function smilify(body)
|
||||
return body;
|
||||
}
|
||||
|
||||
var regexs = Smileys["regexs"];
|
||||
for(var smiley in regexs) {
|
||||
if(regexs.hasOwnProperty(smiley)) {
|
||||
body = body.replace(regexs[smiley],
|
||||
@@ -53,3 +55,8 @@ function smilify(body)
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
processReplacements: processReplacements,
|
||||
linkify: linkify
|
||||
};
|
||||
48
modules/UI/side_pannels/chat/smileys.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"smileys": {
|
||||
"smiley1": ":)",
|
||||
"smiley2": ":(",
|
||||
"smiley3": ":D",
|
||||
"smiley4": "(y)",
|
||||
"smiley5": " :P",
|
||||
"smiley6": "(wave)",
|
||||
"smiley7": "(blush)",
|
||||
"smiley8": "(chuckle)",
|
||||
"smiley9": "(shocked)",
|
||||
"smiley10": ":*",
|
||||
"smiley11": "(n)",
|
||||
"smiley12": "(search)",
|
||||
"smiley13": " <3",
|
||||
"smiley14": "(oops)",
|
||||
"smiley15": "(angry)",
|
||||
"smiley16": "(angel)",
|
||||
"smiley17": "(sick)",
|
||||
"smiley18": ";(",
|
||||
"smiley19": "(bomb)",
|
||||
"smiley20": "(clap)",
|
||||
"smiley21": " ;)"
|
||||
},
|
||||
"regexs": {
|
||||
"smiley2": /(:-\(\(|:-\(|:\(\(|:\(|\(sad\))/gi,
|
||||
"smiley3": /(:-\)\)|:\)\)|\(lol\)|:-D|:D)/gi,
|
||||
"smiley1": /(:-\)|:\))/gi,
|
||||
"smiley4": /(\(y\)|\(Y\)|\(ok\))/gi,
|
||||
"smiley5": /(:-P|:P|:-p|:p)/gi,
|
||||
"smiley6": /(\(wave\))/gi,
|
||||
"smiley7": /(\(blush\))/gi,
|
||||
"smiley8": /(\(chuckle\))/gi,
|
||||
"smiley9": /(:-0|\(shocked\))/gi,
|
||||
"smiley10": /(:-\*|:\*|\(kiss\))/gi,
|
||||
"smiley11": /(\(n\))/gi,
|
||||
"smiley12": /(\(search\))/g,
|
||||
"smiley13": /(<3|<3|&lt;3|\(L\)|\(l\)|\(H\)|\(h\))/gi,
|
||||
"smiley14": /(\(oops\))/gi,
|
||||
"smiley15": /(\(angry\))/gi,
|
||||
"smiley16": /(\(angel\))/gi,
|
||||
"smiley17": /(\(sick\))/gi,
|
||||
"smiley18": /(;-\(\(|;\(\(|;-\(|;\(|:"\(|:"-\(|:~-\(|:~\(|\(upset\))/gi,
|
||||
"smiley19": /(\(bomb\))/gi,
|
||||
"smiley20": /(\(clap\))/gi,
|
||||
"smiley21": /(;-\)|;\)|;-\)\)|;\)\)|;-D|;D|\(wink\))/gi
|
||||
}
|
||||
}
|
||||
189
modules/UI/side_pannels/contactlist/ContactList.js
Normal file
@@ -0,0 +1,189 @@
|
||||
|
||||
var numberOfContacts = 0;
|
||||
var notificationInterval;
|
||||
|
||||
/**
|
||||
* Updates the number of participants in the contact list button and sets
|
||||
* the glow
|
||||
* @param delta indicates whether a new user has joined (1) or someone has
|
||||
* left(-1)
|
||||
*/
|
||||
function updateNumberOfParticipants(delta) {
|
||||
//when the user is alone we don't show the number of participants
|
||||
if(numberOfContacts === 0) {
|
||||
$("#numberOfParticipants").text('');
|
||||
numberOfContacts += delta;
|
||||
} else if(numberOfContacts !== 0 && !ContactList.isVisible()) {
|
||||
ContactList.setVisualNotification(true);
|
||||
numberOfContacts += delta;
|
||||
$("#numberOfParticipants").text(numberOfContacts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the avatar element.
|
||||
*
|
||||
* @return the newly created avatar element
|
||||
*/
|
||||
function createAvatar(id) {
|
||||
var avatar = document.createElement('img');
|
||||
avatar.className = "icon-avatar avatar";
|
||||
avatar.src = "https://www.gravatar.com/avatar/" + id + "?d=wavatar&size=30";
|
||||
|
||||
return avatar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the display name paragraph.
|
||||
*
|
||||
* @param displayName the display name to set
|
||||
*/
|
||||
function createDisplayNameParagraph(key, displayName) {
|
||||
var p = document.createElement('p');
|
||||
if(displayName)
|
||||
p.innerText = displayName;
|
||||
else if(key)
|
||||
{
|
||||
p.setAttribute("data-i18n",key);
|
||||
p.innerText = APP.translation.translateString(key);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
function stopGlowing(glower) {
|
||||
window.clearInterval(notificationInterval);
|
||||
notificationInterval = false;
|
||||
glower.removeClass('glowing');
|
||||
if (!ContactList.isVisible()) {
|
||||
glower.removeClass('active');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Contact list.
|
||||
*/
|
||||
var ContactList = {
|
||||
/**
|
||||
* Indicates if the chat is currently visible.
|
||||
*
|
||||
* @return <tt>true</tt> if the chat is currently visible, <tt>false</tt> -
|
||||
* otherwise
|
||||
*/
|
||||
isVisible: function () {
|
||||
return $('#contactlist').is(":visible");
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a contact for the given peerJid if such doesn't yet exist.
|
||||
*
|
||||
* @param peerJid the peerJid corresponding to the contact
|
||||
* @param id the user's email or userId used to get the user's avatar
|
||||
*/
|
||||
ensureAddContact: function (peerJid, id) {
|
||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
|
||||
|
||||
if (!contact || contact.length <= 0)
|
||||
ContactList.addContact(peerJid, id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a contact for the given peer jid.
|
||||
*
|
||||
* @param peerJid the jid of the contact to add
|
||||
* @param id the email or userId of the user
|
||||
*/
|
||||
addContact: function (peerJid, id) {
|
||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
var contactlist = $('#contactlist>ul');
|
||||
|
||||
var newContact = document.createElement('li');
|
||||
newContact.id = resourceJid;
|
||||
newContact.className = "clickable";
|
||||
newContact.onclick = function (event) {
|
||||
if (event.currentTarget.className === "clickable") {
|
||||
$(ContactList).trigger('contactclicked', [peerJid]);
|
||||
}
|
||||
};
|
||||
|
||||
newContact.appendChild(createAvatar(id));
|
||||
newContact.appendChild(createDisplayNameParagraph("participant"));
|
||||
|
||||
var clElement = contactlist.get(0);
|
||||
|
||||
if (resourceJid === APP.xmpp.myResource()
|
||||
&& $('#contactlist>ul .title')[0].nextSibling.nextSibling) {
|
||||
clElement.insertBefore(newContact,
|
||||
$('#contactlist>ul .title')[0].nextSibling.nextSibling);
|
||||
}
|
||||
else {
|
||||
clElement.appendChild(newContact);
|
||||
}
|
||||
updateNumberOfParticipants(1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes a contact for the given peer jid.
|
||||
*
|
||||
* @param peerJid the peerJid corresponding to the contact to remove
|
||||
*/
|
||||
removeContact: function (peerJid) {
|
||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
|
||||
|
||||
if (contact && contact.length > 0) {
|
||||
var contactlist = $('#contactlist>ul');
|
||||
|
||||
contactlist.get(0).removeChild(contact.get(0));
|
||||
|
||||
updateNumberOfParticipants(-1);
|
||||
}
|
||||
},
|
||||
|
||||
setVisualNotification: function (show, stopGlowingIn) {
|
||||
var glower = $('#contactListButton');
|
||||
|
||||
if (show && !notificationInterval) {
|
||||
notificationInterval = window.setInterval(function () {
|
||||
glower.toggleClass('active glowing');
|
||||
}, 800);
|
||||
}
|
||||
else if (!show && notificationInterval) {
|
||||
stopGlowing(glower);
|
||||
}
|
||||
if (stopGlowingIn) {
|
||||
setTimeout(function () {
|
||||
stopGlowing(glower);
|
||||
}, stopGlowingIn);
|
||||
}
|
||||
},
|
||||
|
||||
setClickable: function (resourceJid, isClickable) {
|
||||
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
|
||||
if (isClickable) {
|
||||
contact.addClass('clickable');
|
||||
} else {
|
||||
contact.removeClass('clickable');
|
||||
}
|
||||
},
|
||||
|
||||
onDisplayNameChange: function (peerJid, displayName) {
|
||||
if (peerJid === 'localVideoContainer')
|
||||
peerJid = APP.xmpp.myJid();
|
||||
|
||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
var contactName = $('#contactlist #' + resourceJid + '>p');
|
||||
|
||||
if (contactName && displayName && displayName.length > 0)
|
||||
contactName.html(displayName);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ContactList;
|
||||
80
modules/UI/side_pannels/settings/SettingsMenu.js
Normal file
@@ -0,0 +1,80 @@
|
||||
var Avatar = require("../../avatar/Avatar");
|
||||
var Settings = require("./../../../settings/Settings");
|
||||
var UIUtil = require("../../util/UIUtil");
|
||||
var languages = require("../../../../service/translation/languages");
|
||||
|
||||
function generateLanguagesSelectBox()
|
||||
{
|
||||
var currentLang = APP.translation.getCurrentLanguage();
|
||||
var html = "<select id=\"languages_selectbox\">";
|
||||
var langArray = languages.getLanguages();
|
||||
for(var i = 0; i < langArray.length; i++)
|
||||
{
|
||||
var lang = langArray[i];
|
||||
html += "<option ";
|
||||
if(lang === currentLang)
|
||||
html += "selected ";
|
||||
html += "value=\"" + lang + "\" data-i18n='languages:" + lang + "'>";
|
||||
html += "</option>";
|
||||
|
||||
}
|
||||
|
||||
return html + "</select>";
|
||||
}
|
||||
|
||||
|
||||
var SettingsMenu = {
|
||||
|
||||
init: function () {
|
||||
$("#updateSettings").before(generateLanguagesSelectBox());
|
||||
APP.translation.translateElement($("#languages_selectbox"));
|
||||
$('#settingsmenu>input').keyup(function(event){
|
||||
if(event.keyCode === 13) {//enter
|
||||
SettingsMenu.update();
|
||||
}
|
||||
});
|
||||
|
||||
$("#updateSettings").click(function () {
|
||||
SettingsMenu.update();
|
||||
});
|
||||
},
|
||||
|
||||
update: function() {
|
||||
var newDisplayName = UIUtil.escapeHtml($('#setDisplayName').get(0).value);
|
||||
var newEmail = UIUtil.escapeHtml($('#setEmail').get(0).value);
|
||||
|
||||
if(newDisplayName) {
|
||||
var displayName = Settings.setDisplayName(newDisplayName);
|
||||
APP.xmpp.addToPresence("displayName", displayName, true);
|
||||
}
|
||||
|
||||
var language = $("#languages_selectbox").val();
|
||||
APP.translation.setLanguage(language);
|
||||
Settings.setLanguage(language);
|
||||
|
||||
APP.xmpp.addToPresence("email", newEmail);
|
||||
var email = Settings.setEmail(newEmail);
|
||||
|
||||
|
||||
Avatar.setUserAvatar(APP.xmpp.myJid(), email);
|
||||
},
|
||||
|
||||
isVisible: function() {
|
||||
return $('#settingsmenu').is(':visible');
|
||||
},
|
||||
|
||||
setDisplayName: function(newDisplayName) {
|
||||
var displayName = Settings.setDisplayName(newDisplayName);
|
||||
$('#setDisplayName').get(0).value = displayName;
|
||||
},
|
||||
|
||||
onDisplayNameChange: function(peerJid, newDisplayName) {
|
||||
if(peerJid === 'localVideoContainer' ||
|
||||
peerJid === APP.xmpp.myJid()) {
|
||||
this.setDisplayName(newDisplayName);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = SettingsMenu;
|
||||
@@ -1,4 +1,23 @@
|
||||
var PanelToggler = require("../side_pannels/SidePanelToggler");
|
||||
|
||||
var buttonHandlers = {
|
||||
"bottom_toolbar_contact_list": function () {
|
||||
BottomToolbar.toggleContactList();
|
||||
},
|
||||
"bottom_toolbar_film_strip": function () {
|
||||
BottomToolbar.toggleFilmStrip();
|
||||
},
|
||||
"bottom_toolbar_chat": function () {
|
||||
BottomToolbar.toggleChat();
|
||||
}
|
||||
};
|
||||
|
||||
var BottomToolbar = (function (my) {
|
||||
my.init = function () {
|
||||
for(var k in buttonHandlers)
|
||||
$("#" + k).click(buttonHandlers[k]);
|
||||
};
|
||||
|
||||
my.toggleChat = function() {
|
||||
PanelToggler.toggleChat();
|
||||
};
|
||||
@@ -20,3 +39,5 @@ var BottomToolbar = (function (my) {
|
||||
|
||||
return my;
|
||||
}(BottomToolbar || {}));
|
||||
|
||||
module.exports = BottomToolbar;
|
||||
633
modules/UI/toolbars/Toolbar.js
Normal file
@@ -0,0 +1,633 @@
|
||||
/* global APP,$, buttonClick, config, lockRoom,
|
||||
setSharedKey, Util */
|
||||
var messageHandler = require("../util/MessageHandler");
|
||||
var BottomToolbar = require("./BottomToolbar");
|
||||
var Prezi = require("../prezi/Prezi");
|
||||
var Etherpad = require("../etherpad/Etherpad");
|
||||
var PanelToggler = require("../side_pannels/SidePanelToggler");
|
||||
var Authentication = require("../authentication/Authentication");
|
||||
var UIUtil = require("../util/UIUtil");
|
||||
var AuthenticationEvents
|
||||
= require("../../../service/authentication/AuthenticationEvents");
|
||||
|
||||
var roomUrl = null;
|
||||
var sharedKey = '';
|
||||
var UI = null;
|
||||
|
||||
var buttonHandlers =
|
||||
{
|
||||
"toolbar_button_mute": function () {
|
||||
return APP.UI.toggleAudio();
|
||||
},
|
||||
"toolbar_button_camera": function () {
|
||||
return APP.UI.toggleVideo();
|
||||
},
|
||||
/*"toolbar_button_authentication": function () {
|
||||
return Toolbar.authenticateClicked();
|
||||
},*/
|
||||
"toolbar_button_record": function () {
|
||||
return toggleRecording();
|
||||
},
|
||||
"toolbar_button_security": function () {
|
||||
return Toolbar.openLockDialog();
|
||||
},
|
||||
"toolbar_button_link": function () {
|
||||
return Toolbar.openLinkDialog();
|
||||
},
|
||||
"toolbar_button_chat": function () {
|
||||
return BottomToolbar.toggleChat();
|
||||
},
|
||||
"toolbar_button_prezi": function () {
|
||||
return Prezi.openPreziDialog();
|
||||
},
|
||||
"toolbar_button_etherpad": function () {
|
||||
return Etherpad.toggleEtherpad(0);
|
||||
},
|
||||
"toolbar_button_desktopsharing": function () {
|
||||
return APP.desktopsharing.toggleScreenSharing();
|
||||
},
|
||||
"toolbar_button_fullScreen": function()
|
||||
{
|
||||
UIUtil.buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");
|
||||
return Toolbar.toggleFullScreen();
|
||||
},
|
||||
"toolbar_button_sip": function () {
|
||||
return callSipButtonClicked();
|
||||
},
|
||||
"toolbar_button_settings": function () {
|
||||
PanelToggler.toggleSettingsMenu();
|
||||
},
|
||||
"toolbar_button_hangup": function () {
|
||||
return hangup();
|
||||
},
|
||||
"toolbar_button_login": function () {
|
||||
Toolbar.authenticateClicked();
|
||||
},
|
||||
"toolbar_button_logout": function () {
|
||||
// Ask for confirmation
|
||||
messageHandler.openTwoButtonDialog(
|
||||
"dialog.logoutTitle",
|
||||
null,
|
||||
"dialog.logoutQuestion",
|
||||
null,
|
||||
false,
|
||||
"dialog.Yes",
|
||||
function (evt, yes) {
|
||||
if (yes) {
|
||||
APP.xmpp.logout(function (url) {
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
} else {
|
||||
hangup();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function hangup() {
|
||||
APP.xmpp.disposeConference();
|
||||
if(config.enableWelcomePage)
|
||||
{
|
||||
setTimeout(function()
|
||||
{
|
||||
window.localStorage.welcomePageDisabled = false;
|
||||
window.location.pathname = "/";
|
||||
}, 10000);
|
||||
|
||||
}
|
||||
|
||||
var title = APP.translation.generateTranslatonHTML(
|
||||
"dialog.sessTerminated");
|
||||
var msg = APP.translation.generateTranslatonHTML(
|
||||
"dialog.hungUp");
|
||||
var button = APP.translation.generateTranslatonHTML(
|
||||
"dialog.joinAgain");
|
||||
var buttons = {};
|
||||
buttons.joinAgain = {title: button, value: true};
|
||||
|
||||
UI.messageHandler.openDialog(
|
||||
title,
|
||||
msg,
|
||||
true,
|
||||
buttons,
|
||||
function(event, value, message, formVals)
|
||||
{
|
||||
window.location.reload();
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts or stops the recording for the conference.
|
||||
*/
|
||||
|
||||
function toggleRecording() {
|
||||
APP.xmpp.toggleRecording(function (callback) {
|
||||
var msg = APP.translation.generateTranslatonHTML(
|
||||
"dialog.recordingToken");
|
||||
var token = APP.translation.translateString("dialog.token");
|
||||
APP.UI.messageHandler.openTwoButtonDialog(null, null, null,
|
||||
'<h2>' + msg + '</h2>' +
|
||||
'<input id="recordingToken" type="text" ' +
|
||||
' data-i18n="[placeholder]dialog.token" ' +
|
||||
'placeholder="' + token + '" autofocus>',
|
||||
false,
|
||||
"dialog.Save",
|
||||
function (e, v, m, f) {
|
||||
if (v) {
|
||||
var token = document.getElementById('recordingToken');
|
||||
|
||||
if (token.value) {
|
||||
callback(UIUtil.escapeHtml(token.value));
|
||||
}
|
||||
}
|
||||
},
|
||||
function (event) {
|
||||
document.getElementById('recordingToken').focus();
|
||||
},
|
||||
function () {
|
||||
}
|
||||
);
|
||||
}, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks / unlocks the room.
|
||||
*/
|
||||
function lockRoom(lock) {
|
||||
var currentSharedKey = '';
|
||||
if (lock)
|
||||
currentSharedKey = sharedKey;
|
||||
|
||||
APP.xmpp.lockRoom(currentSharedKey, function (res) {
|
||||
// password is required
|
||||
if (sharedKey)
|
||||
{
|
||||
console.log('set room password');
|
||||
Toolbar.lockLockButton();
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log('removed room password');
|
||||
Toolbar.unlockLockButton();
|
||||
}
|
||||
}, function (err) {
|
||||
console.warn('setting password failed', err);
|
||||
messageHandler.showError("dialog.lockTitle",
|
||||
"dialog.lockMessage");
|
||||
Toolbar.setSharedKey('');
|
||||
}, function () {
|
||||
console.warn('room passwords not supported');
|
||||
messageHandler.showError("dialog.warning",
|
||||
"dialog.passwordNotSupported");
|
||||
Toolbar.setSharedKey('');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Invite participants to conference.
|
||||
*/
|
||||
function inviteParticipants() {
|
||||
if (roomUrl === null)
|
||||
return;
|
||||
|
||||
var sharedKeyText = "";
|
||||
if (sharedKey && sharedKey.length > 0) {
|
||||
sharedKeyText =
|
||||
APP.translation.translateString("email.sharedKey",
|
||||
{sharedKey: sharedKey});
|
||||
sharedKeyText = sharedKeyText.replace(/\n/g, "%0D%0A");
|
||||
}
|
||||
|
||||
var supportedBrowsers = "Chromium, Google Chrome " +
|
||||
APP.translation.translateString("email.and") + " Opera";
|
||||
var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1);
|
||||
var subject = APP.translation.translateString("email.subject",
|
||||
{appName:interfaceConfig.APP_NAME, conferenceName: conferenceName});
|
||||
var body = APP.translation.translateString("email.body",
|
||||
{appName:interfaceConfig.APP_NAME, sharedKeyText: sharedKeyText,
|
||||
roomUrl: roomUrl, supportedBrowsers: supportedBrowsers});
|
||||
body = body.replace(/\n/g, "%0D%0A");
|
||||
|
||||
if (window.localStorage.displayname) {
|
||||
body += "%0D%0A%0D%0A" + window.localStorage.displayname;
|
||||
}
|
||||
|
||||
if (interfaceConfig.INVITATION_POWERED_BY) {
|
||||
body += "%0D%0A%0D%0A--%0D%0Apowered by jitsi.org";
|
||||
}
|
||||
|
||||
window.open("mailto:?subject=" + subject + "&body=" + body, '_blank');
|
||||
}
|
||||
|
||||
function callSipButtonClicked()
|
||||
{
|
||||
var defaultNumber
|
||||
= config.defaultSipNumber ? config.defaultSipNumber : '';
|
||||
|
||||
var sipMsg = APP.translation.generateTranslatonHTML(
|
||||
"dialog.sipMsg");
|
||||
messageHandler.openTwoButtonDialog(null, null, null,
|
||||
'<h2>' + sipMsg + '</h2>' +
|
||||
'<input id="sipNumber" type="text"' +
|
||||
' value="' + defaultNumber + '" autofocus>',
|
||||
false,
|
||||
"dialog.Dial",
|
||||
function (e, v, m, f) {
|
||||
if (v) {
|
||||
var numberInput = document.getElementById('sipNumber');
|
||||
if (numberInput.value) {
|
||||
APP.xmpp.dial(numberInput.value, 'fromnumber',
|
||||
UI.getRoomName(), sharedKey);
|
||||
}
|
||||
}
|
||||
},
|
||||
function (event) {
|
||||
document.getElementById('sipNumber').focus();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var Toolbar = (function (my) {
|
||||
|
||||
my.init = function (ui) {
|
||||
for(var k in buttonHandlers)
|
||||
$("#" + k).click(buttonHandlers[k]);
|
||||
UI = ui;
|
||||
// Update login info
|
||||
APP.xmpp.addListener(
|
||||
AuthenticationEvents.IDENTITY_UPDATED,
|
||||
function (authenticationEnabled, userIdentity) {
|
||||
|
||||
var loggedIn = false;
|
||||
if (userIdentity) {
|
||||
loggedIn = true;
|
||||
}
|
||||
|
||||
//FIXME: XMPP authentication need improvements for "live" login
|
||||
if (!APP.xmpp.isExternalAuthEnabled() && !loggedIn)
|
||||
{
|
||||
authenticationEnabled = false;
|
||||
}
|
||||
|
||||
Toolbar.showAuthenticateButton(authenticationEnabled);
|
||||
|
||||
if (authenticationEnabled) {
|
||||
Toolbar.setAuthenticatedIdentity(userIdentity);
|
||||
|
||||
Toolbar.showLoginButton(!loggedIn);
|
||||
Toolbar.showLogoutButton(loggedIn);
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets shared key
|
||||
* @param sKey the shared key
|
||||
*/
|
||||
my.setSharedKey = function (sKey) {
|
||||
sharedKey = sKey;
|
||||
};
|
||||
|
||||
my.authenticateClicked = function () {
|
||||
Authentication.focusAuthenticationWindow();
|
||||
// Get authentication URL
|
||||
if (!APP.xmpp.getMUCJoined()) {
|
||||
APP.xmpp.getLoginUrl(UI.getRoomName(), function (url) {
|
||||
// If conference has not been started yet - redirect to login page
|
||||
window.location.href = url;
|
||||
});
|
||||
} else {
|
||||
APP.xmpp.getPopupLoginUrl(UI.getRoomName(), function (url) {
|
||||
// Otherwise - open popup with authentication URL
|
||||
var authenticationWindow = Authentication.createAuthenticationWindow(
|
||||
function () {
|
||||
// On popup closed - retry room allocation
|
||||
APP.xmpp.allocateConferenceFocus(
|
||||
APP.UI.getRoomName(),
|
||||
function () { console.info("AUTH DONE"); }
|
||||
);
|
||||
}, url);
|
||||
if (!authenticationWindow) {
|
||||
messageHandler.openMessageDialog(
|
||||
null, "dialog.popupError");
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the room invite url.
|
||||
*/
|
||||
my.updateRoomUrl = function (newRoomUrl) {
|
||||
roomUrl = newRoomUrl;
|
||||
|
||||
// If the invite dialog has been already opened we update the information.
|
||||
var inviteLink = document.getElementById('inviteLinkRef');
|
||||
if (inviteLink) {
|
||||
inviteLink.value = roomUrl;
|
||||
inviteLink.select();
|
||||
document.getElementById('jqi_state0_buttonInvite').disabled = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Disables and enables some of the buttons.
|
||||
*/
|
||||
my.setupButtonsFromConfig = function () {
|
||||
if (config.disablePrezi)
|
||||
{
|
||||
$("#prezi_button").css({display: "none"});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the lock room dialog.
|
||||
*/
|
||||
my.openLockDialog = function () {
|
||||
// Only the focus is able to set a shared key.
|
||||
if (!APP.xmpp.isModerator()) {
|
||||
if (sharedKey) {
|
||||
messageHandler.openMessageDialog(null,
|
||||
"dialog.passwordError");
|
||||
} else {
|
||||
messageHandler.openMessageDialog(null, "dialog.passwordError2");
|
||||
}
|
||||
} else {
|
||||
if (sharedKey) {
|
||||
messageHandler.openTwoButtonDialog(null, null,
|
||||
"dialog.passwordCheck",
|
||||
null,
|
||||
false,
|
||||
"dialog.Remove",
|
||||
function (e, v) {
|
||||
if (v) {
|
||||
Toolbar.setSharedKey('');
|
||||
lockRoom(false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var msg = APP.translation.generateTranslatonHTML(
|
||||
"dialog.passwordMsg");
|
||||
var yourPassword = APP.translation.translateString(
|
||||
"dialog.yourPassword");
|
||||
messageHandler.openTwoButtonDialog(null, null, null,
|
||||
'<h2>' + msg + '</h2>' +
|
||||
'<input id="lockKey" type="text"' +
|
||||
' data-i18n="[placeholder]dialog.yourPassword" ' +
|
||||
'placeholder="' + yourPassword + '" autofocus>',
|
||||
false,
|
||||
"dialog.Save",
|
||||
function (e, v) {
|
||||
if (v) {
|
||||
var lockKey = document.getElementById('lockKey');
|
||||
|
||||
if (lockKey.value) {
|
||||
Toolbar.setSharedKey(UIUtil.escapeHtml(lockKey.value));
|
||||
lockRoom(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
function () {
|
||||
document.getElementById('lockKey').focus();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the invite link dialog.
|
||||
*/
|
||||
my.openLinkDialog = function () {
|
||||
var inviteAttreibutes;
|
||||
|
||||
if (roomUrl === null) {
|
||||
inviteAttreibutes = 'data-i18n="[value]roomUrlDefaultMsg" value="' +
|
||||
APP.translation.translateString("roomUrlDefaultMsg") + '"';
|
||||
} else {
|
||||
inviteAttreibutes = "value=\"" + encodeURI(roomUrl) + "\"";
|
||||
}
|
||||
messageHandler.openTwoButtonDialog("dialog.shareLink",
|
||||
null, null,
|
||||
'<input id="inviteLinkRef" type="text" ' +
|
||||
inviteAttreibutes + ' onclick="this.select();" readonly>',
|
||||
false,
|
||||
"dialog.Invite",
|
||||
function (e, v) {
|
||||
if (v) {
|
||||
if (roomUrl) {
|
||||
inviteParticipants();
|
||||
}
|
||||
}
|
||||
},
|
||||
function () {
|
||||
if (roomUrl) {
|
||||
document.getElementById('inviteLinkRef').select();
|
||||
} else {
|
||||
document.getElementById('jqi_state0_buttonInvite')
|
||||
.disabled = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the settings dialog.
|
||||
*/
|
||||
my.openSettingsDialog = function () {
|
||||
var settings1 = APP.translation.generateTranslatonHTML(
|
||||
"dialog.settings1");
|
||||
var settings2 = APP.translation.generateTranslatonHTML(
|
||||
"dialog.settings2");
|
||||
var settings3 = APP.translation.generateTranslatonHTML(
|
||||
"dialog.settings3");
|
||||
|
||||
var yourPassword = APP.translation.translateString(
|
||||
"dialog.yourPassword");
|
||||
|
||||
messageHandler.openTwoButtonDialog(null,
|
||||
'<h2>' + settings1 + '</h2>' +
|
||||
'<input type="checkbox" id="initMuted">' +
|
||||
settings2 + '<br/>' +
|
||||
'<input type="checkbox" id="requireNicknames">' +
|
||||
settings3 +
|
||||
'<input id="lockKey" type="text" placeholder="' + yourPassword +
|
||||
'" data-i18n="[placeholder]dialog.yourPassword" autofocus>',
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
"dialog.Save",
|
||||
function () {
|
||||
document.getElementById('lockKey').focus();
|
||||
},
|
||||
function (e, v) {
|
||||
if (v) {
|
||||
if ($('#initMuted').is(":checked")) {
|
||||
// it is checked
|
||||
}
|
||||
|
||||
if ($('#requireNicknames').is(":checked")) {
|
||||
// it is checked
|
||||
}
|
||||
/*
|
||||
var lockKey = document.getElementById('lockKey');
|
||||
|
||||
if (lockKey.value) {
|
||||
setSharedKey(lockKey.value);
|
||||
lockRoom(true);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles the application in and out of full screen mode
|
||||
* (a.k.a. presentation mode in Chrome).
|
||||
*/
|
||||
my.toggleFullScreen = function () {
|
||||
var fsElement = document.documentElement;
|
||||
|
||||
if (!document.mozFullScreen && !document.webkitIsFullScreen) {
|
||||
//Enter Full Screen
|
||||
if (fsElement.mozRequestFullScreen) {
|
||||
fsElement.mozRequestFullScreen();
|
||||
}
|
||||
else {
|
||||
fsElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
|
||||
}
|
||||
} else {
|
||||
//Exit Full Screen
|
||||
if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else {
|
||||
document.webkitCancelFullScreen();
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Unlocks the lock button state.
|
||||
*/
|
||||
my.unlockLockButton = function () {
|
||||
if ($("#lockIcon").hasClass("icon-security-locked"))
|
||||
UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
|
||||
};
|
||||
/**
|
||||
* Updates the lock button state to locked.
|
||||
*/
|
||||
my.lockLockButton = function () {
|
||||
if ($("#lockIcon").hasClass("icon-security"))
|
||||
UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows or hides authentication button
|
||||
* @param show <tt>true</tt> to show or <tt>false</tt> to hide
|
||||
*/
|
||||
my.showAuthenticateButton = function (show) {
|
||||
if (show) {
|
||||
$('#authentication').css({display: "inline"});
|
||||
}
|
||||
else {
|
||||
$('#authentication').css({display: "none"});
|
||||
}
|
||||
};
|
||||
|
||||
// Shows or hides the 'recording' button.
|
||||
my.showRecordingButton = function (show) {
|
||||
if (!config.enableRecording) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (show) {
|
||||
$('#recording').css({display: "inline"});
|
||||
}
|
||||
else {
|
||||
$('#recording').css({display: "none"});
|
||||
}
|
||||
};
|
||||
|
||||
// Sets the state of the recording button
|
||||
my.setRecordingButtonState = function (isRecording) {
|
||||
if (isRecording) {
|
||||
$('#recordButton').removeClass("icon-recEnable");
|
||||
$('#recordButton').addClass("icon-recEnable active");
|
||||
} else {
|
||||
$('#recordButton').removeClass("icon-recEnable active");
|
||||
$('#recordButton').addClass("icon-recEnable");
|
||||
}
|
||||
};
|
||||
|
||||
// Shows or hides SIP calls button
|
||||
my.showSipCallButton = function (show) {
|
||||
if (APP.xmpp.isSipGatewayEnabled() && show) {
|
||||
$('#sipCallButton').css({display: "inline-block"});
|
||||
} else {
|
||||
$('#sipCallButton').css({display: "none"});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays user authenticated identity name(login).
|
||||
* @param authIdentity identity name to be displayed.
|
||||
*/
|
||||
my.setAuthenticatedIdentity = function (authIdentity) {
|
||||
if (authIdentity) {
|
||||
$('#toolbar_auth_identity').css({display: "list-item"});
|
||||
$('#toolbar_auth_identity').text(authIdentity);
|
||||
} else {
|
||||
$('#toolbar_auth_identity').css({display: "none"});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows/hides login button.
|
||||
* @param show <tt>true</tt> to show
|
||||
*/
|
||||
my.showLoginButton = function (show) {
|
||||
if (show) {
|
||||
$('#toolbar_button_login').css({display: "list-item"});
|
||||
} else {
|
||||
$('#toolbar_button_login').css({display: "none"});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows/hides logout button.
|
||||
* @param show <tt>true</tt> to show
|
||||
*/
|
||||
my.showLogoutButton = function (show) {
|
||||
if (show) {
|
||||
$('#toolbar_button_logout').css({display: "list-item"});
|
||||
} else {
|
||||
$('#toolbar_button_logout').css({display: "none"});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the state of the button. The button has blue glow if desktop
|
||||
* streaming is active.
|
||||
* @param active the state of the desktop streaming.
|
||||
*/
|
||||
my.changeDesktopSharingButtonState = function (active) {
|
||||
var button = $("#desktopsharing > a");
|
||||
if (active)
|
||||
{
|
||||
button.addClass("glow");
|
||||
}
|
||||
else
|
||||
{
|
||||
button.removeClass("glow");
|
||||
}
|
||||
};
|
||||
|
||||
return my;
|
||||
}(Toolbar || {}));
|
||||
|
||||
module.exports = Toolbar;
|
||||
@@ -1,12 +1,54 @@
|
||||
/* global $, interfaceConfig, Moderator, showDesktopSharingButton */
|
||||
var ToolbarToggler = (function (my) {
|
||||
var toolbarTimeoutObject,
|
||||
toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
|
||||
/* global $, interfaceConfig, Moderator, DesktopStreaming.showDesktopSharingButton */
|
||||
|
||||
var toolbarTimeoutObject,
|
||||
toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
|
||||
|
||||
function showDesktopSharingButton() {
|
||||
if (APP.desktopsharing.isDesktopSharingEnabled()) {
|
||||
$('#desktopsharing').css({display: "inline"});
|
||||
} else {
|
||||
$('#desktopsharing').css({display: "none"});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the toolbar.
|
||||
*/
|
||||
function hideToolbar() {
|
||||
var header = $("#header"),
|
||||
bottomToolbar = $("#bottomToolbar");
|
||||
var isToolbarHover = false;
|
||||
header.find('*').each(function () {
|
||||
var id = $(this).attr('id');
|
||||
if ($("#" + id + ":hover").length > 0) {
|
||||
isToolbarHover = true;
|
||||
}
|
||||
});
|
||||
if ($("#bottomToolbar:hover").length > 0) {
|
||||
isToolbarHover = true;
|
||||
}
|
||||
|
||||
clearTimeout(toolbarTimeoutObject);
|
||||
toolbarTimeoutObject = null;
|
||||
|
||||
if (!isToolbarHover) {
|
||||
header.hide("slide", { direction: "up", duration: 300});
|
||||
$('#subject').animate({top: "-=40"}, 300);
|
||||
if ($("#remoteVideos").hasClass("hidden")) {
|
||||
bottomToolbar.hide(
|
||||
"slide", {direction: "right", duration: 300});
|
||||
}
|
||||
}
|
||||
else {
|
||||
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
var ToolbarToggler = {
|
||||
/**
|
||||
* Shows the main toolbar.
|
||||
*/
|
||||
my.showToolbar = function () {
|
||||
showToolbar: function () {
|
||||
var header = $("#header"),
|
||||
bottomToolbar = $("#bottomToolbar");
|
||||
if (!header.is(':visible') || !bottomToolbar.is(":visible")) {
|
||||
@@ -25,7 +67,7 @@ var ToolbarToggler = (function (my) {
|
||||
toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
|
||||
}
|
||||
|
||||
if (Moderator.isModerator())
|
||||
if (APP.xmpp.isModerator())
|
||||
{
|
||||
// TODO: Enable settings functionality.
|
||||
// Need to uncomment the settings button in index.html.
|
||||
@@ -34,40 +76,7 @@ var ToolbarToggler = (function (my) {
|
||||
|
||||
// Show/hide desktop sharing button
|
||||
showDesktopSharingButton();
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides the toolbar.
|
||||
*/
|
||||
var hideToolbar = function () {
|
||||
var header = $("#header"),
|
||||
bottomToolbar = $("#bottomToolbar");
|
||||
var isToolbarHover = false;
|
||||
header.find('*').each(function () {
|
||||
var id = $(this).attr('id');
|
||||
if ($("#" + id + ":hover").length > 0) {
|
||||
isToolbarHover = true;
|
||||
}
|
||||
});
|
||||
if ($("#bottomToolbar:hover").length > 0) {
|
||||
isToolbarHover = true;
|
||||
}
|
||||
|
||||
clearTimeout(toolbarTimeoutObject);
|
||||
toolbarTimeoutObject = null;
|
||||
|
||||
if (!isToolbarHover) {
|
||||
header.hide("slide", { direction: "up", duration: 300});
|
||||
$('#subject').animate({top: "-=40"}, 300);
|
||||
if ($("#remoteVideos").hasClass("hidden")) {
|
||||
bottomToolbar.hide(
|
||||
"slide", {direction: "right", duration: 300});
|
||||
}
|
||||
}
|
||||
else {
|
||||
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
@@ -75,11 +84,11 @@ var ToolbarToggler = (function (my) {
|
||||
*
|
||||
* @param isDock indicates what operation to perform
|
||||
*/
|
||||
my.dockToolbar = function (isDock) {
|
||||
dockToolbar: function (isDock) {
|
||||
if (isDock) {
|
||||
// First make sure the toolbar is shown.
|
||||
if (!$('#header').is(':visible')) {
|
||||
ToolbarToggler.showToolbar();
|
||||
this.showToolbar();
|
||||
}
|
||||
|
||||
// Then clear the time out, to dock the toolbar.
|
||||
@@ -90,14 +99,16 @@ var ToolbarToggler = (function (my) {
|
||||
}
|
||||
else {
|
||||
if (!$('#header').is(':visible')) {
|
||||
ToolbarToggler.showToolbar();
|
||||
this.showToolbar();
|
||||
}
|
||||
else {
|
||||
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
showDesktopSharingButton: showDesktopSharingButton
|
||||
|
||||
return my;
|
||||
}(ToolbarToggler || {}));
|
||||
};
|
||||
|
||||
module.exports = ToolbarToggler;
|
||||
@@ -118,4 +118,6 @@ var JitsiPopover = (function () {
|
||||
return JitsiPopover;
|
||||
|
||||
|
||||
})();
|
||||
})();
|
||||
|
||||
module.exports = JitsiPopover;
|
||||
@@ -1,3 +1,4 @@
|
||||
/* global $, jQuery */
|
||||
var messageHandler = (function(my) {
|
||||
|
||||
/**
|
||||
@@ -6,10 +7,16 @@ var messageHandler = (function(my) {
|
||||
* @param titleString the title of the message
|
||||
* @param messageString the text of the message
|
||||
*/
|
||||
my.openMessageDialog = function(titleString, messageString) {
|
||||
$.prompt(messageString,
|
||||
my.openMessageDialog = function(titleKey, messageKey) {
|
||||
var title = null;
|
||||
if(titleKey)
|
||||
{
|
||||
title = APP.translation.generateTranslatonHTML(titleKey);
|
||||
}
|
||||
var message = APP.translation.generateTranslatonHTML(messageKey);
|
||||
$.prompt(message,
|
||||
{
|
||||
title: titleString,
|
||||
title: title,
|
||||
persistent: false
|
||||
}
|
||||
);
|
||||
@@ -26,13 +33,25 @@ var messageHandler = (function(my) {
|
||||
* @param loadedFunction function to be called after the prompt is fully loaded
|
||||
* @param closeFunction function to be called after the prompt is closed
|
||||
*/
|
||||
my.openTwoButtonDialog = function(titleString, msgString, persistent, leftButton,
|
||||
submitFunction, loadedFunction, closeFunction) {
|
||||
my.openTwoButtonDialog = function(titleKey, titleString, msgKey, msgString,
|
||||
persistent, leftButtonKey, submitFunction, loadedFunction,
|
||||
closeFunction)
|
||||
{
|
||||
var leftButton = APP.translation.generateTranslatonHTML(leftButtonKey);
|
||||
var buttons = {};
|
||||
buttons[leftButton] = true;
|
||||
buttons.Cancel = false;
|
||||
$.prompt(msgString, {
|
||||
title: titleString,
|
||||
buttons.button1 = {title: leftButton, value: true};
|
||||
var cancelButton = APP.translation.generateTranslatonHTML("dialog.Cancel");
|
||||
buttons.button2 = {title: cancelButton, value: false};
|
||||
var message = msgString, title = titleString;
|
||||
if(titleKey)
|
||||
{
|
||||
title = APP.translation.generateTranslatonHTML(titleKey);
|
||||
}
|
||||
if(msgKey) {
|
||||
message = APP.translation.generateTranslatonHTML(msgKey);
|
||||
}
|
||||
$.prompt(message, {
|
||||
title: title,
|
||||
persistent: false,
|
||||
buttons: buttons,
|
||||
defaultButton: 1,
|
||||
@@ -53,15 +72,27 @@ var messageHandler = (function(my) {
|
||||
* @param submitFunction function to be called on submit
|
||||
* @param loadedFunction function to be called after the prompt is fully loaded
|
||||
*/
|
||||
my.openDialog = function(titleString, msgString, persistent, buttons, submitFunction, loadedFunction) {
|
||||
$.prompt(msgString, {
|
||||
my.openDialog = function (titleString, msgString, persistent, buttons,
|
||||
submitFunction, loadedFunction) {
|
||||
var args = {
|
||||
title: titleString,
|
||||
persistent: false,
|
||||
persistent: persistent,
|
||||
buttons: buttons,
|
||||
defaultButton: 1,
|
||||
loaded: loadedFunction,
|
||||
submit: submitFunction
|
||||
});
|
||||
};
|
||||
if (persistent) {
|
||||
args.closeText = '';
|
||||
}
|
||||
return $.prompt(msgString, args);
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes currently opened dialog.
|
||||
*/
|
||||
my.closeDialog = function () {
|
||||
$.prompt.close();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -117,8 +148,8 @@ var messageHandler = (function(my) {
|
||||
* @param msgString the text of the message
|
||||
* @param error the error that is being reported
|
||||
*/
|
||||
my.openReportDialog = function(titleString, msgString, error) {
|
||||
my.openMessageDialog(titleString, msgString);
|
||||
my.openReportDialog = function(titleKey, msgKey, error) {
|
||||
my.openMessageDialog(titleKey, msgKey);
|
||||
console.log(error);
|
||||
//FIXME send the error to the server
|
||||
};
|
||||
@@ -128,25 +159,45 @@ var messageHandler = (function(my) {
|
||||
* @param title the title of the message
|
||||
* @param message the text of the messafe
|
||||
*/
|
||||
my.showError = function(title, message) {
|
||||
if(!(title || message)) {
|
||||
title = title || "Oops!";
|
||||
message = message || "There was some kind of error";
|
||||
my.showError = function(titleKey, msgKey) {
|
||||
|
||||
if(!titleKey) {
|
||||
titleKey = "dialog.oops";
|
||||
}
|
||||
messageHandler.openMessageDialog(title, message);
|
||||
if(!msgKey)
|
||||
{
|
||||
msgKey = "dialog.defaultError";
|
||||
}
|
||||
messageHandler.openMessageDialog(titleKey, msgKey);
|
||||
};
|
||||
|
||||
my.notify = function(displayName, cls, message) {
|
||||
my.notify = function(displayName, displayNameKey,
|
||||
cls, messageKey, messageArguments) {
|
||||
var displayNameSpan = '<span class="nickname" ';
|
||||
if(displayName)
|
||||
{
|
||||
displayNameSpan += ">" + displayName;
|
||||
}
|
||||
else
|
||||
{
|
||||
displayNameSpan += "data-i18n='" + displayNameKey +
|
||||
"'>" + APP.translation.translateString(displayNameKey);
|
||||
}
|
||||
displayNameSpan += "</span>";
|
||||
toastr.info(
|
||||
'<span class="nickname">' +
|
||||
displayName +
|
||||
'</span><br>' +
|
||||
'<span class=' + cls + '>' +
|
||||
message +
|
||||
displayNameSpan + '<br>' +
|
||||
'<span class=' + cls + ' data-i18n="' + messageKey + '"' +
|
||||
(messageArguments?
|
||||
" data-i18n-options='" + JSON.stringify(messageArguments) + "'"
|
||||
: "") + ">" +
|
||||
APP.translation.translateString(messageKey,
|
||||
messageArguments) +
|
||||
'</span>');
|
||||
};
|
||||
|
||||
return my;
|
||||
}(messageHandler || {}));
|
||||
|
||||
module.exports = messageHandler;
|
||||
|
||||
|
||||
30
modules/UI/util/NicknameHandler.js
Normal file
@@ -0,0 +1,30 @@
|
||||
var UIEvents = require("../../../service/UI/UIEvents");
|
||||
|
||||
var nickname = null;
|
||||
var eventEmitter = null;
|
||||
|
||||
var NickanameHandler = {
|
||||
init: function (emitter) {
|
||||
eventEmitter = emitter;
|
||||
var storedDisplayName = window.localStorage.displayname;
|
||||
if (storedDisplayName) {
|
||||
nickname = storedDisplayName;
|
||||
}
|
||||
},
|
||||
setNickname: function (newNickname) {
|
||||
if (!newNickname || nickname === newNickname)
|
||||
return;
|
||||
|
||||
nickname = newNickname;
|
||||
window.localStorage.displayname = nickname;
|
||||
eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newNickname);
|
||||
},
|
||||
getNickname: function () {
|
||||
return nickname;
|
||||
},
|
||||
addListener: function (type, listener) {
|
||||
eventEmitter.on(type, listener);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = NickanameHandler;
|
||||
@@ -1,63 +1,58 @@
|
||||
/* global $ */
|
||||
/**
|
||||
* Utility functions.
|
||||
* Created by hristo on 12/22/14.
|
||||
*/
|
||||
var Util = (function (my) {
|
||||
module.exports = {
|
||||
/**
|
||||
* Returns the available video width.
|
||||
*/
|
||||
getAvailableVideoWidth: function () {
|
||||
var PanelToggler = require("../side_pannels/SidePanelToggler");
|
||||
var rightPanelWidth
|
||||
= PanelToggler.isVisible() ? PanelToggler.getPanelSize()[0] : 0;
|
||||
|
||||
return window.innerWidth - rightPanelWidth;
|
||||
},
|
||||
/**
|
||||
* Changes the style class of the element given by id.
|
||||
*/
|
||||
buttonClick: function(id, classname) {
|
||||
$(id).toggleClass(classname); // add the class to the clicked element
|
||||
},
|
||||
/**
|
||||
* Returns the text width for the given element.
|
||||
*
|
||||
* @param el the element
|
||||
*/
|
||||
my.getTextWidth = function (el) {
|
||||
getTextWidth: function (el) {
|
||||
return (el.clientWidth + 1);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the text height for the given element.
|
||||
*
|
||||
* @param el the element
|
||||
*/
|
||||
my.getTextHeight = function (el) {
|
||||
getTextHeight: function (el) {
|
||||
return (el.clientHeight + 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Casts the given number to integer.
|
||||
*
|
||||
* @param number the number to cast
|
||||
*/
|
||||
my.toInteger = function (number) {
|
||||
return Math.round(Number(number));
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Plays the sound given by id.
|
||||
*
|
||||
* @param id the identifier of the audio element.
|
||||
*/
|
||||
my.playSoundNotification = function (id) {
|
||||
playSoundNotification: function (id) {
|
||||
document.getElementById(id).play();
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Escapes the given text.
|
||||
*/
|
||||
my.escapeHtml = function (unsafeText) {
|
||||
escapeHtml: function (unsafeText) {
|
||||
return $('<div/>').text(unsafeText).html();
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the available video width.
|
||||
*/
|
||||
my.getAvailableVideoWidth = function () {
|
||||
var rightPanelWidth
|
||||
= PanelToggler.isVisible() ? PanelToggler.getPanelSize()[0] : 0;
|
||||
|
||||
return window.innerWidth - rightPanelWidth;
|
||||
};
|
||||
|
||||
my.imageToGrayScale = function (canvas) {
|
||||
imageToGrayScale: function (canvas) {
|
||||
var context = canvas.getContext('2d');
|
||||
var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
|
||||
var pixels = imgData.data;
|
||||
@@ -72,30 +67,15 @@ var Util = (function (my) {
|
||||
}
|
||||
// redraw the image in black & white
|
||||
context.putImageData(imgData, 0, 0);
|
||||
};
|
||||
},
|
||||
|
||||
my.setTooltip = function (element, tooltipText, position) {
|
||||
element.setAttribute("data-content", tooltipText);
|
||||
setTooltip: function (element, key, position) {
|
||||
element.setAttribute("data-i18n", "[data-content]" + key);
|
||||
element.setAttribute("data-toggle", "popover");
|
||||
element.setAttribute("data-placement", position);
|
||||
element.setAttribute("data-html", true);
|
||||
element.setAttribute("data-container", "body");
|
||||
};
|
||||
}
|
||||
|
||||
my.createExpBackoffTimer = function (step) {
|
||||
var count = 1;
|
||||
return function (reset) {
|
||||
// Reset call
|
||||
if (reset) {
|
||||
count = 1;
|
||||
return;
|
||||
}
|
||||
// Calculate next timeout
|
||||
var timeout = Math.pow(2, count - 1);
|
||||
count += 1;
|
||||
return timeout * step;
|
||||
};
|
||||
};
|
||||
|
||||
return my;
|
||||
}(Util || {}));
|
||||
};
|
||||
427
modules/UI/videolayout/ConnectionIndicator.js
Normal file
@@ -0,0 +1,427 @@
|
||||
var JitsiPopover = require("../util/JitsiPopover");
|
||||
|
||||
/**
|
||||
* Constructs new connection indicator.
|
||||
* @param videoContainer the video container associated with the indicator.
|
||||
* @constructor
|
||||
*/
|
||||
function ConnectionIndicator(videoContainer, jid, VideoLayout)
|
||||
{
|
||||
this.videoContainer = videoContainer;
|
||||
this.bandwidth = null;
|
||||
this.packetLoss = null;
|
||||
this.bitrate = null;
|
||||
this.showMoreValue = false;
|
||||
this.resolution = null;
|
||||
this.transport = [];
|
||||
this.popover = null;
|
||||
this.jid = jid;
|
||||
this.create();
|
||||
this.videoLayout = VideoLayout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Values for the connection quality
|
||||
* @type {{98: string,
|
||||
* 81: string,
|
||||
* 64: string,
|
||||
* 47: string,
|
||||
* 30: string,
|
||||
* 0: string}}
|
||||
*/
|
||||
ConnectionIndicator.connectionQualityValues = {
|
||||
98: "18px", //full
|
||||
81: "15px",//4 bars
|
||||
64: "11px",//3 bars
|
||||
47: "7px",//2 bars
|
||||
30: "3px",//1 bar
|
||||
0: "0px"//empty
|
||||
};
|
||||
|
||||
ConnectionIndicator.getIP = function(value)
|
||||
{
|
||||
return value.substring(0, value.lastIndexOf(":"));
|
||||
};
|
||||
|
||||
ConnectionIndicator.getPort = function(value)
|
||||
{
|
||||
return value.substring(value.lastIndexOf(":") + 1, value.length);
|
||||
};
|
||||
|
||||
ConnectionIndicator.getStringFromArray = function (array) {
|
||||
var res = "";
|
||||
for(var i = 0; i < array.length; i++)
|
||||
{
|
||||
res += (i === 0? "" : ", ") + array[i];
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates the html content.
|
||||
* @returns {string} the html content.
|
||||
*/
|
||||
ConnectionIndicator.prototype.generateText = function () {
|
||||
var downloadBitrate, uploadBitrate, packetLoss, resolution, i;
|
||||
|
||||
var translate = APP.translation.translateString;
|
||||
|
||||
if(this.bitrate === null)
|
||||
{
|
||||
downloadBitrate = "N/A";
|
||||
uploadBitrate = "N/A";
|
||||
}
|
||||
else
|
||||
{
|
||||
downloadBitrate =
|
||||
this.bitrate.download? this.bitrate.download + " Kbps" : "N/A";
|
||||
uploadBitrate =
|
||||
this.bitrate.upload? this.bitrate.upload + " Kbps" : "N/A";
|
||||
}
|
||||
|
||||
if(this.packetLoss === null)
|
||||
{
|
||||
packetLoss = "N/A";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
packetLoss = "<span class='jitsipopover_green'>↓</span>" +
|
||||
(this.packetLoss.download !== null? this.packetLoss.download : "N/A") +
|
||||
"% <span class='jitsipopover_orange'>↑</span>" +
|
||||
(this.packetLoss.upload !== null? this.packetLoss.upload : "N/A") + "%";
|
||||
}
|
||||
|
||||
var resolutionValue = null;
|
||||
if(this.resolution && this.jid != null)
|
||||
{
|
||||
var keys = Object.keys(this.resolution);
|
||||
if(keys.length == 1)
|
||||
{
|
||||
for(var ssrc in this.resolution)
|
||||
{
|
||||
resolutionValue = this.resolution[ssrc];
|
||||
}
|
||||
}
|
||||
else if(keys.length > 1)
|
||||
{
|
||||
var displayedSsrc = APP.simulcast.getReceivingSSRC(this.jid);
|
||||
resolutionValue = this.resolution[displayedSsrc];
|
||||
}
|
||||
}
|
||||
|
||||
if(this.jid === null)
|
||||
{
|
||||
resolution = "";
|
||||
if(this.resolution === null || !Object.keys(this.resolution) ||
|
||||
Object.keys(this.resolution).length === 0)
|
||||
{
|
||||
resolution = "N/A";
|
||||
}
|
||||
else
|
||||
for(i in this.resolution)
|
||||
{
|
||||
resolutionValue = this.resolution[i];
|
||||
if(resolutionValue)
|
||||
{
|
||||
if(resolutionValue.height &&
|
||||
resolutionValue.width)
|
||||
{
|
||||
resolution += (resolution === ""? "" : ", ") +
|
||||
resolutionValue.width + "x" +
|
||||
resolutionValue.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(!resolutionValue ||
|
||||
!resolutionValue.height ||
|
||||
!resolutionValue.width)
|
||||
{
|
||||
resolution = "N/A";
|
||||
}
|
||||
else
|
||||
{
|
||||
resolution = resolutionValue.width + "x" + resolutionValue.height;
|
||||
}
|
||||
|
||||
var result = "<table style='width:100%'>" +
|
||||
"<tr>" +
|
||||
"<td><span class='jitsipopover_blue' data-i18n='connectionindicator.bitrate'>" +
|
||||
translate("connectionindicator.bitrate") + "</span></td>" +
|
||||
"<td><span class='jitsipopover_green'>↓</span>" +
|
||||
downloadBitrate + " <span class='jitsipopover_orange'>↑</span>" +
|
||||
uploadBitrate + "</td>" +
|
||||
"</tr><tr>" +
|
||||
"<td><span class='jitsipopover_blue' data-i18n='connectionindicator.packetloss'>" +
|
||||
translate("connectionindicator.packetloss") + "</span></td>" +
|
||||
"<td>" + packetLoss + "</td>" +
|
||||
"</tr><tr>" +
|
||||
"<td><span class='jitsipopover_blue' data-i18n='connectionindicator.resolution'>" +
|
||||
translate("connectionindicator.resolution") + "</span></td>" +
|
||||
"<td>" + resolution + "</td></tr></table>";
|
||||
|
||||
if(this.videoContainer.id == "localVideoContainer") {
|
||||
result += "<div class=\"jitsipopover_showmore\" " +
|
||||
"onclick = \"APP.UI.connectionIndicatorShowMore('" +
|
||||
this.videoContainer.id + "')\" data-i18n='connectionindicator." +
|
||||
(this.showMoreValue ? "less" : "more") + "'>" +
|
||||
translate("connectionindicator." + (this.showMoreValue ? "less" : "more")) +
|
||||
"</div><br />";
|
||||
}
|
||||
|
||||
if(this.showMoreValue)
|
||||
{
|
||||
var downloadBandwidth, uploadBandwidth, transport;
|
||||
if(this.bandwidth === null)
|
||||
{
|
||||
downloadBandwidth = "N/A";
|
||||
uploadBandwidth = "N/A";
|
||||
}
|
||||
else
|
||||
{
|
||||
downloadBandwidth = this.bandwidth.download?
|
||||
this.bandwidth.download + " Kbps" :
|
||||
"N/A";
|
||||
uploadBandwidth = this.bandwidth.upload?
|
||||
this.bandwidth.upload + " Kbps" :
|
||||
"N/A";
|
||||
}
|
||||
|
||||
if(!this.transport || this.transport.length === 0)
|
||||
{
|
||||
transport = "<tr>" +
|
||||
"<td><span class='jitsipopover_blue' " +
|
||||
"data-i18n='connectionindicator.address'>" +
|
||||
translate("connectionindicator.address") + "</span></td>" +
|
||||
"<td> N/A</td></tr>";
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = {remoteIP: [], localIP:[], remotePort:[], localPort:[]};
|
||||
for(i = 0; i < this.transport.length; i++)
|
||||
{
|
||||
var ip = ConnectionIndicator.getIP(this.transport[i].ip);
|
||||
var port = ConnectionIndicator.getPort(this.transport[i].ip);
|
||||
var localIP =
|
||||
ConnectionIndicator.getIP(this.transport[i].localip);
|
||||
var localPort =
|
||||
ConnectionIndicator.getPort(this.transport[i].localip);
|
||||
if(data.remoteIP.indexOf(ip) == -1)
|
||||
{
|
||||
data.remoteIP.push(ip);
|
||||
}
|
||||
|
||||
if(data.remotePort.indexOf(port) == -1)
|
||||
{
|
||||
data.remotePort.push(port);
|
||||
}
|
||||
|
||||
if(data.localIP.indexOf(localIP) == -1)
|
||||
{
|
||||
data.localIP.push(localIP);
|
||||
}
|
||||
|
||||
if(data.localPort.indexOf(localPort) == -1)
|
||||
{
|
||||
data.localPort.push(localPort);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var local_address_key = "connectionindicator.localaddress";
|
||||
var remote_address_key = "connectionindicator.remoteaddress";
|
||||
var localTransport =
|
||||
"<tr><td><span class='jitsipopover_blue' data-i18n='" +
|
||||
local_address_key +"' data-i18n-options='" +
|
||||
JSON.stringify({count: data.localIP.length}) + "'>" +
|
||||
translate(local_address_key, {count: data.localIP.length}) +
|
||||
"</span></td><td> " +
|
||||
ConnectionIndicator.getStringFromArray(data.localIP) +
|
||||
"</td></tr>";
|
||||
transport =
|
||||
"<tr><td><span class='jitsipopover_blue' data-i18n='" +
|
||||
remote_address_key + "' data-i18n-options='" +
|
||||
JSON.stringify({count: data.remoteIP.length}) + "'>" +
|
||||
translate(remote_address_key,
|
||||
{count: data.remoteIP.length}) +
|
||||
"</span></td><td> " +
|
||||
ConnectionIndicator.getStringFromArray(data.remoteIP) +
|
||||
"</td></tr>";
|
||||
|
||||
var key_remote = "connectionindicator.remoteport",
|
||||
key_local = "connectionindicator.localport";
|
||||
|
||||
transport += "<tr>" +
|
||||
"<td>" +
|
||||
"<span class='jitsipopover_blue' data-i18n='" + key_remote +
|
||||
"' data-i18n-options='" +
|
||||
JSON.stringify({count: this.transport.length}) + "'>" +
|
||||
translate(key_remote, {count: this.transport.length}) +
|
||||
"</span></td><td>";
|
||||
localTransport += "<tr>" +
|
||||
"<td>" +
|
||||
"<span class='jitsipopover_blue' data-i18n='" + key_local +
|
||||
"' data-i18n-options='" +
|
||||
JSON.stringify({count: this.transport.length}) + "'>" +
|
||||
translate(key_local, {count: this.transport.length}) +
|
||||
"</span></td><td>";
|
||||
|
||||
transport +=
|
||||
ConnectionIndicator.getStringFromArray(data.remotePort);
|
||||
localTransport +=
|
||||
ConnectionIndicator.getStringFromArray(data.localPort);
|
||||
transport += "</td></tr>";
|
||||
transport += localTransport + "</td></tr>";
|
||||
transport +="<tr>" +
|
||||
"<td><span class='jitsipopover_blue' data-i18n='connectionindicator.transport'>" +
|
||||
translate("connectionindicator.transport") + "</span></td>" +
|
||||
"<td>" + this.transport[0].type + "</td></tr>";
|
||||
|
||||
}
|
||||
|
||||
result += "<table style='width:100%'>" +
|
||||
"<tr>" +
|
||||
"<td>" +
|
||||
"<span class='jitsipopover_blue' data-i18n='connectionindicator.bandwidth'>" +
|
||||
translate("connectionindicator.bandwidth") + "</span>" +
|
||||
"</td><td>" +
|
||||
"<span class='jitsipopover_green'>↓</span>" +
|
||||
downloadBandwidth +
|
||||
" <span class='jitsipopover_orange'>↑</span>" +
|
||||
uploadBandwidth + "</td></tr>";
|
||||
|
||||
result += transport + "</table>";
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows or hide the additional information.
|
||||
*/
|
||||
ConnectionIndicator.prototype.showMore = function () {
|
||||
this.showMoreValue = !this.showMoreValue;
|
||||
this.updatePopoverData();
|
||||
};
|
||||
|
||||
|
||||
function createIcon(classes)
|
||||
{
|
||||
var icon = document.createElement("span");
|
||||
for(var i in classes)
|
||||
{
|
||||
icon.classList.add(classes[i]);
|
||||
}
|
||||
icon.appendChild(
|
||||
document.createElement("i")).classList.add("icon-connection");
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the indicator
|
||||
*/
|
||||
ConnectionIndicator.prototype.create = function () {
|
||||
this.connectionIndicatorContainer = document.createElement("div");
|
||||
this.connectionIndicatorContainer.className = "connectionindicator";
|
||||
this.connectionIndicatorContainer.style.display = "none";
|
||||
this.videoContainer.appendChild(this.connectionIndicatorContainer);
|
||||
this.popover = new JitsiPopover(
|
||||
$("#" + this.videoContainer.id + " > .connectionindicator"),
|
||||
{content: "<div class=\"connection_info\" data-i18n='connectionindicator.na'>" +
|
||||
APP.translation.translateString("connectionindicator.na") + "</div>",
|
||||
skin: "black"});
|
||||
|
||||
this.emptyIcon = this.connectionIndicatorContainer.appendChild(
|
||||
createIcon(["connection", "connection_empty"]));
|
||||
this.fullIcon = this.connectionIndicatorContainer.appendChild(
|
||||
createIcon(["connection", "connection_full"]));
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the indicator
|
||||
*/
|
||||
ConnectionIndicator.prototype.remove = function()
|
||||
{
|
||||
this.connectionIndicatorContainer.remove();
|
||||
this.popover.forceHide();
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the data of the indicator
|
||||
* @param percent the percent of connection quality
|
||||
* @param object the statistics data.
|
||||
*/
|
||||
ConnectionIndicator.prototype.updateConnectionQuality =
|
||||
function (percent, object) {
|
||||
|
||||
if(percent === null)
|
||||
{
|
||||
this.connectionIndicatorContainer.style.display = "none";
|
||||
this.popover.forceHide();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(this.connectionIndicatorContainer.style.display == "none") {
|
||||
this.connectionIndicatorContainer.style.display = "block";
|
||||
this.videoLayout.updateMutePosition(this.videoContainer.id);
|
||||
}
|
||||
}
|
||||
this.bandwidth = object.bandwidth;
|
||||
this.bitrate = object.bitrate;
|
||||
this.packetLoss = object.packetLoss;
|
||||
this.transport = object.transport;
|
||||
if(object.resolution)
|
||||
{
|
||||
this.resolution = object.resolution;
|
||||
}
|
||||
for(var quality in ConnectionIndicator.connectionQualityValues)
|
||||
{
|
||||
if(percent >= quality)
|
||||
{
|
||||
this.fullIcon.style.width =
|
||||
ConnectionIndicator.connectionQualityValues[quality];
|
||||
}
|
||||
}
|
||||
this.updatePopoverData();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the resolution
|
||||
* @param resolution the new resolution
|
||||
*/
|
||||
ConnectionIndicator.prototype.updateResolution = function (resolution) {
|
||||
this.resolution = resolution;
|
||||
this.updatePopoverData();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the content of the popover
|
||||
*/
|
||||
ConnectionIndicator.prototype.updatePopoverData = function () {
|
||||
this.popover.updateContent(
|
||||
"<div class=\"connection_info\">" + this.generateText() + "</div>");
|
||||
APP.translation.translateElement($(".connection_info"));
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides the popover
|
||||
*/
|
||||
ConnectionIndicator.prototype.hide = function () {
|
||||
this.popover.forceHide();
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides the indicator
|
||||
*/
|
||||
ConnectionIndicator.prototype.hideIndicator = function () {
|
||||
this.connectionIndicatorContainer.style.display = "none";
|
||||
if(this.popover)
|
||||
this.popover.forceHide();
|
||||
};
|
||||
|
||||
module.exports = ConnectionIndicator;
|
||||
179
modules/UI/welcome_page/RoomnameGenerator.js
Normal file
@@ -0,0 +1,179 @@
|
||||
//var nouns = [
|
||||
//];
|
||||
var pluralNouns = [
|
||||
"Aliens", "Animals", "Antelopes", "Ants", "Apes", "Apples", "Baboons", "Bacteria", "Badgers", "Bananas", "Bats",
|
||||
"Bears", "Birds", "Bonobos", "Brides", "Bugs", "Bulls", "Butterflies", "Cheetahs",
|
||||
"Cherries", "Chicken", "Children", "Chimps", "Clowns", "Cows", "Creatures", "Dinosaurs", "Dogs", "Dolphins",
|
||||
"Donkeys", "Dragons", "Ducks", "Dwarfs", "Eagles", "Elephants", "Elves", "FAIL", "Fathers",
|
||||
"Fish", "Flowers", "Frogs", "Fruit", "Fungi", "Galaxies", "Geese", "Goats",
|
||||
"Gorillas", "Hedgehogs", "Hippos", "Horses", "Hunters", "Insects", "Kids", "Knights",
|
||||
"Lemons", "Lemurs", "Leopards", "LifeForms", "Lions", "Lizards", "Mice", "Monkeys", "Monsters",
|
||||
"Mushrooms", "Octopodes", "Oranges", "Orangutans", "Organisms", "Pants", "Parrots", "Penguins",
|
||||
"People", "Pigeons", "Pigs", "Pineapples", "Plants", "Potatoes", "Priests", "Rats", "Reptiles", "Reptilians",
|
||||
"Rhinos", "Seagulls", "Sheep", "Siblings", "Snakes", "Spaghetti", "Spiders", "Squid", "Squirrels",
|
||||
"Stars", "Students", "Teachers", "Tigers", "Tomatoes", "Trees", "Vampires", "Vegetables", "Viruses", "Vulcans",
|
||||
"Warewolves", "Weasels", "Whales", "Witches", "Wizards", "Wolves", "Workers", "Worms", "Zebras"
|
||||
];
|
||||
//var places = [
|
||||
//"Pub", "University", "Airport", "Library", "Mall", "Theater", "Stadium", "Office", "Show", "Gallows", "Beach",
|
||||
// "Cemetery", "Hospital", "Reception", "Restaurant", "Bar", "Church", "House", "School", "Square", "Village",
|
||||
// "Cinema", "Movies", "Party", "Restroom", "End", "Jail", "PostOffice", "Station", "Circus", "Gates", "Entrance",
|
||||
// "Bridge"
|
||||
//];
|
||||
var verbs = [
|
||||
"Abandon", "Adapt", "Advertise", "Answer", "Anticipate", "Appreciate",
|
||||
"Approach", "Argue", "Ask", "Bite", "Blossom", "Blush", "Breathe", "Breed", "Bribe", "Burn", "Calculate",
|
||||
"Clean", "Code", "Communicate", "Compute", "Confess", "Confiscate", "Conjugate", "Conjure", "Consume",
|
||||
"Contemplate", "Crawl", "Dance", "Delegate", "Devour", "Develop", "Differ", "Discuss",
|
||||
"Dissolve", "Drink", "Eat", "Elaborate", "Emancipate", "Estimate", "Expire", "Extinguish",
|
||||
"Extract", "FAIL", "Facilitate", "Fall", "Feed", "Finish", "Floss", "Fly", "Follow", "Fragment", "Freeze",
|
||||
"Gather", "Glow", "Grow", "Hex", "Hide", "Hug", "Hurry", "Improve", "Intersect", "Investigate", "Jinx",
|
||||
"Joke", "Jubilate", "Kiss", "Laugh", "Manage", "Meet", "Merge", "Move", "Object", "Observe", "Offer",
|
||||
"Paint", "Participate", "Party", "Perform", "Plan", "Pursue", "Pierce", "Play", "Postpone", "Pray", "Proclaim",
|
||||
"Question", "Read", "Reckon", "Rejoice", "Represent", "Resize", "Rhyme", "Scream", "Search", "Select", "Share", "Shoot",
|
||||
"Shout", "Signal", "Sing", "Skate", "Sleep", "Smile", "Smoke", "Solve", "Spell", "Steer", "Stink",
|
||||
"Substitute", "Swim", "Taste", "Teach", "Terminate", "Think", "Type", "Unite", "Vanish", "Worship"
|
||||
];
|
||||
var adverbs = [
|
||||
"Absently", "Accurately", "Accusingly", "Adorably", "AllTheTime", "Alone", "Always", "Amazingly", "Angrily",
|
||||
"Anxiously", "Anywhere", "Appallingly", "Apparently", "Articulately", "Astonishingly", "Badly", "Barely",
|
||||
"Beautifully", "Blindly", "Bravely", "Brightly", "Briskly", "Brutally", "Calmly", "Carefully", "Casually",
|
||||
"Cautiously", "Cleverly", "Constantly", "Correctly", "Crazily", "Curiously", "Cynically", "Daily",
|
||||
"Dangerously", "Deliberately", "Delicately", "Desperately", "Discreetly", "Eagerly", "Easily", "Euphoricly",
|
||||
"Evenly", "Everywhere", "Exactly", "Expectantly", "Extensively", "FAIL", "Ferociously", "Fiercely", "Finely",
|
||||
"Flatly", "Frequently", "Frighteningly", "Gently", "Gloriously", "Grimly", "Guiltily", "Happily",
|
||||
"Hard", "Hastily", "Heroically", "High", "Highly", "Hourly", "Humbly", "Hysterically", "Immensely",
|
||||
"Impartially", "Impolitely", "Indifferently", "Intensely", "Jealously", "Jovially", "Kindly", "Lazily",
|
||||
"Lightly", "Loudly", "Lovingly", "Loyally", "Magnificently", "Malevolently", "Merrily", "Mightily", "Miserably",
|
||||
"Mysteriously", "NOT", "Nervously", "Nicely", "Nowhere", "Objectively", "Obnoxiously", "Obsessively",
|
||||
"Obviously", "Often", "Painfully", "Patiently", "Playfully", "Politely", "Poorly", "Precisely", "Promptly",
|
||||
"Quickly", "Quietly", "Randomly", "Rapidly", "Rarely", "Recklessly", "Regularly", "Remorsefully", "Responsibly",
|
||||
"Rudely", "Ruthlessly", "Sadly", "Scornfully", "Seamlessly", "Seldom", "Selfishly", "Seriously", "Shakily",
|
||||
"Sharply", "Sideways", "Silently", "Sleepily", "Slightly", "Slowly", "Slyly", "Smoothly", "Softly", "Solemnly", "Steadily", "Sternly", "Strangely", "Strongly", "Stunningly", "Surely", "Tenderly", "Thoughtfully",
|
||||
"Tightly", "Uneasily", "Vanishingly", "Violently", "Warmly", "Weakly", "Wearily", "Weekly", "Weirdly", "Well",
|
||||
"Well", "Wickedly", "Wildly", "Wisely", "Wonderfully", "Yearly"
|
||||
];
|
||||
var adjectives = [
|
||||
"Abominable", "Accurate", "Adorable", "All", "Alleged", "Ancient", "Angry", "Angry", "Anxious", "Appalling",
|
||||
"Apparent", "Astonishing", "Attractive", "Awesome", "Baby", "Bad", "Beautiful", "Benign", "Big", "Bitter",
|
||||
"Blind", "Blue", "Bold", "Brave", "Bright", "Brisk", "Calm", "Camouflaged", "Casual", "Cautious",
|
||||
"Choppy", "Chosen", "Clever", "Cold", "Cool", "Crawly", "Crazy", "Creepy", "Cruel", "Curious", "Cynical",
|
||||
"Dangerous", "Dark", "Delicate", "Desperate", "Difficult", "Discreet", "Disguised", "Dizzy",
|
||||
"Dumb", "Eager", "Easy", "Edgy", "Electric", "Elegant", "Emancipated", "Enormous", "Euphoric", "Evil",
|
||||
"FAIL", "Fast", "Ferocious", "Fierce", "Fine", "Flawed", "Flying", "Foolish", "Foxy",
|
||||
"Freezing", "Funny", "Furious", "Gentle", "Glorious", "Golden", "Good", "Green", "Green", "Guilty",
|
||||
"Hairy", "Happy", "Hard", "Hasty", "Hazy", "Heroic", "Hostile", "Hot", "Humble", "Humongous",
|
||||
"Humorous", "Hysterical", "Idealistic", "Ignorant", "Immense", "Impartial", "Impolite", "Indifferent",
|
||||
"Infuriated", "Insightful", "Intense", "Interesting", "Intimidated", "Intriguing", "Jealous", "Jolly", "Jovial",
|
||||
"Jumpy", "Kind", "Laughing", "Lazy", "Liquid", "Lonely", "Longing", "Loud", "Loving", "Loyal", "Macabre", "Mad",
|
||||
"Magical", "Magnificent", "Malevolent", "Medieval", "Memorable", "Mere", "Merry", "Mighty",
|
||||
"Mischievous", "Miserable", "Modified", "Moody", "Most", "Mysterious", "Mystical", "Needy",
|
||||
"Nervous", "Nice", "Objective", "Obnoxious", "Obsessive", "Obvious", "Opinionated", "Orange",
|
||||
"Painful", "Passionate", "Perfect", "Pink", "Playful", "Poisonous", "Polite", "Poor", "Popular", "Powerful",
|
||||
"Precise", "Preserved", "Pretty", "Purple", "Quick", "Quiet", "Random", "Rapid", "Rare", "Real",
|
||||
"Reassuring", "Reckless", "Red", "Regular", "Remorseful", "Responsible", "Rich", "Rude", "Ruthless",
|
||||
"Sad", "Scared", "Scary", "Scornful", "Screaming", "Selfish", "Serious", "Shady", "Shaky", "Sharp",
|
||||
"Shiny", "Shy", "Simple", "Sleepy", "Slow", "Sly", "Small", "Smart", "Smelly", "Smiling", "Smooth",
|
||||
"Smug", "Sober", "Soft", "Solemn", "Square", "Square", "Steady", "Strange", "Strong",
|
||||
"Stunning", "Subjective", "Successful", "Surly", "Sweet", "Tactful", "Tense",
|
||||
"Thoughtful", "Tight", "Tiny", "Tolerant", "Uneasy", "Unique", "Unseen", "Warm", "Weak",
|
||||
"Weird", "WellCooked", "Wild", "Wise", "Witty", "Wonderful", "Worried", "Yellow", "Young",
|
||||
"Zealous"
|
||||
];
|
||||
//var pronouns = [
|
||||
//];
|
||||
//var conjunctions = [
|
||||
//"And", "Or", "For", "Above", "Before", "Against", "Between"
|
||||
//];
|
||||
|
||||
/*
|
||||
* Maps a string (category name) to the array of words from that category.
|
||||
*/
|
||||
var CATEGORIES =
|
||||
{
|
||||
//"_NOUN_": nouns,
|
||||
"_PLURALNOUN_": pluralNouns,
|
||||
//"_PLACE_": places,
|
||||
"_VERB_": verbs,
|
||||
"_ADVERB_": adverbs,
|
||||
"_ADJECTIVE_": adjectives
|
||||
//"_PRONOUN_": pronouns,
|
||||
//"_CONJUNCTION_": conjunctions,
|
||||
};
|
||||
|
||||
var PATTERNS = [
|
||||
"_ADJECTIVE__PLURALNOUN__VERB__ADVERB_"
|
||||
|
||||
// BeautifulFungiOrSpaghetti
|
||||
//"_ADJECTIVE__PLURALNOUN__CONJUNCTION__PLURALNOUN_",
|
||||
|
||||
// AmazinglyScaryToy
|
||||
//"_ADVERB__ADJECTIVE__NOUN_",
|
||||
|
||||
// NeitherTrashNorRifle
|
||||
//"Neither_NOUN_Nor_NOUN_",
|
||||
//"Either_NOUN_Or_NOUN_",
|
||||
|
||||
// EitherCopulateOrInvestigate
|
||||
//"Either_VERB_Or_VERB_",
|
||||
//"Neither_VERB_Nor_VERB_",
|
||||
|
||||
//"The_ADJECTIVE__ADJECTIVE__NOUN_",
|
||||
//"The_ADVERB__ADJECTIVE__NOUN_",
|
||||
//"The_ADVERB__ADJECTIVE__NOUN_s",
|
||||
//"The_ADVERB__ADJECTIVE__PLURALNOUN__VERB_",
|
||||
|
||||
// WolvesComputeBadly
|
||||
//"_PLURALNOUN__VERB__ADVERB_",
|
||||
|
||||
// UniteFacilitateAndMerge
|
||||
//"_VERB__VERB_And_VERB_",
|
||||
|
||||
//NastyWitchesAtThePub
|
||||
//"_ADJECTIVE__PLURALNOUN_AtThe_PLACE_",
|
||||
];
|
||||
|
||||
|
||||
/*
|
||||
* Returns a random element from the array 'arr'
|
||||
*/
|
||||
function randomElement(arr)
|
||||
{
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the string 's' contains one of the
|
||||
* template strings.
|
||||
*/
|
||||
function hasTemplate(s)
|
||||
{
|
||||
for (var template in CATEGORIES){
|
||||
if (s.indexOf(template) >= 0){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates new room name.
|
||||
*/
|
||||
var RoomNameGenerator = {
|
||||
generateRoomWithoutSeparator: function()
|
||||
{
|
||||
// Note that if more than one pattern is available, the choice of 'name' won't be random (names from patterns
|
||||
// with fewer options will have higher probability of being chosen that names from patterns with more options).
|
||||
var name = randomElement(PATTERNS);
|
||||
var word;
|
||||
while (hasTemplate(name)){
|
||||
for (var template in CATEGORIES){
|
||||
word = randomElement(CATEGORIES[template]);
|
||||
name = name.replace(template, word);
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RoomNameGenerator;
|
||||
101
modules/UI/welcome_page/WelcomePage.js
Normal file
@@ -0,0 +1,101 @@
|
||||
var animateTimeout, updateTimeout;
|
||||
|
||||
var RoomNameGenerator = require("./RoomnameGenerator");
|
||||
|
||||
function enter_room()
|
||||
{
|
||||
var val = $("#enter_room_field").val();
|
||||
if(!val) {
|
||||
val = $("#enter_room_field").attr("room_name");
|
||||
}
|
||||
if (val) {
|
||||
window.location.pathname = "/" + val;
|
||||
}
|
||||
}
|
||||
|
||||
function animate(word) {
|
||||
var currentVal = $("#enter_room_field").attr("placeholder");
|
||||
$("#enter_room_field").attr("placeholder", currentVal + word.substr(0, 1));
|
||||
animateTimeout = setTimeout(function() {
|
||||
animate(word.substring(1, word.length))
|
||||
}, 70);
|
||||
}
|
||||
|
||||
function update_roomname()
|
||||
{
|
||||
var word = RoomNameGenerator.generateRoomWithoutSeparator();
|
||||
$("#enter_room_field").attr("room_name", word);
|
||||
$("#enter_room_field").attr("placeholder", "");
|
||||
clearTimeout(animateTimeout);
|
||||
animate(word);
|
||||
updateTimeout = setTimeout(update_roomname, 10000);
|
||||
}
|
||||
|
||||
|
||||
function setupWelcomePage()
|
||||
{
|
||||
$("#videoconference_page").hide();
|
||||
$("#domain_name").text(
|
||||
window.location.protocol + "//" + window.location.host + "/");
|
||||
if (interfaceConfig.SHOW_JITSI_WATERMARK) {
|
||||
var leftWatermarkDiv
|
||||
= $("#welcome_page_header div[class='watermark leftwatermark']");
|
||||
if(leftWatermarkDiv && leftWatermarkDiv.length > 0)
|
||||
{
|
||||
leftWatermarkDiv.css({display: 'block'});
|
||||
leftWatermarkDiv.parent().get(0).href
|
||||
= interfaceConfig.JITSI_WATERMARK_LINK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
|
||||
var rightWatermarkDiv
|
||||
= $("#welcome_page_header div[class='watermark rightwatermark']");
|
||||
if(rightWatermarkDiv && rightWatermarkDiv.length > 0) {
|
||||
rightWatermarkDiv.css({display: 'block'});
|
||||
rightWatermarkDiv.parent().get(0).href
|
||||
= interfaceConfig.BRAND_WATERMARK_LINK;
|
||||
rightWatermarkDiv.get(0).style.backgroundImage
|
||||
= "url(images/rightwatermark.png)";
|
||||
}
|
||||
}
|
||||
|
||||
if (interfaceConfig.SHOW_POWERED_BY) {
|
||||
$("#welcome_page_header>a[class='poweredby']")
|
||||
.css({display: 'block'});
|
||||
}
|
||||
|
||||
$("#enter_room_button").click(function()
|
||||
{
|
||||
enter_room();
|
||||
});
|
||||
|
||||
$("#enter_room_field").keydown(function (event) {
|
||||
if (event.keyCode === 13 /* enter */) {
|
||||
enter_room();
|
||||
}
|
||||
});
|
||||
|
||||
if (!(interfaceConfig.GENERATE_ROOMNAMES_ON_WELCOME_PAGE === false)){
|
||||
var updateTimeout;
|
||||
var animateTimeout;
|
||||
$("#reload_roomname").click(function () {
|
||||
clearTimeout(updateTimeout);
|
||||
clearTimeout(animateTimeout);
|
||||
update_roomname();
|
||||
});
|
||||
$("#reload_roomname").show();
|
||||
|
||||
|
||||
update_roomname();
|
||||
}
|
||||
|
||||
$("#disable_welcome").click(function () {
|
||||
window.localStorage.welcomePageDisabled
|
||||
= $("#disable_welcome").is(":checked");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
module.exports = setupWelcomePage;
|
||||
134
modules/connectionquality/connectionquality.js
Normal file
@@ -0,0 +1,134 @@
|
||||
var EventEmitter = require("events");
|
||||
var eventEmitter = new EventEmitter();
|
||||
var CQEvents = require("../../service/connectionquality/CQEvents");
|
||||
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
|
||||
|
||||
/**
|
||||
* local stats
|
||||
* @type {{}}
|
||||
*/
|
||||
var stats = {};
|
||||
|
||||
/**
|
||||
* remote stats
|
||||
* @type {{}}
|
||||
*/
|
||||
var remoteStats = {};
|
||||
|
||||
/**
|
||||
* Interval for sending statistics to other participants
|
||||
* @type {null}
|
||||
*/
|
||||
var sendIntervalId = null;
|
||||
|
||||
|
||||
/**
|
||||
* Start statistics sending.
|
||||
*/
|
||||
function startSendingStats() {
|
||||
sendStats();
|
||||
sendIntervalId = setInterval(sendStats, 10000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends statistics to other participants
|
||||
*/
|
||||
function sendStats() {
|
||||
APP.xmpp.addToPresence("connectionQuality", convertToMUCStats(stats));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts statistics to format for sending through XMPP
|
||||
* @param stats the statistics
|
||||
* @returns {{bitrate_donwload: *, bitrate_uplpoad: *, packetLoss_total: *, packetLoss_download: *, packetLoss_upload: *}}
|
||||
*/
|
||||
function convertToMUCStats(stats) {
|
||||
return {
|
||||
"bitrate_download": stats.bitrate.download,
|
||||
"bitrate_upload": stats.bitrate.upload,
|
||||
"packetLoss_total": stats.packetLoss.total,
|
||||
"packetLoss_download": stats.packetLoss.download,
|
||||
"packetLoss_upload": stats.packetLoss.upload
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts statitistics to format used by VideoLayout
|
||||
* @param stats
|
||||
* @returns {{bitrate: {download: *, upload: *}, packetLoss: {total: *, download: *, upload: *}}}
|
||||
*/
|
||||
function parseMUCStats(stats) {
|
||||
return {
|
||||
bitrate: {
|
||||
download: stats.bitrate_download,
|
||||
upload: stats.bitrate_upload
|
||||
},
|
||||
packetLoss: {
|
||||
total: stats.packetLoss_total,
|
||||
download: stats.packetLoss_download,
|
||||
upload: stats.packetLoss_upload
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var ConnectionQuality = {
|
||||
init: function () {
|
||||
APP.xmpp.addListener(XMPPEvents.REMOTE_STATS, this.updateRemoteStats);
|
||||
APP.statistics.addConnectionStatsListener(this.updateLocalStats);
|
||||
APP.statistics.addRemoteStatsStopListener(this.stopSendingStats);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the local statistics
|
||||
* @param data new statistics
|
||||
*/
|
||||
updateLocalStats: function (data) {
|
||||
stats = data;
|
||||
eventEmitter.emit(CQEvents.LOCALSTATS_UPDATED, 100 - stats.packetLoss.total, stats);
|
||||
if (sendIntervalId == null) {
|
||||
startSendingStats();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates remote statistics
|
||||
* @param jid the jid associated with the statistics
|
||||
* @param data the statistics
|
||||
*/
|
||||
updateRemoteStats: function (jid, data) {
|
||||
if (data == null || data.packetLoss_total == null) {
|
||||
eventEmitter.emit(CQEvents.REMOTESTATS_UPDATED, jid, null, null);
|
||||
return;
|
||||
}
|
||||
remoteStats[jid] = parseMUCStats(data);
|
||||
|
||||
eventEmitter.emit(CQEvents.REMOTESTATS_UPDATED,
|
||||
jid, 100 - data.packetLoss_total, remoteStats[jid]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops statistics sending.
|
||||
*/
|
||||
stopSendingStats: function () {
|
||||
clearInterval(sendIntervalId);
|
||||
sendIntervalId = null;
|
||||
//notify UI about stopping statistics gathering
|
||||
eventEmitter.emit(CQEvents.STOP);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the local statistics.
|
||||
*/
|
||||
getStats: function () {
|
||||
return stats;
|
||||
},
|
||||
|
||||
addListener: function (type, listener) {
|
||||
eventEmitter.on(type, listener);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = ConnectionQuality;
|
||||
@@ -1,4 +1,4 @@
|
||||
/* global $, alert, changeLocalVideo, chrome, config, connection, getConferenceHandler, getUserMediaWithConstraints, VideoLayout */
|
||||
/* global $, alert, changeLocalVideo, chrome, config, getConferenceHandler, getUserMediaWithConstraints */
|
||||
/**
|
||||
* Indicates that desktop stream is currently in use(for toggle purpose).
|
||||
* @type {boolean}
|
||||
@@ -23,12 +23,18 @@ var obtainDesktopStream = null;
|
||||
*/
|
||||
var _desktopSharingEnabled = null;
|
||||
|
||||
var EventEmitter = require("events");
|
||||
|
||||
var eventEmitter = new EventEmitter();
|
||||
|
||||
var DesktopSharingEventTypes = require("../../service/desktopsharing/DesktopSharingEventTypes");
|
||||
|
||||
/**
|
||||
* Method obtains desktop stream from WebRTC 'screen' source.
|
||||
* Flag 'chrome://flags/#enable-usermedia-screen-capture' must be enabled.
|
||||
*/
|
||||
function obtainWebRTCScreen(streamCallback, failCallback) {
|
||||
RTC.getUserMediaWithConstraints(
|
||||
APP.RTC.getUserMediaWithConstraints(
|
||||
['screen'],
|
||||
streamCallback,
|
||||
failCallback
|
||||
@@ -86,8 +92,8 @@ function isUpdateRequired(minVersion, extVersion)
|
||||
catch (e)
|
||||
{
|
||||
console.error("Failed to parse extension version", e);
|
||||
messageHandler.showError('Error',
|
||||
'Error when trying to detect desktopsharing extension.');
|
||||
APP.UI.messageHandler.showError("dialog.error",
|
||||
"dialog.detectext");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -135,7 +141,7 @@ function doGetStreamFromExtension(streamCallback, failCallback) {
|
||||
}
|
||||
console.log("Response from extension: " + response);
|
||||
if (response.streamId) {
|
||||
RTC.getUserMediaWithConstraints(
|
||||
APP.RTC.getUserMediaWithConstraints(
|
||||
['desktop'],
|
||||
function (stream) {
|
||||
streamCallback(stream);
|
||||
@@ -168,8 +174,8 @@ function obtainScreenFromExtension(streamCallback, failCallback) {
|
||||
function (arg) {
|
||||
console.log("Failed to install the extension", arg);
|
||||
failCallback(arg);
|
||||
messageHandler.showError('Error',
|
||||
'Failed to install desktop sharing extension');
|
||||
APP.UI.messageHandler.showError("dialog.error",
|
||||
"dialog.failtoinstall");
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -177,33 +183,6 @@ function obtainScreenFromExtension(streamCallback, failCallback) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} <tt>true</tt> if desktop sharing feature is available and enabled.
|
||||
*/
|
||||
function isDesktopSharingEnabled() {
|
||||
if (_desktopSharingEnabled === null) {
|
||||
if (obtainDesktopStream === obtainScreenFromExtension) {
|
||||
// Parse chrome version
|
||||
var userAgent = navigator.userAgent.toLowerCase();
|
||||
// We can assume that user agent is chrome, because it's enforced when 'ext' streaming method is set
|
||||
var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10);
|
||||
console.log("Chrome version" + userAgent, ver);
|
||||
_desktopSharingEnabled = ver >= 34;
|
||||
} else {
|
||||
_desktopSharingEnabled = obtainDesktopStream === obtainWebRTCScreen;
|
||||
}
|
||||
}
|
||||
return _desktopSharingEnabled;
|
||||
}
|
||||
|
||||
function showDesktopSharingButton() {
|
||||
if (isDesktopSharingEnabled()) {
|
||||
$('#desktopsharing').css({display: "inline"});
|
||||
} else {
|
||||
$('#desktopsharing').css({display: "none"});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method to toggle desktop sharing feature.
|
||||
* @param method pass "ext" to use chrome extension for desktop capture(chrome extension required),
|
||||
@@ -225,8 +204,6 @@ function setDesktopSharing(method) {
|
||||
|
||||
// Reset enabled cache
|
||||
_desktopSharingEnabled = null;
|
||||
|
||||
showDesktopSharingButton();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,74 +221,103 @@ function getSwitchStreamFailed(error) {
|
||||
}
|
||||
|
||||
function streamSwitchDone() {
|
||||
//window.setTimeout(
|
||||
// function () {
|
||||
switchInProgress = false;
|
||||
Toolbar.changeDesktopSharingButtonState(isUsingScreenStream);
|
||||
// }, 100
|
||||
//);
|
||||
eventEmitter.emit(
|
||||
DesktopSharingEventTypes.SWITCHING_DONE,
|
||||
isUsingScreenStream);
|
||||
}
|
||||
|
||||
function newStreamCreated(stream) {
|
||||
|
||||
var oldStream = connection.jingle.localVideo;
|
||||
|
||||
connection.jingle.localVideo = stream;
|
||||
|
||||
VideoLayout.changeLocalVideo(stream, !isUsingScreenStream);
|
||||
|
||||
var conferenceHandler = getConferenceHandler();
|
||||
if (conferenceHandler) {
|
||||
// FIXME: will block switchInProgress on true value in case of exception
|
||||
conferenceHandler.switchStreams(stream, oldStream, streamSwitchDone);
|
||||
} else {
|
||||
// We are done immediately
|
||||
console.error("No conference handler");
|
||||
messageHandler.showError('Error',
|
||||
'Unable to switch video stream.');
|
||||
streamSwitchDone();
|
||||
}
|
||||
function newStreamCreated(stream)
|
||||
{
|
||||
eventEmitter.emit(DesktopSharingEventTypes.NEW_STREAM_CREATED,
|
||||
stream, isUsingScreenStream, streamSwitchDone);
|
||||
}
|
||||
|
||||
/*
|
||||
* Toggles screen sharing.
|
||||
*/
|
||||
function toggleScreenSharing() {
|
||||
if (switchInProgress || !obtainDesktopStream) {
|
||||
console.warn("Switch in progress or no method defined");
|
||||
return;
|
||||
}
|
||||
switchInProgress = true;
|
||||
|
||||
if (!isUsingScreenStream)
|
||||
module.exports = {
|
||||
isUsingScreenStream: function () {
|
||||
return isUsingScreenStream;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {boolean} <tt>true</tt> if desktop sharing feature is available and enabled.
|
||||
*/
|
||||
isDesktopSharingEnabled: function () {
|
||||
if (_desktopSharingEnabled === null) {
|
||||
if (obtainDesktopStream === obtainScreenFromExtension) {
|
||||
// Parse chrome version
|
||||
var userAgent = navigator.userAgent.toLowerCase();
|
||||
// We can assume that user agent is chrome, because it's enforced when 'ext' streaming method is set
|
||||
var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10);
|
||||
console.log("Chrome version" + userAgent, ver);
|
||||
_desktopSharingEnabled = ver >= 34;
|
||||
} else {
|
||||
_desktopSharingEnabled = obtainDesktopStream === obtainWebRTCScreen;
|
||||
}
|
||||
}
|
||||
return _desktopSharingEnabled;
|
||||
},
|
||||
|
||||
init: function () {
|
||||
setDesktopSharing(config.desktopSharing);
|
||||
|
||||
// Initialize Chrome extension inline installs
|
||||
if (config.chromeExtensionId) {
|
||||
initInlineInstalls();
|
||||
}
|
||||
|
||||
eventEmitter.emit(DesktopSharingEventTypes.INIT);
|
||||
},
|
||||
|
||||
addListener: function(listener, type)
|
||||
{
|
||||
// Switch to desktop stream
|
||||
obtainDesktopStream(
|
||||
function (stream) {
|
||||
// We now use screen stream
|
||||
isUsingScreenStream = true;
|
||||
// Hook 'ended' event to restore camera when screen stream stops
|
||||
stream.addEventListener('ended',
|
||||
function (e) {
|
||||
if (!switchInProgress && isUsingScreenStream) {
|
||||
toggleScreenSharing();
|
||||
}
|
||||
}
|
||||
);
|
||||
newStreamCreated(stream);
|
||||
},
|
||||
getSwitchStreamFailed);
|
||||
} else {
|
||||
// Disable screen stream
|
||||
RTC.getUserMediaWithConstraints(
|
||||
['video'],
|
||||
function (stream) {
|
||||
// We are now using camera stream
|
||||
isUsingScreenStream = false;
|
||||
newStreamCreated(stream);
|
||||
},
|
||||
getSwitchStreamFailed, config.resolution || '360'
|
||||
);
|
||||
}
|
||||
}
|
||||
eventEmitter.on(type, listener);
|
||||
},
|
||||
|
||||
removeListener: function (listener,type) {
|
||||
eventEmitter.removeListener(type, listener);
|
||||
},
|
||||
|
||||
/*
|
||||
* Toggles screen sharing.
|
||||
*/
|
||||
toggleScreenSharing: function () {
|
||||
if (switchInProgress || !obtainDesktopStream) {
|
||||
console.warn("Switch in progress or no method defined");
|
||||
return;
|
||||
}
|
||||
switchInProgress = true;
|
||||
|
||||
if (!isUsingScreenStream)
|
||||
{
|
||||
// Switch to desktop stream
|
||||
obtainDesktopStream(
|
||||
function (stream) {
|
||||
// We now use screen stream
|
||||
isUsingScreenStream = true;
|
||||
// Hook 'ended' event to restore camera when screen stream stops
|
||||
stream.addEventListener('ended',
|
||||
function (e) {
|
||||
if (!switchInProgress && isUsingScreenStream) {
|
||||
toggleScreenSharing();
|
||||
}
|
||||
}
|
||||
);
|
||||
newStreamCreated(stream);
|
||||
},
|
||||
getSwitchStreamFailed);
|
||||
} else {
|
||||
// Disable screen stream
|
||||
APP.RTC.getUserMediaWithConstraints(
|
||||
['video'],
|
||||
function (stream) {
|
||||
// We are now using camera stream
|
||||
isUsingScreenStream = false;
|
||||
newStreamCreated(stream);
|
||||
},
|
||||
getSwitchStreamFailed, config.resolution || '360'
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
91
modules/keyboardshortcut/keyboardshortcut.js
Normal file
@@ -0,0 +1,91 @@
|
||||
//maps keycode to character, id of popover for given function and function
|
||||
var shortcuts = {
|
||||
67: {
|
||||
character: "C",
|
||||
id: "toggleChatPopover",
|
||||
function: APP.UI.toggleChat
|
||||
},
|
||||
70: {
|
||||
character: "F",
|
||||
id: "filmstripPopover",
|
||||
function: APP.UI.toggleFilmStrip
|
||||
},
|
||||
77: {
|
||||
character: "M",
|
||||
id: "mutePopover",
|
||||
function: APP.UI.toggleAudio
|
||||
},
|
||||
84: {
|
||||
character: "T",
|
||||
function: function() {
|
||||
if(!APP.RTC.localAudio.isMuted()) {
|
||||
APP.UI.toggleAudio();
|
||||
}
|
||||
}
|
||||
},
|
||||
86: {
|
||||
character: "V",
|
||||
id: "toggleVideoPopover",
|
||||
function: APP.UI.toggleVideo
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var KeyboardShortcut = {
|
||||
init: function () {
|
||||
window.onkeyup = function(e) {
|
||||
var keycode = e.which;
|
||||
if(!($(":focus").is("input[type=text]") ||
|
||||
$(":focus").is("input[type=password]") ||
|
||||
$(":focus").is("textarea"))) {
|
||||
if (typeof shortcuts[keycode] === "object") {
|
||||
shortcuts[keycode].function();
|
||||
}
|
||||
else if (keycode >= "0".charCodeAt(0) &&
|
||||
keycode <= "9".charCodeAt(0)) {
|
||||
APP.UI.clickOnVideo(keycode - "0".charCodeAt(0) + 1);
|
||||
}
|
||||
//esc while the smileys are visible hides them
|
||||
} else if (keycode === 27 && $('#smileysContainer').is(':visible')) {
|
||||
APP.UI.toggleSmileys();
|
||||
}
|
||||
};
|
||||
|
||||
window.onkeydown = function(e) {
|
||||
if(!($(":focus").is("input[type=text]") ||
|
||||
$(":focus").is("input[type=password]") ||
|
||||
$(":focus").is("textarea"))) {
|
||||
if(e.which === "T".charCodeAt(0)) {
|
||||
if(APP.RTC.localAudio.isMuted()) {
|
||||
APP.UI.toggleAudio();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var self = this;
|
||||
$('body').popover({ selector: '[data-toggle=popover]',
|
||||
trigger: 'click hover',
|
||||
content: function() {
|
||||
return this.getAttribute("content") +
|
||||
self.getShortcut(this.getAttribute("shortcut"));
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param id indicates the popover associated with the shortcut
|
||||
* @returns {string} the keyboard shortcut used for the id given
|
||||
*/
|
||||
getShortcut: function (id) {
|
||||
for (var keycode in shortcuts) {
|
||||
if (shortcuts.hasOwnProperty(keycode)) {
|
||||
if (shortcuts[keycode].id === id) {
|
||||
return " (" + shortcuts[keycode].character + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = KeyboardShortcut;
|
||||
65
modules/settings/Settings.js
Normal file
@@ -0,0 +1,65 @@
|
||||
var email = '';
|
||||
var displayName = '';
|
||||
var userId;
|
||||
var language = null;
|
||||
|
||||
|
||||
function supportsLocalStorage() {
|
||||
try {
|
||||
return 'localStorage' in window && window.localStorage !== null;
|
||||
} catch (e) {
|
||||
console.log("localstorage is not supported");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function generateUniqueId() {
|
||||
function _p8() {
|
||||
return (Math.random().toString(16) + "000000000").substr(2, 8);
|
||||
}
|
||||
return _p8() + _p8() + _p8() + _p8();
|
||||
}
|
||||
|
||||
if (supportsLocalStorage()) {
|
||||
if (!window.localStorage.jitsiMeetId) {
|
||||
window.localStorage.jitsiMeetId = generateUniqueId();
|
||||
console.log("generated id", window.localStorage.jitsiMeetId);
|
||||
}
|
||||
userId = window.localStorage.jitsiMeetId || '';
|
||||
email = window.localStorage.email || '';
|
||||
displayName = window.localStorage.displayname || '';
|
||||
language = window.localStorage.language;
|
||||
} else {
|
||||
console.log("local storage is not supported");
|
||||
userId = generateUniqueId();
|
||||
}
|
||||
|
||||
var Settings =
|
||||
{
|
||||
setDisplayName: function (newDisplayName) {
|
||||
displayName = newDisplayName;
|
||||
window.localStorage.displayname = displayName;
|
||||
return displayName;
|
||||
},
|
||||
setEmail: function (newEmail)
|
||||
{
|
||||
email = newEmail;
|
||||
window.localStorage.email = newEmail;
|
||||
return email;
|
||||
},
|
||||
getSettings: function () {
|
||||
return {
|
||||
email: email,
|
||||
displayName: displayName,
|
||||
uid: userId,
|
||||
language: language
|
||||
};
|
||||
},
|
||||
setLanguage: function (lang) {
|
||||
language = lang;
|
||||
window.localStorage.language = lang;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Settings;
|
||||
32
modules/simulcast/SimulcastLogger.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function SimulcastLogger(name, lvl) {
|
||||
this.name = name;
|
||||
this.lvl = lvl;
|
||||
}
|
||||
|
||||
SimulcastLogger.prototype.log = function (text) {
|
||||
if (this.lvl) {
|
||||
console.log(text);
|
||||
}
|
||||
};
|
||||
|
||||
SimulcastLogger.prototype.info = function (text) {
|
||||
if (this.lvl > 1) {
|
||||
console.info(text);
|
||||
}
|
||||
};
|
||||
|
||||
SimulcastLogger.prototype.fine = function (text) {
|
||||
if (this.lvl > 2) {
|
||||
console.log(text);
|
||||
}
|
||||
};
|
||||
|
||||
SimulcastLogger.prototype.error = function (text) {
|
||||
console.error(text);
|
||||
};
|
||||
|
||||
module.exports = SimulcastLogger;
|
||||
268
modules/simulcast/SimulcastReceiver.js
Normal file
@@ -0,0 +1,268 @@
|
||||
var SimulcastLogger = require("./SimulcastLogger");
|
||||
var SimulcastUtils = require("./SimulcastUtils");
|
||||
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
|
||||
|
||||
function SimulcastReceiver() {
|
||||
this.simulcastUtils = new SimulcastUtils();
|
||||
this.logger = new SimulcastLogger('SimulcastReceiver', 1);
|
||||
}
|
||||
|
||||
SimulcastReceiver.prototype._remoteVideoSourceCache = '';
|
||||
SimulcastReceiver.prototype._remoteMaps = {
|
||||
msid2Quality: {},
|
||||
ssrc2Msid: {},
|
||||
msid2ssrc: {},
|
||||
receivingVideoStreams: {}
|
||||
};
|
||||
|
||||
SimulcastReceiver.prototype._cacheRemoteVideoSources = function (lines) {
|
||||
this._remoteVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
|
||||
};
|
||||
|
||||
SimulcastReceiver.prototype._restoreRemoteVideoSources = function (lines) {
|
||||
this.simulcastUtils._replaceVideoSources(lines, this._remoteVideoSourceCache);
|
||||
};
|
||||
|
||||
SimulcastReceiver.prototype._ensureGoogConference = function (lines) {
|
||||
var sb;
|
||||
|
||||
this.logger.info('Ensuring x-google-conference flag...')
|
||||
|
||||
if (this.simulcastUtils._indexOfArray('a=x-google-flag:conference', lines) === this.simulcastUtils._emptyCompoundIndex) {
|
||||
// TODO(gp) do that for the audio as well as suggested by fippo.
|
||||
// Add the google conference flag
|
||||
sb = this.simulcastUtils._getVideoSources(lines);
|
||||
sb = ['a=x-google-flag:conference'].concat(sb);
|
||||
this.simulcastUtils._replaceVideoSources(lines, sb);
|
||||
}
|
||||
};
|
||||
|
||||
SimulcastReceiver.prototype._restoreSimulcastGroups = function (sb) {
|
||||
this._restoreRemoteVideoSources(sb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Restores the simulcast groups of the remote description. In
|
||||
* transformRemoteDescription we remove those in order for the set remote
|
||||
* description to succeed. The focus needs the signal the groups to new
|
||||
* participants.
|
||||
*
|
||||
* @param desc
|
||||
* @returns {*}
|
||||
*/
|
||||
SimulcastReceiver.prototype.reverseTransformRemoteDescription = function (desc) {
|
||||
var sb;
|
||||
|
||||
if (!this.simulcastUtils.isValidDescription(desc)) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
if (config.enableSimulcast) {
|
||||
sb = desc.sdp.split('\r\n');
|
||||
|
||||
this._restoreSimulcastGroups(sb);
|
||||
|
||||
desc = new RTCSessionDescription({
|
||||
type: desc.type,
|
||||
sdp: sb.join('\r\n')
|
||||
});
|
||||
}
|
||||
|
||||
return desc;
|
||||
};
|
||||
|
||||
SimulcastUtils.prototype._ensureOrder = function (lines) {
|
||||
var videoSources, sb;
|
||||
|
||||
videoSources = this.parseMedia(lines, ['video'])[0];
|
||||
sb = this._compileVideoSources(videoSources);
|
||||
|
||||
this._replaceVideoSources(lines, sb);
|
||||
};
|
||||
|
||||
SimulcastReceiver.prototype._updateRemoteMaps = function (lines) {
|
||||
var remoteVideoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0],
|
||||
videoSource, quality;
|
||||
|
||||
// (re) initialize the remote maps.
|
||||
this._remoteMaps.msid2Quality = {};
|
||||
this._remoteMaps.ssrc2Msid = {};
|
||||
this._remoteMaps.msid2ssrc = {};
|
||||
|
||||
var self = this;
|
||||
if (remoteVideoSources.groups && remoteVideoSources.groups.length !== 0) {
|
||||
remoteVideoSources.groups.forEach(function (group) {
|
||||
if (group.semantics === 'SIM' && group.ssrcs && group.ssrcs.length !== 0) {
|
||||
quality = 0;
|
||||
group.ssrcs.forEach(function (ssrc) {
|
||||
videoSource = remoteVideoSources.sources[ssrc];
|
||||
self._remoteMaps.msid2Quality[videoSource.msid] = quality++;
|
||||
self._remoteMaps.ssrc2Msid[videoSource.ssrc] = videoSource.msid;
|
||||
self._remoteMaps.msid2ssrc[videoSource.msid] = videoSource.ssrc;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
SimulcastReceiver.prototype._setReceivingVideoStream = function (resource, ssrc) {
|
||||
this._remoteMaps.receivingVideoStreams[resource] = ssrc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a stream with single video track, the one currently being
|
||||
* received by this endpoint.
|
||||
*
|
||||
* @param stream the remote simulcast stream.
|
||||
* @returns {webkitMediaStream}
|
||||
*/
|
||||
SimulcastReceiver.prototype.getReceivingVideoStream = function (stream) {
|
||||
var tracks, i, electedTrack, msid, quality = 0, receivingTrackId;
|
||||
|
||||
var self = this;
|
||||
if (config.enableSimulcast) {
|
||||
|
||||
stream.getVideoTracks().some(function (track) {
|
||||
return Object.keys(self._remoteMaps.receivingVideoStreams).some(function (resource) {
|
||||
var ssrc = self._remoteMaps.receivingVideoStreams[resource];
|
||||
var msid = self._remoteMaps.ssrc2Msid[ssrc];
|
||||
if (msid == [stream.id, track.id].join(' ')) {
|
||||
electedTrack = track;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!electedTrack) {
|
||||
// we don't have an elected track, choose by initial quality.
|
||||
tracks = stream.getVideoTracks();
|
||||
for (i = 0; i < tracks.length; i++) {
|
||||
msid = [stream.id, tracks[i].id].join(' ');
|
||||
if (this._remoteMaps.msid2Quality[msid] === quality) {
|
||||
electedTrack = tracks[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(gp) if the initialQuality could not be satisfied, lower
|
||||
// the requirement and try again.
|
||||
}
|
||||
}
|
||||
|
||||
return (electedTrack)
|
||||
? new webkitMediaStream([electedTrack])
|
||||
: stream;
|
||||
};
|
||||
|
||||
SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
|
||||
var resource = Strophe.getResourceFromJid(jid);
|
||||
var ssrc = this._remoteMaps.receivingVideoStreams[resource];
|
||||
|
||||
// If we haven't receiving a "changed" event yet, then we must be receiving
|
||||
// low quality (that the sender always streams).
|
||||
if(!ssrc)
|
||||
{
|
||||
var remoteStreamObject = APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
|
||||
var remoteStream = remoteStreamObject.getOriginalStream();
|
||||
var tracks = remoteStream.getVideoTracks();
|
||||
if (tracks) {
|
||||
for (var k = 0; k < tracks.length; k++) {
|
||||
var track = tracks[k];
|
||||
var msid = [remoteStream.id, track.id].join(' ');
|
||||
var _ssrc = this._remoteMaps.msid2ssrc[msid];
|
||||
var quality = this._remoteMaps.msid2Quality[msid];
|
||||
if (quality == 0) {
|
||||
ssrc = _ssrc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ssrc;
|
||||
};
|
||||
|
||||
SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
|
||||
{
|
||||
var sid, electedStream;
|
||||
var i, j, k;
|
||||
var jid = APP.xmpp.getJidFromSSRC(ssrc);
|
||||
if(jid && APP.RTC.remoteStreams[jid])
|
||||
{
|
||||
var remoteStreamObject = APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
|
||||
var remoteStream = remoteStreamObject.getOriginalStream();
|
||||
var tracks = remoteStream.getVideoTracks();
|
||||
if (tracks) {
|
||||
for (k = 0; k < tracks.length; k++) {
|
||||
var track = tracks[k];
|
||||
var msid = [remoteStream.id, track.id].join(' ');
|
||||
var tmp = this._remoteMaps.msid2ssrc[msid];
|
||||
if (tmp == ssrc) {
|
||||
electedStream = new webkitMediaStream([track]);
|
||||
sid = remoteStreamObject.sid;
|
||||
// stream found, stop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
console.debug(APP.RTC.remoteStreams, jid, ssrc);
|
||||
}
|
||||
|
||||
return {
|
||||
sid: sid,
|
||||
stream: electedStream
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the fully qualified msid (stream.id + track.id) associated to the
|
||||
* SSRC.
|
||||
*
|
||||
* @param ssrc
|
||||
* @returns {*}
|
||||
*/
|
||||
SimulcastReceiver.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
|
||||
return this._remoteMaps.ssrc2Msid[ssrc];
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the ssrc-group:SIM from the remote description bacause Chrome
|
||||
* either gets confused and thinks this is an FID group or, if an FID group
|
||||
* is already present, it fails to set the remote description.
|
||||
*
|
||||
* @param desc
|
||||
* @returns {*}
|
||||
*/
|
||||
SimulcastReceiver.prototype.transformRemoteDescription = function (desc) {
|
||||
|
||||
if (desc && desc.sdp) {
|
||||
var sb = desc.sdp.split('\r\n');
|
||||
|
||||
this._updateRemoteMaps(sb);
|
||||
this._cacheRemoteVideoSources(sb);
|
||||
|
||||
// NOTE(gp) this needs to be called after updateRemoteMaps because we
|
||||
// need the simulcast group in the _updateRemoteMaps() method.
|
||||
this.simulcastUtils._removeSimulcastGroup(sb);
|
||||
|
||||
if (desc.sdp.indexOf('a=ssrc-group:SIM') !== -1) {
|
||||
// We don't need the goog conference flag if we're not doing
|
||||
// simulcast.
|
||||
this._ensureGoogConference(sb);
|
||||
}
|
||||
|
||||
desc = new RTCSessionDescription({
|
||||
type: desc.type,
|
||||
sdp: sb.join('\r\n')
|
||||
});
|
||||
|
||||
this.logger.fine(['Transformed remote description', desc.sdp].join(' '));
|
||||
}
|
||||
|
||||
return desc;
|
||||
};
|
||||
|
||||
module.exports = SimulcastReceiver;
|
||||
521
modules/simulcast/SimulcastSender.js
Normal file
@@ -0,0 +1,521 @@
|
||||
var SimulcastLogger = require("./SimulcastLogger");
|
||||
var SimulcastUtils = require("./SimulcastUtils");
|
||||
|
||||
function SimulcastSender() {
|
||||
this.simulcastUtils = new SimulcastUtils();
|
||||
this.logger = new SimulcastLogger('SimulcastSender', 1);
|
||||
}
|
||||
|
||||
SimulcastSender.prototype.displayedLocalVideoStream = null;
|
||||
|
||||
SimulcastSender.prototype._generateGuid = (function () {
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
|
||||
return function () {
|
||||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
|
||||
s4() + '-' + s4() + s4() + s4();
|
||||
};
|
||||
}());
|
||||
|
||||
// Returns a random integer between min (included) and max (excluded)
|
||||
// Using Math.round() gives a non-uniform distribution!
|
||||
SimulcastSender.prototype._generateRandomSSRC = function () {
|
||||
var min = 0, max = 0xffffffff;
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
};
|
||||
|
||||
SimulcastSender.prototype.getLocalVideoStream = function () {
|
||||
return (this.displayedLocalVideoStream != null)
|
||||
? this.displayedLocalVideoStream
|
||||
// in case we have no simulcast at all, i.e. we didn't perform the GUM
|
||||
: APP.RTC.localVideo.getOriginalStream();
|
||||
};
|
||||
|
||||
function NativeSimulcastSender() {
|
||||
SimulcastSender.call(this); // call the super constructor.
|
||||
}
|
||||
|
||||
NativeSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
|
||||
|
||||
NativeSimulcastSender.prototype._localExplosionMap = {};
|
||||
NativeSimulcastSender.prototype._isUsingScreenStream = false;
|
||||
NativeSimulcastSender.prototype._localVideoSourceCache = '';
|
||||
|
||||
NativeSimulcastSender.prototype.reset = function () {
|
||||
this._localExplosionMap = {};
|
||||
this._isUsingScreenStream = APP.desktopsharing.isUsingScreenStream();
|
||||
};
|
||||
|
||||
NativeSimulcastSender.prototype._cacheLocalVideoSources = function (lines) {
|
||||
this._localVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
|
||||
};
|
||||
|
||||
NativeSimulcastSender.prototype._restoreLocalVideoSources = function (lines) {
|
||||
this.simulcastUtils._replaceVideoSources(lines, this._localVideoSourceCache);
|
||||
};
|
||||
|
||||
NativeSimulcastSender.prototype._appendSimulcastGroup = function (lines) {
|
||||
var videoSources, ssrcGroup, simSSRC, numOfSubs = 2, i, sb, msid;
|
||||
|
||||
this.logger.info('Appending simulcast group...');
|
||||
|
||||
// Get the primary SSRC information.
|
||||
videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
|
||||
|
||||
// Start building the SIM SSRC group.
|
||||
ssrcGroup = ['a=ssrc-group:SIM'];
|
||||
|
||||
// The video source buffer.
|
||||
sb = [];
|
||||
|
||||
// Create the simulcast sub-streams.
|
||||
for (i = 0; i < numOfSubs; i++) {
|
||||
// TODO(gp) prevent SSRC collision.
|
||||
simSSRC = this._generateRandomSSRC();
|
||||
ssrcGroup.push(simSSRC);
|
||||
|
||||
if (videoSources.base) {
|
||||
sb.splice.apply(sb, [sb.length, 0].concat(
|
||||
[["a=ssrc:", simSSRC, " cname:", videoSources.base.cname].join(''),
|
||||
["a=ssrc:", simSSRC, " msid:", videoSources.base.msid].join('')]
|
||||
));
|
||||
}
|
||||
|
||||
this.logger.info(['Generated substream ', i, ' with SSRC ', simSSRC, '.'].join(''));
|
||||
|
||||
}
|
||||
|
||||
// Add the group sim layers.
|
||||
sb.splice(0, 0, ssrcGroup.join(' '))
|
||||
|
||||
this.simulcastUtils._replaceVideoSources(lines, sb);
|
||||
};
|
||||
|
||||
// Does the actual patching.
|
||||
NativeSimulcastSender.prototype._ensureSimulcastGroup = function (lines) {
|
||||
|
||||
this.logger.info('Ensuring simulcast group...');
|
||||
|
||||
if (this.simulcastUtils._indexOfArray('a=ssrc-group:SIM', lines) === this.simulcastUtils._emptyCompoundIndex) {
|
||||
this._appendSimulcastGroup(lines);
|
||||
this._cacheLocalVideoSources(lines);
|
||||
} else {
|
||||
// verify that the ssrcs participating in the SIM group are present
|
||||
// in the SDP (needed for presence).
|
||||
this._restoreLocalVideoSources(lines);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Produces a single stream with multiple tracks for local video sources.
|
||||
*
|
||||
* @param lines
|
||||
* @private
|
||||
*/
|
||||
NativeSimulcastSender.prototype._explodeSimulcastSenderSources = function (lines) {
|
||||
var sb, msid, sid, tid, videoSources, self;
|
||||
|
||||
this.logger.info('Exploding local video sources...');
|
||||
|
||||
videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
|
||||
|
||||
self = this;
|
||||
if (videoSources.groups && videoSources.groups.length !== 0) {
|
||||
videoSources.groups.forEach(function (group) {
|
||||
if (group.semantics === 'SIM') {
|
||||
group.ssrcs.forEach(function (ssrc) {
|
||||
|
||||
// Get the msid for this ssrc..
|
||||
if (self._localExplosionMap[ssrc]) {
|
||||
// .. either from the explosion map..
|
||||
msid = self._localExplosionMap[ssrc];
|
||||
} else {
|
||||
// .. or generate a new one (msid).
|
||||
sid = videoSources.sources[ssrc].msid
|
||||
.substring(0, videoSources.sources[ssrc].msid.indexOf(' '));
|
||||
|
||||
tid = self._generateGuid();
|
||||
msid = [sid, tid].join(' ');
|
||||
self._localExplosionMap[ssrc] = msid;
|
||||
}
|
||||
|
||||
// Assign it to the source object.
|
||||
videoSources.sources[ssrc].msid = msid;
|
||||
|
||||
// TODO(gp) Change the msid of associated sources.
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sb = this.simulcastUtils._compileVideoSources(videoSources);
|
||||
|
||||
this.simulcastUtils._replaceVideoSources(lines, sb);
|
||||
};
|
||||
|
||||
/**
|
||||
* GUM for simulcast.
|
||||
*
|
||||
* @param constraints
|
||||
* @param success
|
||||
* @param err
|
||||
*/
|
||||
NativeSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
|
||||
|
||||
// There's nothing special to do for native simulcast, so just do a normal GUM.
|
||||
navigator.webkitGetUserMedia(constraints, function (hqStream) {
|
||||
success(hqStream);
|
||||
}, err);
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepares the local description for public usage (i.e. to be signaled
|
||||
* through Jingle to the focus).
|
||||
*
|
||||
* @param desc
|
||||
* @returns {RTCSessionDescription}
|
||||
*/
|
||||
NativeSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
|
||||
var sb;
|
||||
|
||||
if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
|
||||
sb = desc.sdp.split('\r\n');
|
||||
|
||||
this._explodeSimulcastSenderSources(sb);
|
||||
|
||||
desc = new RTCSessionDescription({
|
||||
type: desc.type,
|
||||
sdp: sb.join('\r\n')
|
||||
});
|
||||
|
||||
this.logger.fine(['Exploded local video sources', desc.sdp].join(' '));
|
||||
|
||||
return desc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensures that the simulcast group is present in the answer, _if_ native
|
||||
* simulcast is enabled,
|
||||
*
|
||||
* @param desc
|
||||
* @returns {*}
|
||||
*/
|
||||
NativeSimulcastSender.prototype.transformAnswer = function (desc) {
|
||||
|
||||
if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
var sb = desc.sdp.split('\r\n');
|
||||
|
||||
// Even if we have enabled native simulcasting previously
|
||||
// (with a call to SLD with an appropriate SDP, for example),
|
||||
// createAnswer seems to consistently generate incomplete SDP
|
||||
// with missing SSRCS.
|
||||
//
|
||||
// So, subsequent calls to SLD will have missing SSRCS and presence
|
||||
// won't have the complete list of SRCs.
|
||||
this._ensureSimulcastGroup(sb);
|
||||
|
||||
desc = new RTCSessionDescription({
|
||||
type: desc.type,
|
||||
sdp: sb.join('\r\n')
|
||||
});
|
||||
|
||||
this.logger.fine(['Transformed answer', desc.sdp].join(' '));
|
||||
|
||||
return desc;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param desc
|
||||
* @returns {*}
|
||||
*/
|
||||
NativeSimulcastSender.prototype.transformLocalDescription = function (desc) {
|
||||
return desc;
|
||||
};
|
||||
|
||||
NativeSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
|
||||
// Nothing to do here, native simulcast does that auto-magically.
|
||||
};
|
||||
|
||||
NativeSimulcastSender.prototype.constructor = NativeSimulcastSender;
|
||||
|
||||
function SimpleSimulcastSender() {
|
||||
SimulcastSender.call(this);
|
||||
}
|
||||
|
||||
SimpleSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
|
||||
|
||||
SimpleSimulcastSender.prototype.localStream = null;
|
||||
SimpleSimulcastSender.prototype._localMaps = {
|
||||
msids: [],
|
||||
msid2ssrc: {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Groups local video sources together in the ssrc-group:SIM group.
|
||||
*
|
||||
* @param lines
|
||||
* @private
|
||||
*/
|
||||
SimpleSimulcastSender.prototype._groupLocalVideoSources = function (lines) {
|
||||
var sb, videoSources, ssrcs = [], ssrc;
|
||||
|
||||
this.logger.info('Grouping local video sources...');
|
||||
|
||||
videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
|
||||
|
||||
for (ssrc in videoSources.sources) {
|
||||
// jitsi-meet destroys/creates streams at various places causing
|
||||
// the original local stream ids to change. The only thing that
|
||||
// remains unchanged is the trackid.
|
||||
this._localMaps.msid2ssrc[videoSources.sources[ssrc].msid.split(' ')[1]] = ssrc;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
// TODO(gp) add only "free" sources.
|
||||
this._localMaps.msids.forEach(function (msid) {
|
||||
ssrcs.push(self._localMaps.msid2ssrc[msid]);
|
||||
});
|
||||
|
||||
if (!videoSources.groups) {
|
||||
videoSources.groups = [];
|
||||
}
|
||||
|
||||
videoSources.groups.push({
|
||||
'semantics': 'SIM',
|
||||
'ssrcs': ssrcs
|
||||
});
|
||||
|
||||
sb = this.simulcastUtils._compileVideoSources(videoSources);
|
||||
|
||||
this.simulcastUtils._replaceVideoSources(lines, sb);
|
||||
};
|
||||
|
||||
/**
|
||||
* GUM for simulcast.
|
||||
*
|
||||
* @param constraints
|
||||
* @param success
|
||||
* @param err
|
||||
*/
|
||||
SimpleSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
|
||||
|
||||
// TODO(gp) what if we request a resolution not supported by the hardware?
|
||||
// TODO(gp) make the lq stream configurable; although this wouldn't work with native simulcast
|
||||
var lqConstraints = {
|
||||
audio: false,
|
||||
video: {
|
||||
mandatory: {
|
||||
maxWidth: 320,
|
||||
maxHeight: 180,
|
||||
maxFrameRate: 15
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.logger.info('HQ constraints: ', constraints);
|
||||
this.logger.info('LQ constraints: ', lqConstraints);
|
||||
|
||||
|
||||
// NOTE(gp) if we request the lq stream first webkitGetUserMedia
|
||||
// fails randomly. Tested with Chrome 37. As fippo suggested, the
|
||||
// reason appears to be that Chrome only acquires the cam once and
|
||||
// then downscales the picture (https://code.google.com/p/chromium/issues/detail?id=346616#c11)
|
||||
|
||||
var self = this;
|
||||
navigator.webkitGetUserMedia(constraints, function (hqStream) {
|
||||
|
||||
self.localStream = hqStream;
|
||||
|
||||
// reset local maps.
|
||||
self._localMaps.msids = [];
|
||||
self._localMaps.msid2ssrc = {};
|
||||
|
||||
// add hq trackid to local map
|
||||
self._localMaps.msids.push(hqStream.getVideoTracks()[0].id);
|
||||
|
||||
navigator.webkitGetUserMedia(lqConstraints, function (lqStream) {
|
||||
|
||||
self.displayedLocalVideoStream = lqStream;
|
||||
|
||||
// NOTE(gp) The specification says Array.forEach() will visit
|
||||
// the array elements in numeric order, and that it doesn't
|
||||
// visit elements that don't exist.
|
||||
|
||||
// add lq trackid to local map
|
||||
self._localMaps.msids.splice(0, 0, lqStream.getVideoTracks()[0].id);
|
||||
|
||||
self.localStream.addTrack(lqStream.getVideoTracks()[0]);
|
||||
success(self.localStream);
|
||||
}, err);
|
||||
}, err);
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepares the local description for public usage (i.e. to be signaled
|
||||
* through Jingle to the focus).
|
||||
*
|
||||
* @param desc
|
||||
* @returns {RTCSessionDescription}
|
||||
*/
|
||||
SimpleSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
|
||||
var sb;
|
||||
|
||||
if (!this.simulcastUtils.isValidDescription(desc)) {
|
||||
return desc;
|
||||
}
|
||||
|
||||
sb = desc.sdp.split('\r\n');
|
||||
|
||||
this._groupLocalVideoSources(sb);
|
||||
|
||||
desc = new RTCSessionDescription({
|
||||
type: desc.type,
|
||||
sdp: sb.join('\r\n')
|
||||
});
|
||||
|
||||
this.logger.fine('Grouped local video sources');
|
||||
this.logger.fine(desc.sdp);
|
||||
|
||||
return desc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensures that the simulcast group is present in the answer, _if_ native
|
||||
* simulcast is enabled,
|
||||
*
|
||||
* @param desc
|
||||
* @returns {*}
|
||||
*/
|
||||
SimpleSimulcastSender.prototype.transformAnswer = function (desc) {
|
||||
return desc;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param desc
|
||||
* @returns {*}
|
||||
*/
|
||||
SimpleSimulcastSender.prototype.transformLocalDescription = function (desc) {
|
||||
|
||||
var sb = desc.sdp.split('\r\n');
|
||||
|
||||
this.simulcastUtils._removeSimulcastGroup(sb);
|
||||
|
||||
desc = new RTCSessionDescription({
|
||||
type: desc.type,
|
||||
sdp: sb.join('\r\n')
|
||||
});
|
||||
|
||||
this.logger.fine('Transformed local description');
|
||||
this.logger.fine(desc.sdp);
|
||||
|
||||
return desc;
|
||||
};
|
||||
|
||||
SimpleSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
|
||||
var trackid;
|
||||
|
||||
var self = this;
|
||||
this.logger.log(['Requested to', enabled ? 'enable' : 'disable', ssrc].join(' '));
|
||||
if (Object.keys(this._localMaps.msid2ssrc).some(function (tid) {
|
||||
// Search for the track id that corresponds to the ssrc
|
||||
if (self._localMaps.msid2ssrc[tid] == ssrc) {
|
||||
trackid = tid;
|
||||
return true;
|
||||
}
|
||||
}) && self.localStream.getVideoTracks().some(function (track) {
|
||||
// Start/stop the track that corresponds to the track id
|
||||
if (track.id === trackid) {
|
||||
track.enabled = enabled;
|
||||
return true;
|
||||
}
|
||||
})) {
|
||||
this.logger.log([trackid, enabled ? 'enabled' : 'disabled'].join(' '));
|
||||
$(document).trigger(enabled
|
||||
? 'simulcastlayerstarted'
|
||||
: 'simulcastlayerstopped');
|
||||
} else {
|
||||
this.logger.error("I don't have a local stream with SSRC " + ssrc);
|
||||
}
|
||||
};
|
||||
|
||||
SimpleSimulcastSender.prototype.constructor = SimpleSimulcastSender;
|
||||
|
||||
function NoSimulcastSender() {
|
||||
SimulcastSender.call(this);
|
||||
}
|
||||
|
||||
NoSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
|
||||
|
||||
/**
|
||||
* GUM for simulcast.
|
||||
*
|
||||
* @param constraints
|
||||
* @param success
|
||||
* @param err
|
||||
*/
|
||||
NoSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
|
||||
navigator.webkitGetUserMedia(constraints, function (hqStream) {
|
||||
success(hqStream);
|
||||
}, err);
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepares the local description for public usage (i.e. to be signaled
|
||||
* through Jingle to the focus).
|
||||
*
|
||||
* @param desc
|
||||
* @returns {RTCSessionDescription}
|
||||
*/
|
||||
NoSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
|
||||
return desc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensures that the simulcast group is present in the answer, _if_ native
|
||||
* simulcast is enabled,
|
||||
*
|
||||
* @param desc
|
||||
* @returns {*}
|
||||
*/
|
||||
NoSimulcastSender.prototype.transformAnswer = function (desc) {
|
||||
return desc;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param desc
|
||||
* @returns {*}
|
||||
*/
|
||||
NoSimulcastSender.prototype.transformLocalDescription = function (desc) {
|
||||
return desc;
|
||||
};
|
||||
|
||||
NoSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
|
||||
|
||||
};
|
||||
|
||||
NoSimulcastSender.prototype.constructor = NoSimulcastSender;
|
||||
|
||||
module.exports = {
|
||||
"native": NativeSimulcastSender,
|
||||
"no": NoSimulcastSender
|
||||
}
|
||||