Compare commits

...

126 Commits
332 ... 428

Author SHA1 Message Date
hristo
15f4f03ba3 Commit from translate.jitsi.org by user hristo.: 11 of 149 strings translated (0 fuzzy). 2015-03-11 17:50:42 +00:00
hristoterezov
4f9b6f7180 Changes the format of email text in the language resource files. 2015-03-11 18:54:43 +02:00
hristoterezov
b36ec5fd01 Moves supported browser list from the email message to the JS 2015-03-11 10:53:39 +02:00
ibauersachs
ac95ea03fe Commit from translate.jitsi.org by user ibauersachs.: 148 of 148 strings translated (0 fuzzy). 2015-03-11 08:51:16 +00:00
hristoterezov
ae535fcb7d Replaces %0D%0A with \n in the email template text. 2015-03-11 10:29:21 +02:00
hristoterezov
957cc6afc1 Merge branch 'master' of github.com:jitsi/jitsi-meet 2015-03-10 17:25:44 +02:00
hristoterezov
16fdd59617 Adds turkish language. 2015-03-10 17:24:27 +02:00
George Politis
fabf8f42c6 Updates .gitignore. 2015-03-10 15:50:26 +01:00
ibauersachs
c98a56dc37 Commit from translate.jitsi.org by user ibauersachs.: 135 of 135 strings translated (0 fuzzy). 2015-03-10 10:10:08 +00:00
hristoterezov
deb68dd420 Fixes translation issues 2015-03-10 11:38:09 +02:00
Boris Grozev
0fd1a7fa08 Removes embedded html as per Ingo's suggestion. 2015-03-09 19:14:11 +01:00
Boris Grozev
c6ff8aa5dd Tries to improve readibility... 2015-03-09 18:44:42 +01:00
Boris Grozev
06f025e92a Fix formatting. 2015-03-09 18:29:47 +01:00
Boris Grozev
f14329f2cd Adds instructions to enable logging. 2015-03-09 18:27:56 +01:00
Boris Grozev
53e525597a Add .swp files to .gitignore. 2015-03-09 18:27:55 +01:00
George Politis
54b3cbcf94 Reverts config.js 2015-03-09 17:58:11 +01:00
George Politis
2852740e71 Updates the Makefile. It calls npm update before building the project. 2015-03-09 17:41:17 +01:00
George Politis
5322ba086b Reverts previous change of the Makefile. 2015-03-09 17:39:16 +01:00
hristoterezov
d2f95f3c81 Fixes some translation issues. 2015-03-09 17:50:13 +02:00
George Politis
3747251821 Adds dependency to specific commit for sdp-interop. 2015-03-09 16:21:53 +01:00
George Politis
159ba82167 Updates app.bundle.js with latest sdp-interop module that offers support for ssrc-groups. 2015-03-09 15:25:47 +01:00
George Politis
e34a8e6b60 Updates app.bundle.js with latest sdp-interop module. 2015-03-09 11:11:25 +01:00
jitsi-pootle
17a6e360a2 New files added from translate.jitsi.org based on templates 2015-03-09 08:16:17 +00:00
George Politis
b690f5d4a1 Updates app.bundle.js. 2015-03-05 20:25:50 +01:00
Damian Minkov
30f3168bf7 Adds watch file for the deb. 2015-03-05 19:23:55 +02:00
Damian Minkov
115f2e4663 Updates the patch for debian src package. 2015-03-05 19:23:29 +02:00
Damian Minkov
fa15a75928 Updates missing-source location. 2015-03-05 18:01:27 +02:00
Damian Minkov
4db75446f3 Do not edit /etc/nginx/nginx.conf file directly. 2015-03-05 17:52:05 +02:00
George Politis
d9f7b8b6cc Adds a comment about how FF handles the video.src attribute. 2015-03-05 11:26:44 +01:00
George Politis
05bbfda5bb Adds support for FF/multistream. 2015-03-04 21:33:06 +01:00
paweldomas
e465b3ed90 Removes unnecessary error dialog when desktop sharing is started before the conference. 2015-03-04 13:58:07 +01:00
hristoterezov
1825f47ef2 Adds translation support for placeholder attributes. 2015-03-04 12:59:52 +02:00
ibauersachs
169d613ac4 Commit from translate.jitsi.org by user ibauersachs.: 135 of 135 strings translated (0 fuzzy). 2015-03-04 09:37:19 +00:00
Ingo Bauersachs
3dac5eeff5 Fix punctuation spacing 2015-03-04 10:27:44 +01:00
hristoterezov
f79651f806 Merge branch 'translation' 2015-03-04 11:06:02 +02:00
hristoterezov
6048d0a325 Fixes the html attributes for translation options. 2015-02-27 20:05:32 +02:00
hristoterezov
6f12446c99 Fixes translation of plurals. 2015-02-27 19:58:05 +02:00
hristoterezov
af682f8727 Changes the configuration of translation module to retrieve the resources synchronous. Removes the default values. 2015-02-26 17:35:35 +02:00
paweldomas
9123923818 Displays reservation system error if one is returned by the focus. 2015-02-26 14:59:01 +01:00
Boris Grozev
aee7a8e1bd Fix a bug with stats accumulating. Fix a typo. 2015-02-26 10:12:06 +01:00
paweldomas
5b44edb3cc Handles graceful-shutdown focus error response. 2015-02-25 16:55:22 +01:00
Boris Grozev
806d4ea443 Filters some statistics from the logs. Increases the interval for logged statistics. 2015-02-25 11:38:04 +01:00
hristoterezov
1e35ca5e4d Removes the firefox issue link. 2015-02-25 12:06:32 +02:00
hristoterezov
d4f00d76ab Removes webrtcrequired.html. 2015-02-25 11:07:33 +02:00
hristoterezov
37282e63b3 Fixes the references with chromeonly page. 2015-02-24 18:24:39 +02:00
hristoterezov
4b218499ae Redesigns the supported browsers page. 2015-02-24 18:08:24 +02:00
hristoterezov
f16a1cdf44 Changes the implementation to store the language in local storage. Adds new languages. 2015-02-24 12:49:46 +02:00
hristoterezov
702f02568d Fixes issue with the buttons in the message handler. 2015-02-24 10:57:41 +02:00
paweldomas
b6808d87bc Updates app.bundle.js. 2015-02-23 16:15:42 +01:00
paweldomas
8042bd2aa6 Handles MUC destroyed event. 2015-02-23 16:13:38 +01:00
paweldomas
053b2d5af2 Fixes logout confirmation dialog. 2015-02-23 16:13:26 +01:00
Ingo Bauersachs
222164333b Fix some typos and punctuation spacing. 2015-02-23 13:35:16 +01:00
ibauersachs
db50810e4b Commit from translate.jitsi.org by user ibauersachs.: 130 of 130 strings translated (0 fuzzy). 2015-02-23 12:33:29 +00:00
hristo
720851dcb9 Commit from translate.jitsi.org by user hristo.: 3 of 130 strings translated (0 fuzzy). 2015-02-23 11:44:51 +00:00
jitsi-pootle
d7203b8b1a New files added from translate.jitsi.org based on templates 2015-02-23 11:42:53 +00:00
jitsi-pootle
204ca29ed7 New files added from translate.jitsi.org based on templates 2015-02-23 11:42:33 +00:00
hristoterezov
fdada53a4a Fixes issue with the recording. 2015-02-23 12:06:42 +02:00
hristoterezov
81eb3754a0 Fixes "focus not available" notifications. 2015-02-23 11:20:35 +02:00
hristoterezov
d260f1db61 Removes unused package.son files. 2015-02-20 18:21:58 +02:00
hristoterezov
74f078f166 Adds multi language support for message handlers. 2015-02-20 18:17:46 +02:00
paweldomas
e16cee4187 Delete old session ID and retry on 'session-invalid' response. Updates app.bundle.js. 2015-02-19 13:56:04 +01:00
paweldomas
a904e35c67 Adds auto-sign in feature and login/logout toolbar menu. 2015-02-19 13:49:51 +01:00
paweldomas
b87cd9f842 Moves Settings module out from the UI. 2015-02-19 13:49:41 +01:00
paweldomas
fed34e7671 Fixes PreziPlayer crash when invalid message is received. 2015-02-19 13:49:41 +01:00
George Politis
ed57f72117 Adds extra logging. 2015-02-19 13:27:44 +01:00
hristoterezov
4d39d4ccc3 Fixes issue with selected user resource jid variable. 2015-02-19 11:23:55 +02:00
hristoterezov
79cdd94833 Adds multi language support for notifications. 2015-02-13 18:28:35 +02:00
hristoterezov
e0645b41d3 Moves some function calls in UI service 2015-02-13 11:31:25 +02:00
hristoterezov
aa7f0c8a0b Merge branch 'master' of github.com:jitsi/jitsi-meet 2015-02-12 16:44:24 +02:00
hristoterezov
2362770cce Changes getUserMedia implementation to try lower resolution if the configured one is not supported. 2015-02-12 16:42:08 +02:00
Damian Minkov
8334036cf4 Moves admins definition as it seems on some distributions after latest lua updates, causes trouble for prosody. 2015-02-12 15:39:56 +02:00
Damian Minkov
eec513e9e3 Adds admins line after muc component. 2015-02-12 13:50:21 +02:00
hristoterezov
f2a7a43ba7 Fixes audio level performance issue on avatar. 2015-02-11 18:29:20 +02:00
hristoterezov
61bbbaf6eb Adds gitattributes file that marks the generated files as binary files. 2015-02-09 15:50:49 +02:00
hristoterezov
3519a6ec7b Fixes the generated file. 2015-02-09 15:03:23 +02:00
hristoterezov
d21f994eee Removes rtp stats option and adds options for disabling/enabling stats and audio levels. 2015-02-09 14:51:25 +02:00
hristoterezov
b32acf0dfb Fixes the multi language support for the debian package. 2015-02-09 12:24:11 +02:00
hristoterezov
71a56e13d9 Fixes some issues with the tests 2015-02-09 12:21:23 +02:00
hristoterezov
0f6d0a0439 Adds methods required by torture 2015-02-09 10:12:55 +02:00
hristoterezov
3032ea7684 Implements basic multi language support. 2015-02-06 17:46:50 +02:00
hristoterezov
04cfbafc33 Fixes issue with recording. 2015-02-06 15:43:40 +02:00
hristoterezov
57fcee676a Fixes issues with accessing modules not from APP object. 2015-02-06 14:54:19 +02:00
hristoterezov
2f5d090ca5 Merge pull request #227 from odotom/odotom-patch-1
Fixes typos.
2015-02-03 15:42:30 +02:00
bgrozev
8d796f328b Update README.md
Clarify that the detailed instructions are for a 'manual' installation.
2015-02-03 12:45:01 +02:00
hristoterezov
ffb1d6ea17 Generates app bundle file 2015-02-02 20:00:45 +02:00
hristoterezov
4447e5dac6 Merge pull request #229 from schleussinger/master
Corrected Scope - fixes runtime JS error and  Chrome Ext desktop sharing for me
2015-02-02 19:47:59 +02:00
schleussinger
dbed14db5e Fix correct Scope - this fixes JS error and Chrome Ext desktop sharing for me 2015-02-01 13:14:18 +01:00
Thomas Odorfer
254103e21f Update UI.js
fix typo UI.getCredentials
2015-01-31 22:14:53 +01:00
Thomas Odorfer
d0b39e1c97 Update app.js
app.js: fix getCredentials - return object instead of function reference
2015-01-31 22:11:02 +01:00
hristoterezov
4bb555e4b2 Fixes authentication issues. 2015-01-29 11:43:40 +02:00
hristoterezov
8d0ee3ded9 Updates generated file. 2015-01-29 11:27:02 +02:00
hristoterezov
98d1ca8505 Fixes authentication issues. 2015-01-29 11:09:09 +02:00
hristoterezov
e766bad4ce Merge branch 'master' of https://github.com/Zalmoxisus/jitsi-meet
Conflicts:
	libs/modules/RTC.bundle.js
	libs/modules/simulcast.bundle.js
2015-01-28 18:06:09 +02:00
hristoterezov
9eb2873cfa Removes the bundles for every module and add bundle for the whole application. 2015-01-28 16:35:22 +02:00
hristoterezov
c7e2331284 Removes document bind events between modules. 2015-01-27 14:03:26 +02:00
hristoterezov
02ca5e5732 Merge branch 'master' of github.com:jitsi/jitsi-meet 2015-01-27 11:56:43 +02:00
hristoterezov
bc2d72638b Add events for data chanel. 2015-01-27 11:56:22 +02:00
Zalmoxisus
40de181959 Fixes audio-only (when GUM fails) 2015-01-26 19:54:26 +02:00
bgrozev
70bc071cb8 Merge pull request #220 from Zalmoxisus/master
Fixes some typos that cause errors
2015-01-26 09:29:59 -06:00
Zalmoxisus
567ac23c2c Fixes some typos that cause errors 2015-01-26 16:24:26 +02:00
hristoterezov
af50bd5b94 Resolves some module dependancies by replaces them with events. 2015-01-24 16:28:02 +02:00
hristoterezov
899f0ee83d Removes UI dependancies in the xmpp module. 2015-01-23 17:36:17 +02:00
hristoterezov
29b3ea07e0 Removes util.js. Fixes prezi. 2015-01-23 14:01:44 +02:00
hristoterezov
c0a316c7df Creates keyboard shortcuts module. 2015-01-22 18:26:05 +02:00
hristoterezov
f624833f1f Merge branch 'master' of github.com:jitsi/jitsi-meet 2015-01-22 18:02:56 +02:00
hristoterezov
4c661ffca6 Removes nickname global variable. 2015-01-22 18:02:37 +02:00
Boris Grozev
0819f23049 Adds instructings for building. 2015-01-22 17:52:28 +02:00
Boris Grozev
1e9a463245 Fix a typo. 2015-01-22 17:39:40 +02:00
Boris Grozev
447d8f5677 Makes the default make target execute "deploy" and "clean". 2015-01-22 17:36:12 +02:00
fo
d2453b1f1f Changed capitalisation in require statements. 2015-01-22 16:56:23 +02:00
Boris Grozev
9460138cc3 Fix pako reference. 2015-01-21 17:35:23 +02:00
Boris Grozev
0063461858 Uses pako from npm. 2015-01-21 17:13:22 +02:00
hristoterezov
248d7a3173 Moves ssrc2jid global variable to the xmpp module. 2015-01-21 13:55:20 +02:00
hristoterezov
51277270fe Fixes issues with the recording. 2015-01-20 18:12:32 +02:00
hristoterezov
394738394d Fixes NPE when creating non anonymous room 2015-01-20 18:07:03 +02:00
hristoterezov
6c4a5bd2bc Removes some global variables. Fixes recording. 2015-01-20 17:56:00 +02:00
hristoterezov
6347730dc7 Fixes some issues related to xmpp module creation. 2015-01-19 18:54:41 +02:00
hristoterezov
3da8e39745 Merges app.js and generates bundles. 2015-01-19 12:03:14 +02:00
hristoterezov
f4acf97b00 Merge branch 'master' of github.com:jitsi/jitsi-meet
Conflicts:
	app.js
	libs/strophe/strophe.jingle.js
	modules/xmpp/moderator.js
	muc.js
2015-01-19 12:00:30 +02:00
hristoterezov
e4e66a03d7 Creates initial version of xmpp module. 2015-01-19 11:20:00 +02:00
paweldomas
ed78c0053c Makes it possible to append URL parameters after room name. Adds ?login=true to enforce authenticated domain when anonymous domain is used. This allows to get moderator permissions after room has been created. 2015-01-16 13:22:02 +01:00
paweldomas
398fd18b8e Advertises dtls/sctp support in capabilities. 2015-01-16 12:35:31 +01:00
paweldomas
d3003d4fcd Adjusts anonymous domain functionality to work with Jicofo. 2015-01-14 17:59:50 +01:00
hristoterezov
ee94eca733 Creates desktop sharing module. 2015-01-13 15:11:05 +02:00
hristoterezov
0696fb2c5a Fixes issue with video mute. 2015-01-13 11:33:45 +02:00
hristoterezov
e6fbb0934e Removes local streams from the connection object. 2015-01-12 15:23:29 +02:00
127 changed files with 33672 additions and 18998 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.bundle.js -text -diff

3
.gitignore vendored
View File

@@ -1 +1,4 @@
node_modules
*.swp
.idea/
*.iml

View File

@@ -1,23 +1,24 @@
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
deploy:
@mkdir -p $(DEPLOY_DIR) && cp $(OUTPUT_DIR)/*.bundle.js $(DEPLOY_DIR)

View File

@@ -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.

812
app.js
View File

@@ -1,800 +1,54 @@
/* jshint -W117 */
/* application specific logic */
var connection = null;
var authenticatedUser = false;
/* Initial "authentication required" dialog */
var authDialog = null;
/* Loop retry ID that wits for other user to create the room */
var authRetryId = null;
var activecall = null;
var nickname = null;
var focusMucJid = null;
var roomName = null;
var ssrc2jid = {};
var bridgeIsDown = false;
//TODO: this array must be removed when firefox implement multistream support
var notReceivedSSRCs = [];
var jid2Ssrc = {};
/**
* Indicates whether ssrc is camera video or desktop stream.
* FIXME: remove those maps
*/
var ssrc2videoType = {};
/**
* Currently focused video "src"(displayed in large video).
* @type {String}
*/
var focusedVideoInfo = null;
var mutedAudios = {};
/**
* Remembers if we were muted by the focus.
* @type {boolean}
*/
var forceMuted = false;
/**
* Indicates if we have muted our audio before the conference has started.
* @type {boolean}
*/
var preMuted = false;
var localVideoSrc = null;
var flipXLocalVideo = true;
var isFullScreen = false;
var currentVideoWidth = null;
var currentVideoHeight = null;
var sessionTerminated = false;
var APP =
{
init: function () {
this.UI = require("./modules/UI/UI");
this.API = require("./modules/API/API");
this.connectionquality = require("./modules/connectionquality/connectionquality");
this.statistics = require("./modules/statistics/statistics");
this.RTC = require("./modules/RTC/RTC");
this.simulcast = require("./modules/simulcast/simulcast");
this.desktopsharing = require("./modules/desktopsharing/desktopsharing");
this.xmpp = require("./modules/xmpp/xmpp");
this.keyboardshortcut = require("./modules/keyboardshortcut/keyboardshortcut");
this.translation = require("./modules/translation/translation");
this.settings = require("./modules/settings/Settings");
}
};
function init() {
APP.RTC.start();
APP.xmpp.start(APP.UI.getCredentials());
APP.statistics.start();
APP.connectionquality.init();
RTC.addStreamListener(maybeDoJoin, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
RTC.start();
// Set default desktop sharing method
APP.desktopsharing.init();
var jid = document.getElementById('jid').value || config.hosts.anonymousdomain || config.hosts.domain || window.location.hostname;
connect(jid);
APP.keyboardshortcut.init();
}
function connect(jid, password) {
var localAudio, localVideo;
if (connection && connection.jingle) {
localAudio = connection.jingle.localAudio;
localVideo = connection.jingle.localVideo;
}
connection = new Strophe.Connection(document.getElementById('boshURL').value || config.bosh || '/http-bind');
var settings = UI.getSettings();
var email = settings.email;
var displayName = settings.displayName;
if(email) {
connection.emuc.addEmailToPresence(email);
} else {
connection.emuc.addUserIdToPresence(settings.uid);
}
if(displayName) {
connection.emuc.addDisplayNameToPresence(displayName);
}
if (connection.disco) {
// for chrome, add multistream cap
}
connection.jingle.pc_constraints = RTC.getPCConstraints();
if (config.useIPv6) {
// https://code.google.com/p/webrtc/issues/detail?id=2828
if (!connection.jingle.pc_constraints.optional) connection.jingle.pc_constraints.optional = [];
connection.jingle.pc_constraints.optional.push({googIPv6: true});
}
if (localAudio) connection.jingle.localAudio = localAudio;
if (localVideo) connection.jingle.localVideo = localVideo;
if(!password)
password = document.getElementById('password').value;
var anonymousConnectionFailed = false;
connection.connect(jid, password, function (status, msg) {
console.log('Strophe status changed to', Strophe.getStatusString(status));
if (status === Strophe.Status.CONNECTED) {
if (config.useStunTurn) {
connection.jingle.getStunAndTurnCredentials();
}
document.getElementById('connect').disabled = true;
console.info("My Jabber ID: " + connection.jid);
if(password)
authenticatedUser = true;
maybeDoJoin();
} else if (status === Strophe.Status.CONNFAIL) {
if(msg === 'x-strophe-bad-non-anon-jid') {
anonymousConnectionFailed = true;
}
} else if (status === Strophe.Status.DISCONNECTED) {
if(anonymousConnectionFailed) {
// prompt user for username and password
$(document).trigger('passwordrequired.main');
}
} else if (status === Strophe.Status.AUTHFAIL) {
// wrong password or username, prompt user
$(document).trigger('passwordrequired.main');
}
});
}
function maybeDoJoin() {
if (connection && connection.connected && Strophe.getResourceFromJid(connection.jid) // .connected is true while connecting?
&& (connection.jingle.localAudio || connection.jingle.localVideo)) {
doJoin();
}
}
function doJoin() {
if (!roomName) {
UI.generateRoomName();
}
Moderator.allocateConferenceFocus(
roomName, doJoinAfterFocus);
}
function doJoinAfterFocus() {
// Close authentication dialog if opened
if (authDialog) {
UI.messageHandler.closeDialog();
authDialog = null;
}
// Clear retry interval, so that we don't call 'doJoinAfterFocus' twice
if (authRetryId) {
window.clearTimeout(authRetryId);
authRetryId = null;
}
var roomjid;
roomjid = roomName;
if (config.useNicks) {
var nick = window.prompt('Your nickname (optional)');
if (nick) {
roomjid += '/' + nick;
} else {
roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
}
} else {
var tmpJid = Strophe.getNodeFromJid(connection.jid);
if(!authenticatedUser)
tmpJid = tmpJid.substr(0, 8);
roomjid += '/' + tmpJid;
}
connection.emuc.doJoin(roomjid);
}
function waitForRemoteVideo(selector, ssrc, stream, jid) {
// XXX(gp) so, every call to this function is *always* preceded by a call
// to the RTC.attachMediaStream() function but that call is *not* followed
// by an update to the videoSrcToSsrc map!
//
// The above way of doing things results in video SRCs that don't correspond
// to any SSRC for a short period of time (to be more precise, for as long
// the waitForRemoteVideo takes to complete). This causes problems (see
// bellow).
//
// I'm wondering why we need to do that; i.e. why call RTC.attachMediaStream()
// a second time in here and only then update the videoSrcToSsrc map? Why
// not simply update the videoSrcToSsrc map when the RTC.attachMediaStream()
// is called the first time? I actually do that in the lastN changed event
// handler because the "orphan" video SRC is causing troubles there. The
// purpose of this method would then be to fire the "videoactive.jingle".
//
// Food for though I guess :-)
if (selector.removed || !selector.parent().is(":visible")) {
console.warn("Media removed before had started", selector);
return;
}
if (stream.id === 'mixedmslabel') return;
if (selector[0].currentTime > 0) {
var videoStream = simulcast.getReceivingVideoStream(stream);
RTC.attachMediaStream(selector, videoStream); // FIXME: why do i have to do this for FF?
// FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type
// in order to get rid of too many maps
if (ssrc && jid) {
jid2Ssrc[Strophe.getResourceFromJid(jid)] = ssrc;
} else {
console.warn("No ssrc given for jid", jid);
}
$(document).trigger('videoactive.jingle', [selector]);
} else {
setTimeout(function () {
waitForRemoteVideo(selector, ssrc, stream, jid);
}, 250);
}
}
$(document).bind('remotestreamadded.jingle', function (event, data, sid) {
waitForPresence(data, sid);
});
function waitForPresence(data, sid) {
var sess = connection.jingle.sessions[sid];
var thessrc;
// look up an associated JID for a stream id
if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) {
// look only at a=ssrc: and _not_ at a=ssrc-group: lines
var ssrclines
= SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc:');
ssrclines = ssrclines.filter(function (line) {
// NOTE(gp) previously we filtered on the mslabel, but that property
// is not always present.
// return line.indexOf('mslabel:' + data.stream.label) !== -1;
return ((line.indexOf('msid:' + data.stream.id) !== -1));
});
if (ssrclines.length) {
thessrc = ssrclines[0].substring(7).split(' ')[0];
// We signal our streams (through Jingle to the focus) before we set
// our presence (through which peers associate remote streams to
// jids). So, it might arrive that a remote stream is added but
// ssrc2jid is not yet updated and thus data.peerjid cannot be
// successfully set. Here we wait for up to a second for the
// presence to arrive.
if (!ssrc2jid[thessrc]) {
// TODO(gp) limit wait duration to 1 sec.
setTimeout(function(d, s) {
return function() {
waitForPresence(d, s);
}
}(data, sid), 250);
return;
}
// ok to overwrite the one from focus? might save work in colibri.js
console.log('associated jid', ssrc2jid[thessrc], data.peerjid);
if (ssrc2jid[thessrc]) {
data.peerjid = ssrc2jid[thessrc];
}
}
}
//TODO: this code should be removed when firefox implement multistream support
if(RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_FIREFOX)
{
if((notReceivedSSRCs.length == 0) ||
!ssrc2jid[notReceivedSSRCs[notReceivedSSRCs.length - 1]])
{
// TODO(gp) limit wait duration to 1 sec.
setTimeout(function(d, s) {
return function() {
waitForPresence(d, s);
}
}(data, sid), 250);
return;
}
thessrc = notReceivedSSRCs.pop();
if (ssrc2jid[thessrc]) {
data.peerjid = ssrc2jid[thessrc];
}
}
RTC.createRemoteStream(data, sid, thessrc);
var isVideo = data.stream.getVideoTracks().length > 0;
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
if (isVideo &&
data.peerjid && sess.peerjid === data.peerjid &&
data.stream.getVideoTracks().length === 0 &&
connection.jingle.localVideo.getVideoTracks().length > 0) {
//
window.setTimeout(function () {
sendKeyframe(sess.peerconnection);
}, 3000);
}
}
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
function sendKeyframe(pc) {
console.log('sendkeyframe', pc.iceConnectionState);
if (pc.iceConnectionState !== 'connected') return; // safe...
pc.setRemoteDescription(
pc.remoteDescription,
function () {
pc.createAnswer(
function (modifiedAnswer) {
pc.setLocalDescription(
modifiedAnswer,
function () {
// noop
},
function (error) {
console.log('triggerKeyframe setLocalDescription failed', error);
UI.messageHandler.showError();
}
);
},
function (error) {
console.log('triggerKeyframe createAnswer failed', error);
UI.messageHandler.showError();
}
);
},
function (error) {
console.log('triggerKeyframe setRemoteDescription failed', error);
UI.messageHandler.showError();
}
);
}
// Really mute video, i.e. dont even send black frames
function muteVideo(pc, unmute) {
// FIXME: this probably needs another of those lovely state safeguards...
// which checks for iceconn == connected and sigstate == stable
pc.setRemoteDescription(pc.remoteDescription,
function () {
pc.createAnswer(
function (answer) {
var sdp = new SDP(answer.sdp);
if (sdp.media.length > 1) {
if (unmute)
sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
else
sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
sdp.raw = sdp.session + sdp.media.join('');
answer.sdp = sdp.raw;
}
pc.setLocalDescription(answer,
function () {
console.log('mute SLD ok');
},
function (error) {
console.log('mute SLD error');
UI.messageHandler.showError('Error',
'Oops! Something went wrong and we failed to ' +
'mute! (SLD Failure)');
}
);
},
function (error) {
console.log(error);
UI.messageHandler.showError();
}
);
},
function (error) {
console.log('muteVideo SRD error');
UI.messageHandler.showError('Error',
'Oops! Something went wrong and we failed to stop video!' +
'(SRD Failure)');
}
);
}
$(document).bind('setLocalDescription.jingle', function (event, sid) {
// put our ssrcs into presence so other clients can identify our stream
var sess = connection.jingle.sessions[sid];
var newssrcs = [];
var media = simulcast.parseMedia(sess.peerconnection.localDescription);
media.forEach(function (media) {
if(Object.keys(media.sources).length > 0) {
// TODO(gp) maybe exclude FID streams?
Object.keys(media.sources).forEach(function (ssrc) {
newssrcs.push({
'ssrc': ssrc,
'type': media.type,
'direction': media.direction
});
});
}
else if(sess.localStreamsSSRC && sess.localStreamsSSRC[media.type])
{
newssrcs.push({
'ssrc': sess.localStreamsSSRC[media.type],
'type': media.type,
'direction': media.direction
});
}
});
console.log('new ssrcs', newssrcs);
// Have to clear presence map to get rid of removed streams
connection.emuc.clearPresenceMedia();
if (newssrcs.length > 0) {
for (var i = 1; i <= newssrcs.length; i ++) {
// Change video type to screen
if (newssrcs[i-1].type === 'video' && isUsingScreenStream) {
newssrcs[i-1].type = 'screen';
}
connection.emuc.addMediaToPresence(i,
newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction);
}
connection.emuc.sendPresence();
}
});
$(document).bind('iceconnectionstatechange.jingle', function (event, sid, session) {
switch (session.peerconnection.iceConnectionState) {
case 'checking':
session.timeChecking = (new Date()).getTime();
session.firstconnect = true;
break;
case 'completed': // on caller side
case 'connected':
if (session.firstconnect) {
session.firstconnect = false;
var metadata = {};
metadata.setupTime = (new Date()).getTime() - session.timeChecking;
session.peerconnection.getStats(function (res) {
if(res && res.result) {
res.result().forEach(function (report) {
if (report.type == 'googCandidatePair' && report.stat('googActiveConnection') == 'true') {
metadata.localCandidateType = report.stat('googLocalCandidateType');
metadata.remoteCandidateType = report.stat('googRemoteCandidateType');
// log pair as well so we can get nice pie charts
metadata.candidatePair = report.stat('googLocalCandidateType') + ';' + report.stat('googRemoteCandidateType');
if (report.stat('googRemoteAddress').indexOf('[') === 0) {
metadata.ipv6 = true;
}
}
});
}
});
}
break;
}
});
$(document).bind('presence.muc', function (event, jid, info, pres) {
//check if the video bridge is available
if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
bridgeIsDown = true;
UI.messageHandler.showError("Error",
"Jitsi Videobridge is currently unavailable. Please try again later!");
}
if (info.isFocus)
{
return;
}
// Remove old ssrcs coming from the jid
Object.keys(ssrc2jid).forEach(function (ssrc) {
if (ssrc2jid[ssrc] == jid) {
delete ssrc2jid[ssrc];
delete ssrc2videoType[ssrc];
}
});
$(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
//console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
var ssrcV = ssrc.getAttribute('ssrc');
ssrc2jid[ssrcV] = jid;
notReceivedSSRCs.push(ssrcV);
var type = ssrc.getAttribute('type');
ssrc2videoType[ssrcV] = type;
// might need to update the direction if participant just went from sendrecv to recvonly
if (type === 'video' || type === 'screen') {
var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>video');
switch (ssrc.getAttribute('direction')) {
case 'sendrecv':
el.show();
break;
case 'recvonly':
el.hide();
// FIXME: Check if we have to change large video
//VideoLayout.updateLargeVideo(el);
break;
}
}
});
var displayName = !config.displayJids
? info.displayName : Strophe.getResourceFromJid(jid);
if (displayName && displayName.length > 0)
$(document).trigger('displaynamechanged',
[jid, displayName]);
/*if (focus !== null && info.displayName !== null) {
focus.setEndpointDisplayName(jid, info.displayName);
}*/
//check if the video bridge is available
if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
bridgeIsDown = true;
UI.messageHandler.showError("Error",
"Jitsi Videobridge is currently unavailable. Please try again later!");
}
var id = $(pres).find('>userID').text();
var email = $(pres).find('>email');
if(email.length > 0) {
id = email.text();
}
UI.setUserAvatar(jid, id);
});
$(document).bind('kicked.muc', function (event, jid) {
console.info(jid + " has been kicked from MUC!");
if (connection.emuc.myroomjid === jid) {
sessionTerminated = true;
disposeConference(false);
connection.emuc.doLeave();
UI.messageHandler.openMessageDialog("Session Terminated",
"Ouch! You have been kicked out of the meet!");
}
});
$(document).bind('passwordrequired.main', function (event) {
console.log('password is required');
UI.messageHandler.openTwoButtonDialog(null,
'<h2>Password required</h2>' +
'<input id="passwordrequired.username" type="text" placeholder="user@domain.net" autofocus>' +
'<input id="passwordrequired.password" type="password" placeholder="user password">',
true,
"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) {
connect(username.value, password.value);
}
}
},
function (event) {
document.getElementById('passwordrequired.username').focus();
}
);
});
/**
* 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}
*/
function isVideoSrcDesktop(jid) {
// FIXME: fix this mapping mess...
// figure out if large video is desktop stream or just a camera
if(!jid)
return false;
var isDesktop = false;
if (connection.emuc.myroomjid &&
Strophe.getResourceFromJid(connection.emuc.myroomjid) === jid) {
// local video
isDesktop = isUsingScreenStream;
} else {
// Do we have associations...
var videoSsrc = jid2Ssrc[jid];
if (videoSsrc) {
var videoType = ssrc2videoType[videoSsrc];
if (videoType) {
// Finally there...
isDesktop = videoType === 'screen';
} else {
console.error("No video type for ssrc: " + videoSsrc);
}
} else {
console.error("No ssrc for jid: " + jid);
}
}
return isDesktop;
}
/**
* 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) {
if (connection && connection.jingle.localVideo) {
var session = activecall;
if (session) {
session.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);
}
connection.emuc.addVideoInfoToPresence(mute);
connection.emuc.sendPresence();
},
options);
}
}
}
$(document).on('inlastnchanged', function (event, oldValue, newValue) {
if (config.muteLocalVideoIfNotInLastN) {
setVideoMute(!newValue, { 'byUser': false });
}
});
/**
* Mutes/unmutes the local video.
*/
function toggleVideo() {
buttonClick("#video", "icon-camera icon-camera-disabled");
if (connection && connection.jingle.localVideo) {
var session = activecall;
if (session) {
setVideoMute(!session.isVideoMute());
}
}
}
/**
* Mutes / unmutes audio for the local participant.
*/
function toggleAudio() {
setAudioMuted(!isAudioMuted());
}
/**
* Sets muted audio state for the local participant.
*/
function setAudioMuted(mute) {
if (!(connection && connection.jingle.localAudio)) {
preMuted = mute;
// We still click the button.
buttonClick("#mute", "icon-microphone icon-mic-disabled");
return;
}
if (forceMuted && !mute) {
console.info("Asking focus for unmute");
connection.moderate.setMute(connection.emuc.myroomjid, mute);
// FIXME: wait for result before resetting muted status
forceMuted = false;
}
if (mute == isAudioMuted()) {
// Nothing to do
return;
}
// It is not clear what is the right way to handle multiple tracks.
// So at least make sure that they are all muted or all unmuted and
// that we send presence just once.
var localAudioTracks = connection.jingle.localAudio.getAudioTracks();
if (localAudioTracks.length > 0) {
for (var idx = 0; idx < localAudioTracks.length; idx++) {
localAudioTracks[idx].enabled = !mute;
}
}
// isMuted is the opposite of audioEnabled
connection.emuc.addAudioInfoToPresence(mute);
connection.emuc.sendPresence();
UI.showLocalAudioIndicator(mute);
buttonClick("#mute", "icon-microphone icon-mic-disabled");
}
/**
* Checks whether the audio is muted or not.
* @returns {boolean} true if audio is muted and false if not.
*/
function isAudioMuted()
{
var localAudio = connection.jingle.localAudio;
for (var idx = 0; idx < localAudio.getAudioTracks().length; idx++) {
if(localAudio.getAudioTracks()[idx].enabled === true)
return false;
}
return true;
}
$(document).ready(function () {
if(API.isEnabled())
API.init();
APP.init();
UI.start();
statistics.start();
Moderator.init();
APP.translation.init();
if(APP.API.isEnabled())
APP.API.init();
APP.UI.start(init);
// Set default desktop sharing method
setDesktopSharing(config.desktopSharing);
// Initialize Chrome extension inline installs
if (config.chromeExtensionId) {
initInlineInstalls();
}
});
$(window).bind('beforeunload', function () {
if (connection && connection.connected) {
// ensure signout
$.ajax({
type: 'POST',
url: config.bosh,
async: false,
cache: false,
contentType: 'application/xml',
data: "<body rid='" + (connection.rid || connection._proto.rid)
+ "' xmlns='http://jabber.org/protocol/httpbind' sid='"
+ (connection.sid || connection._proto.sid)
+ "' type='terminate'><presence xmlns='jabber:client' type='unavailable'/></body>",
success: function (data) {
console.log('signed out');
console.log(data);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log('signout error', textStatus + ' (' + errorThrown + ')');
}
});
}
disposeConference(true);
if(API.isEnabled())
API.dispose();
if(APP.API.isEnabled())
APP.API.dispose();
});
function disposeConference(onUnload) {
UI.onDisposeConference(onUnload);
var handler = activecall;
if (handler && handler.peerconnection) {
// FIXME: probably removing streams is not required and close() should
// be enough
if (connection.jingle.localAudio) {
handler.peerconnection.removeStream(connection.jingle.localAudio, onUnload);
}
if (connection.jingle.localVideo) {
handler.peerconnection.removeStream(connection.jingle.localVideo, onUnload);
}
handler.peerconnection.close();
}
statistics.onDisposeConference(onUnload);
activecall = null;
}
module.exports = APP;
/**
* Changes the style class of the element given by id.
*/
function buttonClick(id, classname) {
$(id).toggleClass(classname); // add the class to the clicked element
}

View File

@@ -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>

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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
View 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;
}

View File

@@ -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
View 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');
}

View File

@@ -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"

View File

@@ -7,4 +7,5 @@ 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/

View File

@@ -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"

View File

@@ -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
View File

@@ -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
View File

@@ -0,0 +1,2 @@
version=3
https://github.com/jitsi/jitsi-meet/releases/ /jitsi/jitsi-meet/archive/(\S+)\.tar\.gz

View File

@@ -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"

View File

@@ -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;
}

View File

@@ -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
View 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.

View File

@@ -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]);
},
});

BIN
images/chrome.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

BIN
images/chromium.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
images/dropdownPointer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

BIN
images/firefox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/opera.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -10,41 +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="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="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/simulcast.bundle.js?v=1"></script>
<script src="libs/modules/connectionquality.bundle.js?v=1"></script>
<script src="libs/modules/UI.bundle.js?v=2"></script>
<script src="libs/modules/statistics.bundle.js?v=1"></script>
<script src="libs/modules/RTC.bundle.js?v=1"></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=24"></script><!-- application logic -->
<script src="libs/modules/API.bundle.js?v=1"></script>
<script src="util.js?v=7"></script><!-- utility functions -->
<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="moderator.js?v=2"></script><!-- media stream -->
<script src="tracking.js?v=1"></script><!-- tracking -->
<script src="keyboard_shortcut.js?v=3"></script>
<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"/>
@@ -53,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">
@@ -75,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>
@@ -154,36 +121,46 @@
<div style="position: relative;" id="header_container">
<div id="header">
<span id="toolbar">
<a class="button" id="toolbar_button_mute" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="mutePopover" content="Mute / Unmute">
<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" id="toolbar_button_camera" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleVideoPopover" content="Start / stop camera">
<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" id="toolbar_button_authentication" data-container="body" data-toggle="popover" data-placement="bottom" content="Authenticate">
<i id="authButton" class="icon-avatar"></i>
</a>
</span>
<span id="recording" style="display: none">
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_record" data-container="body" data-toggle="popover" data-placement="bottom" content="Record">
<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" id="toolbar_button_security" data-container="body" data-toggle="popover" data-placement="bottom" content="Lock / unlock room">
<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" id="toolbar_button_link" data-container="body" data-toggle="popover" data-placement="bottom" content="Invite others">
<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" id="toolbar_button_chat" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="bottom" content="Open / close chat">
<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>
@@ -191,38 +168,38 @@
</span>
<span id="prezi_button">
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_prezi" data-container="body" data-toggle="popover" data-placement="bottom" content="Share Prezi">
<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" id="toolbar_button_etherpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared document">
<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" id="toolbar_button_desktopsharing" data-container="body" data-toggle="popover" data-placement="bottom" content="Share screen">
<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" id="toolbar_button_fullScreen" data-container="body" data-toggle="popover" data-placement="bottom" content="Enter / Exit Full Screen">
<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" id="toolbar_button_sip" data-container="body" data-toggle="popover" data-placement="bottom" content="Call SIP number">
<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" id="toolbar_button_settings" data-container="body" data-toggle="popover" data-placement="bottom" content="Settings" >
<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" id="toolbar_button_hangup" data-container="body" data-toggle="popover" data-placement="bottom" content="Hang Up">
<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>
@@ -231,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>
@@ -246,7 +223,7 @@
<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>
@@ -261,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" id="bottom_toolbar_chat" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="top" content="Open / close chat">
<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>
@@ -280,7 +252,7 @@
</span>
<div class="bottom_button_separator"></div>
<span class="bottomToolbar_span">
<a class="bottomToolbarButton" id="bottom_toolbar_contact_list" data-container="body" data-toggle="popover" data-placement="top" id="contactlistpopover" content="Open / close contact list">
<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>
@@ -288,7 +260,7 @@
</span>
<div class="bottom_button_separator"></div>
<span class="bottomToolbar_span">
<a class="bottomToolbarButton" id="bottom_toolbar_film_strip" data-container="body" data-toggle="popover" shortcut="filmstripPopover" data-placement="top" content="Show / hide film strip">
<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>
@@ -296,16 +268,16 @@
</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">&nbsp;</i><span class='nick'></span>:&nbsp;<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" id="toggle_smileys">
<img src="images/smile.svg"/>
@@ -314,18 +286,18 @@
</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 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>

View File

@@ -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",

View File

@@ -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: UI.toggleChat
},
70: {
character: "F",
id: "filmstripPopover",
function: UI.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')) {
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(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
View File

@@ -0,0 +1,6 @@
{
"en": "Английски",
"bg": "Български",
"de": "Немски",
"tr": "Турски"
}

6
lang/languages-de.json Normal file
View File

@@ -0,0 +1,6 @@
{
"en": "Englisch",
"bg": "Bulgarisch",
"de": "Deutsch",
"tr": "Türkisch"
}

5
lang/languages-tr.json Normal file
View File

@@ -0,0 +1,5 @@
{
"en": "İngilizce",
"bg": "Bulgarca",
"de": "Almanca"
}

6
lang/languages.json Normal file
View File

@@ -0,0 +1,6 @@
{
"en": "English",
"bg": "Bulgarian",
"de": "German",
"tr": "Turkish"
}

204
lang/main-bg.json Normal file
View 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
View 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
View 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ışıı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
View 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"
}
}

26842
libs/app.bundle.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,207 +0,0 @@
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.API=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/**
* Implements API class that communicates with external api class
* and provides interface to access Jitsi Meet features by external
* applications that embed Jitsi Meet
*/
/**
* List of the available commands.
* @type {{
* displayName: inputDisplayNameHandler,
* muteAudio: toggleAudio,
* muteVideo: toggleVideo,
* filmStrip: toggleFilmStrip
* }}
*/
var commands =
{
displayName: UI.inputDisplayNameHandler,
muteAudio: toggleAudio,
muteVideo: toggleVideo,
toggleFilmStrip: UI.toggleFilmStrip,
toggleChat: UI.toggleChat,
toggleContactList: 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
};
/**
* 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;
}
}
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});
},
/**
* 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])(1)
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,122 +0,0 @@
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var n;"undefined"!=typeof window?n=window:"undefined"!=typeof global?n=global:"undefined"!=typeof self&&(n=self),n.connectionquality=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/**
* 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() {
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
}
};
}
var ConnectionQuality = {
/**
* Updates the local statistics
* @param data new statistics
*/
updateLocalStats: function (data) {
stats = data;
UI.updateLocalConnectionStats(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) {
UI.updateConnectionStats(jid, null, null);
return;
}
remoteStats[jid] = parseMUCStats(data);
UI.updateConnectionStats(jid, 100 - data.packetLoss_total, remoteStats[jid]);
},
/**
* Stops statistics sending.
*/
stopSendingStats: function () {
clearInterval(sendIntervalId);
sendIntervalId = null;
//notify UI about stopping statistics gathering
UI.onStatsStop();
},
/**
* Returns the local statistics.
*/
getStats: function () {
return stats;
}
};
module.exports = ConnectionQuality;
},{}]},{},[1])(1)
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,103 +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, roomPass)
{
var self = this;
var req = $iq(
{
type: 'set',
to: focusMucJid
}
);
req.c('dial',
{
xmlns: this.RAYO_XMLNS,
to: to,
from: from
});
req.c('header',
{
name: 'JvbRoomName',
value: roomName
}).up();
if (roomPass !== null && roomPass.length) {
req.c('header',
{
name: 'JvbRoomPassword',
value: roomPass
}).up();
}
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;
}
);
}
}
);

View File

@@ -1,344 +0,0 @@
/* jshint -W117 */
function CallIncomingJingle(sid) {
var sess = connection.jingle.sessions[sid];
// TODO: do we check activecall == null?
activecall = sess;
statistics.onConferenceCreated(sess);
RTC.onConferenceCreated(sess);
// TODO: check affiliation and/or role
console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
sess.usedrip = true; // not-so-naive trickle ice
sess.sendAnswer();
sess.accept();
};
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
CallIncomingJingle(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?
},
/**
* Populates the log data
*/
populateData: function () {
var data = {};
Object.keys(this.sessions).forEach(function (sid) {
var session = this.sessions[sid];
if (session.peerconnection && session.peerconnection.updateLog) {
// FIXME: should probably be a .dump call
data["jingle_" + session.sid] = {
updateLog: session.peerconnection.updateLog,
stats: session.peerconnection.stats,
url: window.location.href
};
}
});
return data;
}
});

View File

@@ -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";
}
};

View File

@@ -1,56 +0,0 @@
/* global $, $iq, config, connection, focusMucJid, forceMuted,
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);
});
},
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);
}
});

View File

@@ -1,241 +0,0 @@
/* global $, $iq, config, connection, Etherpad, hangUp, messageHandler,
roomName, sessionTerminated, Strophe, Util */
/**
* 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;
// Sip gateway can be enabled by configuring Jigasi host in config.js or
// it will be enabled automatically if focus detects the component through
// service discovery.
var sipGatewayEnabled = config.hosts.call_control !== undefined;
my.isModerator = function () {
return connection && connection.emuc.isModerator();
};
my.isPeerModerator = function (peerJid) {
return connection && connection.emuc.getMemberRole(peerJid) === 'moderator';
};
my.isExternalAuthEnabled = function () {
return externalAuthEnabled;
};
my.isSipGatewayEnabled = function () {
return sipGatewayEnabled;
};
my.init = function () {
Moderator.onLocalRoleChange = function (from, member, pres) {
UI.onModeratorStatusChanged(Moderator.isModerator());
};
};
my.onMucLeft = function (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();
}
// Tell the focus we have Jigasi configured
if (config.hosts.call_control !== undefined)
{
elem.c(
'property',
{ name: 'call_control', value: config.hosts.call_control})
.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);
// Check if focus has auto-detected Jigasi component(this will be also
// included if we have passed our host from the config)
if ($(resultIq).find(
'>conference>property[name=\'sipGatewayEnabled\']').length) {
sipGatewayEnabled = true;
}
console.info("Sip gateway enabled: " + sipGatewayEnabled);
};
// 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");
UI.onAuthenticationRequired();
return;
}
var waitMs = getNextErrorTimeout();
console.error("Focus error, retry after " + waitMs, error);
// Show message
UI.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 || {}));

View File

@@ -4,7 +4,7 @@
* applications that embed Jitsi Meet
*/
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
/**
* List of the available commands.
@@ -17,12 +17,12 @@
*/
var commands =
{
displayName: UI.inputDisplayNameHandler,
muteAudio: toggleAudio,
muteVideo: toggleVideo,
toggleFilmStrip: UI.toggleFilmStrip,
toggleChat: UI.toggleChat,
toggleContactList: UI.toggleContactList
displayName: APP.UI.inputDisplayNameHandler,
muteAudio: APP.UI.toggleAudio,
muteVideo: APP.UI.toggleVideo,
toggleFilmStrip: APP.UI.toggleFilmStrip,
toggleChat: APP.UI.toggleChat,
toggleContactList: APP.UI.toggleContactList
};
@@ -46,6 +46,8 @@ var events =
participantLeft: false
};
var displayName = {};
/**
* Processes commands from external applicaiton.
* @param message the object with the command
@@ -132,6 +134,30 @@ function processMessage(event)
}
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.
@@ -160,6 +186,7 @@ var API = {
window.attachEvent('onmessage', processMessage);
}
sendMessage({type: "system", loaded: true});
setupListeners();
},
/**
* Checks whether the event is enabled ot not.

View File

@@ -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 = UI.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;

View File

@@ -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;

View File

@@ -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 []; };
}
}

View File

@@ -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;

View File

@@ -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,28 +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);
};
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);
UI.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)
@@ -331,4 +362,4 @@ RTCUtils.prototype.handleLocalStream = function(stream)
module.exports = RTCUtils;
module.exports = RTCUtils;

View File

@@ -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": {
}
}

View File

@@ -5,21 +5,30 @@ 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 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 EventEmitter = require("events");
var SettingsMenu = require("./side_pannels/settings/SettingsMenu");
var Settings = require("./side_pannels/settings/Settings");
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 eventEmitter = new EventEmitter();
var roomName = null;
function setupPrezi()
@@ -39,51 +48,68 @@ function setupChat()
}
function setupToolbars() {
Toolbar.init();
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() {
RTC.addStreamListener(function (stream) {
switch (stream.type)
{
case "audio":
VideoLayout.changeLocalAudio(stream.getOriginalStream());
break;
case "video":
VideoLayout.changeLocalVideo(stream.getOriginalStream(), true);
break;
case "stream":
VideoLayout.changeLocalStream(stream.getOriginalStream());
break;
case "desktop":
VideoLayout.changeLocalVideo(stream, !isUsingScreenStream);
break;
}
}, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
APP.RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
RTC.addStreamListener(function (stream) {
APP.RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED);
APP.RTC.addStreamListener(function (stream) {
VideoLayout.onRemoteStreamAdded(stream);
}, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED);
// Listen for large video size updates
document.getElementById('largeVideo')
.addEventListener('loadedmetadata', function (e) {
currentVideoWidth = this.videoWidth;
currentVideoHeight = this.videoHeight;
VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight);
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);
});
statistics.addAudioLevelListener(function(jid, audioLevel)
APP.statistics.addAudioLevelListener(function(jid, audioLevel)
{
var resourceJid;
if(jid === statistics.LOCAL_JID)
if(jid === APP.statistics.LOCAL_JID)
{
resourceJid = AudioLevels.LOCAL_LEVEL;
if(isAudioMuted())
if(APP.RTC.localAudio.isMuted())
{
audioLevel = 0;
}
@@ -96,9 +122,130 @@ function registerListeners() {
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()
{
/**
@@ -108,10 +255,6 @@ function bindEvents()
function () {
VideoLayout.resizeLargeVideoContainer();
VideoLayout.positionLarge();
isFullScreen = document.fullScreen ||
document.mozFullScreen ||
document.webkitIsFullScreen;
}
);
@@ -121,7 +264,7 @@ function bindEvents()
});
}
UI.start = function () {
UI.start = function (init) {
document.title = interfaceConfig.APP_NAME;
if(config.enableWelcomePage && window.location.pathname == "/" &&
(!window.localStorage.welcomePageDisabled || window.localStorage.welcomePageDisabled == "false"))
@@ -159,13 +302,6 @@ UI.start = function () {
$("#welcome_page").hide();
$('body').popover({ selector: '[data-toggle=popover]',
trigger: 'click hover',
content: function() {
return this.getAttribute("content") +
KeyboardShortcut.getShortcut(this.getAttribute("shortcut"));
}
});
VideoLayout.resizeLargeVideoContainer();
$("#videospace").mousemove(function () {
return ToolbarToggler.showToolbar();
@@ -173,13 +309,16 @@ UI.start = function () {
// Set the defaults for prompt dialogs.
jQuery.prompt.setDefaults({persistent: false});
// KeyboardShortcut.init();
VideoLayout.init(eventEmitter);
AudioLevels.init();
NicknameHandler.init(eventEmitter);
registerListeners();
bindEvents();
setupPrezi();
setupToolbars();
setupChat();
document.title = interfaceConfig.APP_NAME;
$("#downloadlog").click(function (event) {
@@ -234,46 +373,28 @@ UI.start = function () {
"newestOnTop": false
};
$('#settingsmenu>input').keyup(function(event){
if(event.keyCode === 13) {//enter
SettingsMenu.update();
}
});
$("#updateSettings").click(function () {
SettingsMenu.update();
});
SettingsMenu.init();
};
UI.setUserAvatar = function (jid, id) {
Avatar.setUserAvatar(jid, id);
};
UI.toggleSmileys = function () {
Chat.toggleSmileys();
};
UI.chatAddError = function(errorMessage, originalText)
function chatAddError(errorMessage, originalText)
{
return Chat.chatAddError(errorMessage, originalText);
};
UI.chatSetSubject = function(text)
function chatSetSubject(text)
{
return Chat.chatSetSubject(text);
};
UI.updateChatConversation = function (from, displayName, message) {
function updateChatConversation(from, displayName, message) {
return Chat.updateChatConversation(from, displayName, message);
};
UI.onMucJoined = function (jid, info) {
function onMucJoined(jid, info) {
Toolbar.updateRoomUrl(window.location.href);
document.getElementById('localNick').appendChild(
document.createTextNode(Strophe.getResourceFromJid(jid) + ' (me)')
);
var meHTML = APP.translation.generateTranslatonHTML("me");
$("#localNick").html(Strophe.getResourceFromJid(jid) + " (" + meHTML + ")");
var settings = Settings.getSettings();
// Add myself to the contact list.
@@ -282,29 +403,24 @@ UI.onMucJoined = function (jid, info) {
// Once we've joined the muc show the toolbar
ToolbarToggler.showToolbar();
// Show authenticate button if needed
Toolbar.showAuthenticateButton(
Moderator.isExternalAuthEnabled() && !Moderator.isModerator());
var displayName = !config.displayJids
? info.displayName : Strophe.getResourceFromJid(jid);
if (displayName)
$(document).trigger('displaynamechanged',
['localVideoContainer', displayName + ' (me)']);
};
onDisplayNameChanged('localVideoContainer', displayName);
}
UI.initEtherpad = function (name) {
function initEtherpad(name) {
Etherpad.init(name);
};
UI.onMucLeft = function (jid) {
function onMucLeft(jid) {
console.log('left.muc', jid);
var displayName = $('#participant_' + Strophe.getResourceFromJid(jid) +
'>.displayname').html();
messageHandler.notify(displayName || 'Somebody',
messageHandler.notify(displayName,'notify.somebody',
'disconnected',
'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).
@@ -320,15 +436,123 @@ UI.onMucLeft = function (jid) {
}
}, 10);
// Unlock large video
if (focusedVideoInfo && focusedVideoInfo.jid === jid)
{
console.info("Focused video owner has left the conference");
focusedVideoInfo = null;
}
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();
};
@@ -345,171 +569,19 @@ UI.toggleContactList = function () {
return BottomToolbar.toggleContactList();
};
UI.onLocalRoleChange = function (jid, info, pres) {
console.info("My role changed, new role: " + info.role);
var isModerator = Moderator.isModerator();
VideoLayout.showModeratorIndicator();
Toolbar.showAuthenticateButton(
Moderator.isExternalAuthEnabled() && !isModerator);
if (isModerator) {
Toolbar.closeAuthenticationWindow();
messageHandler.notify(
'Me', 'connected', 'Moderator rights granted !');
}
};
UI.onDisposeConference = function (unload) {
Toolbar.showAuthenticateButton(false);
};
UI.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();
}
};
UI.onPasswordReqiured = function (callback) {
// password is required
Toolbar.lockLockButton();
messageHandler.openTwoButtonDialog(null,
'<h2>Password required</h2>' +
'<input id="lockKey" type="text" placeholder="password" autofocus>',
true,
"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);
}
}
}
);
};
UI.onAuthenticationRequired = function () {
// 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(
function () {
Moderator.allocateConferenceFocus(roomName, doJoinAfterFocus);
}, 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('@'));
authDialog = messageHandler.openDialog(
'Stop',
'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.',
true,
{
Authenticate: 'authNow'
},
function (onSubmitEvent, submitValue) {
// Do not close the dialog yet
onSubmitEvent.preventDefault();
// Open login popup
if (submitValue === 'authNow') {
Toolbar.authenticateClicked();
}
}
);
};
UI.setRecordingButtonState = function (state) {
Toolbar.setRecordingButtonState(state);
};
UI.changeDesktopSharingButtonState = function (isUsingScreenStream) {
Toolbar.changeDesktopSharingButtonState(isUsingScreenStream);
};
UI.inputDisplayNameHandler = function (value) {
VideoLayout.inputDisplayNameHandler(value);
};
UI.onMucEntered = function (jid, id, displayName) {
messageHandler.notify(displayName || 'Somebody',
'connected',
'connected');
// Add Peer's container
VideoLayout.ensurePeerContainerExists(jid,id);
};
UI.onMucPresenceStatus = function ( jid, info) {
VideoLayout.setPresenceStatus(
'participant_' + Strophe.getResourceFromJid(jid), info.status);
};
UI.onMucRoleChanged = function (role, displayName) {
VideoLayout.showModeratorIndicator();
if (role === 'moderator') {
var displayName = displayName;
if (!displayName) {
displayName = 'Somebody';
}
messageHandler.notify(
displayName,
'connected',
'Moderator rights granted to ' + displayName + '!');
}
};
UI.updateLocalConnectionStats = function(percent, stats)
{
VideoLayout.updateLocalConnectionStats(percent, stats);
};
UI.updateConnectionStats = function(jid, percent, stats)
{
VideoLayout.updateConnectionStats(jid, percent, stats);
};
UI.onStatsStop = function () {
VideoLayout.onStatsStop();
};
UI.getLargeVideoState = function()
{
return VideoLayout.getLargeVideoState();
};
UI.showLocalAudioIndicator = function (mute) {
VideoLayout.showLocalAudioIndicator(mute);
};
UI.changeLocalVideo = function (stream, flipx) {
VideoLayout.changeLocalVideo(stream, flipx);
};
UI.generateRoomName = function() {
if(roomName)
return roomName;
var roomnode = null;
var path = window.location.pathname;
@@ -539,6 +611,7 @@ UI.generateRoomName = function() {
}
roomName = roomnode + '@' + config.hosts.muc;
return roomName;
};
@@ -547,12 +620,63 @@ UI.connectionIndicatorShowMore = function(id)
return VideoLayout.connectionIndicators[id].showMore();
};
UI.showToolbar = function () {
return ToolbarToggler.showToolbar();
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.dockToolbar = function (isDock) {
return ToolbarToggler.dockToolbar(isDock);
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);
};
@@ -560,21 +684,78 @@ function dump(elem, filename) {
elem = elem.parentNode;
elem.download = filename || 'meetlog.json';
elem.href = 'data:application/json;charset=utf-8,\n';
var data = {};
if (connection.jingle) {
data = connection.jingle.populateData();
}
var data = APP.xmpp.populateData();
var metadata = {};
metadata.time = new Date();
metadata.url = window.location.href;
metadata.ua = navigator.userAgent;
if (connection.logger) {
metadata.xmpp = connection.logger.log;
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;

View File

@@ -1,5 +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.
*/
@@ -8,6 +23,10 @@ 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.
@@ -87,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 === largeVideoResourceJid) {
AudioLevels.updateActiveSpeakerAudioLevel(audioLevel);
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();
};
/**
@@ -221,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;

View 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;

View File

@@ -1,4 +1,5 @@
var Settings = require("../side_pannels/settings/Settings");
var Settings = require("../../settings/Settings");
var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
var users = {};
var activeSpeakerJid;
@@ -12,21 +13,21 @@ function setVisibility(selector, show) {
function isUserMuted(jid) {
// XXX(gp) we may want to rename this method to something like
// isUserStreaming, for example.
if (jid && jid != connection.emuc.myroomjid) {
if (jid && jid != APP.xmpp.myJid()) {
var resource = Strophe.getResourceFromJid(jid);
if (!require("../videolayout/VideoLayout").isInLastN(resource)) {
return true;
}
}
if (!RTC.remoteStreams[jid] || !RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
if (!APP.RTC.remoteStreams[jid] || !APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
return null;
}
return RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted;
return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted;
}
function getGravatarUrl(id, size) {
if(id === connection.emuc.myroomjid || !id) {
if(id === APP.xmpp.myJid() || !id) {
id = Settings.getSettings().uid;
}
return 'https://www.gravatar.com/avatar/' +
@@ -57,7 +58,7 @@ var Avatar = {
// 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');
}
@@ -100,7 +101,7 @@ var Avatar = {
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) {
@@ -130,7 +131,7 @@ var Avatar = {
*/
updateActiveSpeakerAvatarSrc: function (jid) {
if (!jid) {
jid = connection.emuc.findJidFromResource(
jid = APP.xmpp.findJidFromResource(
require("../videolayout/VideoLayout").getLargeVideoState().userResourceJid);
}
var avatar = $("#activeSpeakerAvatar")[0];

View File

@@ -1,4 +1,4 @@
/* global $, config, connection, dockToolbar, Moderator,
/* global $, config,
setLargeVideoVisible, Util */
var VideoLayout = require("../videolayout/VideoLayout");
@@ -30,8 +30,7 @@ function resize() {
* Shares the Etherpad name with other participants.
*/
function shareEtherpad() {
connection.emuc.addEtherpadToPresence(etherpadName);
connection.emuc.sendPresence();
APP.xmpp.addToPresence("etherpad", etherpadName);
}
/**

View File

@@ -2,6 +2,7 @@ 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;
@@ -30,40 +31,59 @@ var Prezi = {
* to load.
*/
openPreziDialog: function() {
var myprezi = connection.emuc.getPrezi(connection.emuc.myroomjid);
var myprezi = APP.xmpp.getPrezi();
if (myprezi) {
messageHandler.openTwoButtonDialog("Remove Prezi",
"Are you sure you would like to remove your Prezi?",
messageHandler.openTwoButtonDialog("dialog.removePreziTitle",
null,
"dialog.removePreziMsg",
null,
false,
"Remove",
"dialog.Remove",
function(e,v,m,f) {
if(v) {
connection.emuc.removePreziFromPresence();
connection.emuc.sendPresence();
APP.xmpp.removePreziFromPresence();
}
}
);
}
else if (preziPlayer != null) {
messageHandler.openTwoButtonDialog("Share a Prezi",
"Another participant is already sharing a Prezi." +
"This conference allows only one Prezi at a time.",
messageHandler.openTwoButtonDialog("dialog.sharePreziTitle",
null, "dialog.sharePreziMsg",
null,
false,
"Ok",
"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>Share a Prezi</h2>' +
html: '<h2>' + html + '</h2>' +
'<input id="preziUrl" type="text" ' +
'placeholder="e.g. ' +
'http://prezi.com/wz7vhjycl7e6/my-prezi" autofocus>',
'data-i18n="[placeholder]defaultPreziLink" data-i18n-options=\'' +
JSON.stringify({"url": "http://prezi.com/wz7vhjycl7e6/my-prezi"}) +
'\' placeholder="' + defaultUrl + '" autofocus>',
persistent: false,
buttons: { "Share": true , "Cancel": false},
buttons: buttons,
defaultButton: 1,
submit: function(e,v,m,f){
e.preventDefault();
@@ -74,7 +94,7 @@ var Prezi = {
if (preziUrl.value)
{
var urlValue
= encodeURI(Util.escapeHtml(preziUrl.value));
= encodeURI(UIUtil.escapeHtml(preziUrl.value));
if (urlValue.indexOf('http://prezi.com/') != 0
&& urlValue.indexOf('https://prezi.com/') != 0)
@@ -91,9 +111,7 @@ var Prezi = {
return false;
}
else {
connection.emuc
.addPreziToPresence(urlValue, 0);
connection.emuc.sendPresence();
APP.xmpp.addToPresence("prezi", urlValue);
$.prompt.close();
}
}
@@ -104,10 +122,10 @@ var Prezi = {
}
},
state1: {
html: '<h2>Share a Prezi</h2>' +
'Please provide a correct prezi link.',
html: '<h2>' + html + '</h2>' +
linkError,
persistent: false,
buttons: { "Back": true, "Cancel": false },
buttons: buttons1,
defaultButton: 1,
submit:function(e,v,m,f) {
e.preventDefault();
@@ -151,7 +169,7 @@ function presentationAdded(event, jid, presUrl, currentSlide) {
VideoLayout.resizeThumbnails();
var controlsEnabled = false;
if (jid === connection.emuc.myroomjid)
if (jid === APP.xmpp.myJid())
controlsEnabled = true;
setPresentationVisible(true);
@@ -191,15 +209,14 @@ function presentationAdded(event, jid, presUrl, currentSlide) {
preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) {
console.log("prezi status", event.value);
if (event.value == PreziPlayer.STATUS_CONTENT_READY) {
if (jid != connection.emuc.myroomjid)
if (jid != APP.xmpp.myJid())
preziPlayer.flyToStep(currentSlide);
}
});
preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) {
console.log("event value", event.value);
connection.emuc.addCurrentSlideToPresence(event.value);
connection.emuc.sendPresence();
APP.xmpp.addToPresence("preziSlide", event.value);
});
$("#" + elementId).css( 'background-image',

View File

@@ -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;

View File

@@ -1,9 +1,10 @@
var Chat = require("./chat/Chat");
var ContactList = require("./contactlist/ContactList");
var Settings = require("./settings/Settings");
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..
@@ -110,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({
@@ -140,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');

View File

@@ -1,8 +1,11 @@
/* global $, Util, connection, nickname:true, showToolbar */
/* 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;
@@ -27,10 +30,10 @@ function setVisualNotification(show) {
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;
var leftIndent = (UIUtil.getTextWidth(chatButtonElement) -
UIUtil.getTextWidth(unreadMsgElement)) / 2;
var topIndent = (UIUtil.getTextHeight(chatButtonElement) -
UIUtil.getTextHeight(unreadMsgElement)) / 2 - 3;
unreadMsgElement.setAttribute(
'style',
@@ -39,10 +42,10 @@ function setVisualNotification(show) {
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;
var bottomLeftIndent = (UIUtil.getTextWidth(chatBottomButtonElement) -
UIUtil.getTextWidth(unreadMsgBottomElement)) / 2;
var bottomTopIndent = (UIUtil.getTextHeight(chatBottomButtonElement) -
UIUtil.getTextHeight(unreadMsgBottomElement)) / 2 - 2;
unreadMsgBottomElement.setAttribute(
'style',
@@ -168,26 +171,20 @@ var Chat = (function (my) {
* Initializes chat related interface.
*/
my.init = function () {
var storedDisplayName = window.localStorage.displayname;
if (storedDisplayName) {
nickname = storedDisplayName;
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 = Util.escapeHtml(this.value);
var val = UIUtil.escapeHtml(this.value);
this.value = '';
if (!nickname) {
nickname = val;
window.localStorage.displayname = nickname;
connection.emuc.addDisplayNameToPresence(nickname);
connection.emuc.sendPresence();
Chat.setChatConversationMode(true);
if (!NicknameHandler.getNickname()) {
NicknameHandler.setNickname(val);
return;
}
@@ -207,8 +204,8 @@ var Chat = (function (my) {
}
else
{
var message = Util.escapeHtml(value);
connection.emuc.sendMessage(message, nickname);
var message = UIUtil.escapeHtml(value);
APP.xmpp.sendChatMessage(message, NicknameHandler.getNickname());
}
}
});
@@ -234,7 +231,7 @@ var Chat = (function (my) {
my.updateChatConversation = function (from, displayName, message) {
var divClassName = '';
if (connection.emuc.myroomjid === from) {
if (APP.xmpp.myJid() === from) {
divClassName = "localuser";
}
else {
@@ -242,7 +239,7 @@ var Chat = (function (my) {
if (!Chat.isVisible()) {
unreadMessages++;
Util.playSoundNotification('chatNotification');
UIUtil.playSoundNotification('chatNotification');
setVisualNotification(true);
}
}
@@ -252,7 +249,7 @@ var Chat = (function (my) {
// so we escape here only tags to avoid double &amp;
var escMessage = message.replace(/</g, '&lt;').
replace(/>/g, '&gt;').replace(/\n/g, '<br/>');
var escDisplayName = Util.escapeHtml(displayName);
var escDisplayName = UIUtil.escapeHtml(displayName);
message = Replacement.processReplacements(escMessage);
var messageContainer =
@@ -275,8 +272,8 @@ var Chat = (function (my) {
*/
my.chatAddError = function(errorMessage, originalText)
{
errorMessage = Util.escapeHtml(errorMessage);
originalText = Util.escapeHtml(originalText);
errorMessage = UIUtil.escapeHtml(errorMessage);
originalText = UIUtil.escapeHtml(originalText);
$('#chatconversation').append(
'<div class="errorMessage"><b>Error: </b>' + 'Your message' +
@@ -295,7 +292,7 @@ var Chat = (function (my) {
{
if(subject)
subject = subject.trim();
$('#subject').html(Replacement.linkify(Util.escapeHtml(subject)));
$('#subject').html(Replacement.linkify(UIUtil.escapeHtml(subject)));
if(subject === "")
{
$("#subject").css({display: "none"});

View File

@@ -1,3 +1,5 @@
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.
@@ -31,8 +33,8 @@ function getCommand(message)
*/
function processTopic(commandArguments)
{
var topic = Util.escapeHtml(commandArguments);
connection.emuc.setSubject(topic);
var topic = UIUtil.escapeHtml(commandArguments);
APP.xmpp.setSubject(topic);
}
/**

View File

@@ -38,31 +38,20 @@ function createAvatar(id) {
*
* @param displayName the display name to set
*/
function createDisplayNameParagraph(displayName) {
function createDisplayNameParagraph(key, displayName) {
var p = document.createElement('p');
p.innerText = displayName;
if(displayName)
p.innerText = displayName;
else if(key)
{
p.setAttribute("data-i18n",key);
p.innerText = APP.translation.translateString(key);
}
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);
});
function stopGlowing(glower) {
window.clearInterval(notificationInterval);
notificationInterval = false;
@@ -123,11 +112,11 @@ var ContactList = {
};
newContact.appendChild(createAvatar(id));
newContact.appendChild(createDisplayNameParagraph("Participant"));
newContact.appendChild(createDisplayNameParagraph("participant"));
var clElement = contactlist.get(0);
if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)
if (resourceJid === APP.xmpp.myResource()
&& $('#contactlist>ul .title')[0].nextSibling.nextSibling) {
clElement.insertBefore(newContact,
$('#contactlist>ul .title')[0].nextSibling.nextSibling);
@@ -182,6 +171,18 @@ var ContactList = {
} 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);
}
};

View File

@@ -1,25 +1,62 @@
var Avatar = require("../../avatar/Avatar");
var Settings = require("./Settings");
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 = Util.escapeHtml($('#setDisplayName').get(0).value);
var newEmail = Util.escapeHtml($('#setEmail').get(0).value);
var newDisplayName = UIUtil.escapeHtml($('#setDisplayName').get(0).value);
var newEmail = UIUtil.escapeHtml($('#setEmail').get(0).value);
if(newDisplayName) {
var displayName = Settings.setDisplayName(newDisplayName);
connection.emuc.addDisplayNameToPresence(displayName);
APP.xmpp.addToPresence("displayName", displayName, true);
}
var language = $("#languages_selectbox").val();
APP.translation.setLanguage(language);
Settings.setLanguage(language);
connection.emuc.addEmailToPresence(newEmail);
APP.xmpp.addToPresence("email", newEmail);
var email = Settings.setEmail(newEmail);
connection.emuc.sendPresence();
Avatar.setUserAvatar(connection.emuc.myroomjid, email);
Avatar.setUserAvatar(APP.xmpp.myJid(), email);
},
isVisible: function() {
@@ -29,14 +66,15 @@ var SettingsMenu = {
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);
}
}
};
$(document).bind('displaynamechanged', function(event, peerJid, newDisplayName) {
if(peerJid === 'localVideoContainer' ||
peerJid === connection.emuc.myroomjid) {
SettingsMenu.setDisplayName(newDisplayName);
}
});
module.exports = SettingsMenu;

View File

@@ -1,26 +1,30 @@
/* global $, buttonClick, config, lockRoom, Moderator, roomName,
setSharedKey, sharedKey, Util */
/* 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 authenticationWindow = null;
var UI = null;
var buttonHandlers =
{
"toolbar_button_mute": function () {
return toggleAudio();
return APP.UI.toggleAudio();
},
"toolbar_button_camera": function () {
return toggleVideo();
return APP.UI.toggleVideo();
},
"toolbar_button_authentication": function () {
/*"toolbar_button_authentication": function () {
return Toolbar.authenticateClicked();
},
},*/
"toolbar_button_record": function () {
return toggleRecording();
},
@@ -40,11 +44,11 @@ var buttonHandlers =
return Etherpad.toggleEtherpad(0);
},
"toolbar_button_desktopsharing": function () {
return toggleScreenSharing();
return APP.desktopsharing.toggleScreenSharing();
},
"toolbar_button_fullScreen": function()
{
buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");
UIUtil.buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");
return Toolbar.toggleFullScreen();
},
"toolbar_button_sip": function () {
@@ -55,13 +59,35 @@ var buttonHandlers =
},
"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() {
disposeConference();
sessionTerminated = true;
connection.emuc.doLeave();
APP.xmpp.disposeConference();
if(config.enableWelcomePage)
{
setTimeout(function()
@@ -72,11 +98,20 @@ function hangup() {
}
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(
"Session Terminated",
"You hung up the call",
title,
msg,
true,
{ "Join again": true },
buttons,
function(event, value, message, formVals)
{
window.location.reload();
@@ -90,7 +125,33 @@ function hangup() {
*/
function toggleRecording() {
Recording.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);
}
/**
@@ -101,7 +162,7 @@ function lockRoom(lock) {
if (lock)
currentSharedKey = sharedKey;
connection.emuc.lockRoom(currentSharedKey, function (res) {
APP.xmpp.lockRoom(currentSharedKey, function (res) {
// password is required
if (sharedKey)
{
@@ -115,14 +176,13 @@ function lockRoom(lock) {
}
}, function (err) {
console.warn('setting password failed', err);
messageHandler.showError('Lock failed',
'Failed to lock conference.',
err);
messageHandler.showError("dialog.lockTitle",
"dialog.lockMessage");
Toolbar.setSharedKey('');
}, function () {
console.warn('room passwords not supported');
messageHandler.showError('Warning',
'Room passwords are currently not supported.');
messageHandler.showError("dialog.warning",
"dialog.passwordNotSupported");
Toolbar.setSharedKey('');
});
};
@@ -137,25 +197,20 @@ function inviteParticipants() {
var sharedKeyText = "";
if (sharedKey && sharedKey.length > 0) {
sharedKeyText =
"This conference is password protected. Please use the " +
"following pin when joining:%0D%0A%0D%0A" +
sharedKey + "%0D%0A%0D%0A";
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 = "Invitation to a " + interfaceConfig.APP_NAME + " (" + conferenceName + ")";
var body = "Hey there, I%27d like to invite you to a " + interfaceConfig.APP_NAME +
" conference I%27ve just set up.%0D%0A%0D%0A" +
"Please click on the following link in order" +
" to join the conference.%0D%0A%0D%0A" +
roomUrl +
"%0D%0A%0D%0A" +
sharedKeyText +
"Note that " + interfaceConfig.APP_NAME + " is currently" +
" only supported by Chromium," +
" Google Chrome and Opera, so you need" +
" to be using one of these browsers.%0D%0A%0D%0A" +
"Talk to you in a sec!";
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;
@@ -173,19 +228,20 @@ function callSipButtonClicked()
var defaultNumber
= config.defaultSipNumber ? config.defaultSipNumber : '';
messageHandler.openTwoButtonDialog(null,
'<h2>Enter SIP number</h2>' +
var sipMsg = APP.translation.generateTranslatonHTML(
"dialog.sipMsg");
messageHandler.openTwoButtonDialog(null, null, null,
'<h2>' + sipMsg + '</h2>' +
'<input id="sipNumber" type="text"' +
' value="' + defaultNumber + '" autofocus>',
false,
"Dial",
"dialog.Dial",
function (e, v, m, f) {
if (v) {
var numberInput = document.getElementById('sipNumber');
if (numberInput.value) {
connection.rayo.dial(
numberInput.value, 'fromnumber',
roomName, sharedKey);
APP.xmpp.dial(numberInput.value, 'fromnumber',
UI.getRoomName(), sharedKey);
}
}
},
@@ -197,10 +253,37 @@ function callSipButtonClicked()
var Toolbar = (function (my) {
my.init = function () {
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
@@ -210,43 +293,31 @@ var Toolbar = (function (my) {
sharedKey = sKey;
};
my.closeAuthenticationWindow = function () {
if (authenticationWindow) {
authenticationWindow.close();
authenticationWindow = null;
}
}
my.authenticateClicked = function () {
// If auth window exists just bring it to the front
if (authenticationWindow) {
authenticationWindow.focus();
return;
}
Authentication.focusAuthenticationWindow();
// Get authentication URL
Moderator.getAuthUrl(function (url) {
// Open popup with authentication URL
authenticationWindow = messageHandler.openCenteredPopup(
url, 910, 660,
// On closed
function () {
// Close authentication dialog if opened
if (authDialog) {
messageHandler.closeDialog();
authDialog = null;
}
// On popup closed - retry room allocation
Moderator.allocateConferenceFocus(roomName, doJoinAfterFocus);
authenticationWindow = null;
});
if (!authenticationWindow) {
Toolbar.showAuthenticateButton(true);
messageHandler.openMessageDialog(
null, "Your browser is blocking popup windows from this site." +
" Please enable popups in your browser security settings" +
" and try again.");
}
});
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");
}
});
}
};
/**
@@ -262,7 +333,7 @@ var Toolbar = (function (my) {
inviteLink.select();
document.getElementById('jqi_state0_buttonInvite').disabled = false;
}
}
};
/**
* Disables and enables some of the buttons.
@@ -279,28 +350,20 @@ var Toolbar = (function (my) {
*/
my.openLockDialog = function () {
// Only the focus is able to set a shared key.
if (!Moderator.isModerator()) {
if (!APP.xmpp.isModerator()) {
if (sharedKey) {
messageHandler.openMessageDialog(null,
"This conversation is currently protected by" +
" a password. Only the owner of the conference" +
" could set a password.",
false,
"Password");
"dialog.passwordError");
} else {
messageHandler.openMessageDialog(null,
"This conversation isn't currently protected by" +
" a password. Only the owner of the conference" +
" could set a password.",
false,
"Password");
messageHandler.openMessageDialog(null, "dialog.passwordError2");
}
} else {
if (sharedKey) {
messageHandler.openTwoButtonDialog(null,
"Are you sure you would like to remove your password?",
messageHandler.openTwoButtonDialog(null, null,
"dialog.passwordCheck",
null,
false,
"Remove",
"dialog.Remove",
function (e, v) {
if (v) {
Toolbar.setSharedKey('');
@@ -308,18 +371,23 @@ var Toolbar = (function (my) {
}
});
} else {
messageHandler.openTwoButtonDialog(null,
'<h2>Set a password to lock your room</h2>' +
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"' +
'placeholder="your password" autofocus>',
' data-i18n="[placeholder]dialog.yourPassword" ' +
'placeholder="' + yourPassword + '" autofocus>',
false,
"Save",
"dialog.Save",
function (e, v) {
if (v) {
var lockKey = document.getElementById('lockKey');
if (lockKey.value) {
Toolbar.setSharedKey(Util.escapeHtml(lockKey.value));
Toolbar.setSharedKey(UIUtil.escapeHtml(lockKey.value));
lockRoom(true);
}
}
@@ -336,18 +404,20 @@ var Toolbar = (function (my) {
* Opens the invite link dialog.
*/
my.openLinkDialog = function () {
var inviteLink;
var inviteAttreibutes;
if (roomUrl === null) {
inviteLink = "Your conference is currently being created...";
inviteAttreibutes = 'data-i18n="[value]roomUrlDefaultMsg" value="' +
APP.translation.translateString("roomUrlDefaultMsg") + '"';
} else {
inviteLink = encodeURI(roomUrl);
inviteAttreibutes = "value=\"" + encodeURI(roomUrl) + "\"";
}
messageHandler.openTwoButtonDialog(
"Share this link with everyone you want to invite",
'<input id="inviteLinkRef" type="text" value="' +
inviteLink + '" onclick="this.select();" readonly>',
messageHandler.openTwoButtonDialog("dialog.shareLink",
null, null,
'<input id="inviteLinkRef" type="text" ' +
inviteAttreibutes + ' onclick="this.select();" readonly>',
false,
"Invite",
"dialog.Invite",
function (e, v) {
if (v) {
if (roomUrl) {
@@ -370,18 +440,28 @@ var Toolbar = (function (my) {
* Opens the settings dialog.
*/
my.openSettingsDialog = function () {
messageHandler.openTwoButtonDialog(
'<h2>Configure your conference</h2>' +
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">' +
'Participants join muted<br/>' +
settings2 + '<br/>' +
'<input type="checkbox" id="requireNicknames">' +
'Require nicknames<br/><br/>' +
'Set a password to lock your room:' +
'<input id="lockKey" type="text" placeholder="your password"' +
'autofocus>',
settings3 +
'<input id="lockKey" type="text" placeholder="' + yourPassword +
'" data-i18n="[placeholder]dialog.yourPassword" autofocus>',
null,
null,
false,
"Save",
"dialog.Save",
function () {
document.getElementById('lockKey').focus();
},
@@ -436,14 +516,14 @@ var Toolbar = (function (my) {
*/
my.unlockLockButton = function () {
if ($("#lockIcon").hasClass("icon-security-locked"))
buttonClick("#lockIcon", "icon-security 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"))
buttonClick("#lockIcon", "icon-security icon-security-locked");
UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
};
/**
@@ -486,13 +566,50 @@ var Toolbar = (function (my) {
// Shows or hides SIP calls button
my.showSipCallButton = function (show) {
if (Moderator.isSipGatewayEnabled() && show) {
$('#sipCallButton').css({display: "inline"});
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.

View File

@@ -1,8 +1,16 @@
/* global $, interfaceConfig, Moderator, showDesktopSharingButton */
/* 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.
*/
@@ -59,7 +67,7 @@ var ToolbarToggler = {
toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
}
if (Moderator.isModerator())
if (APP.xmpp.isModerator())
{
// TODO: Enable settings functionality.
// Need to uncomment the settings button in index.html.
@@ -97,7 +105,9 @@ var ToolbarToggler = {
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
}
}
}
},
showDesktopSharingButton: showDesktopSharingButton
};

View File

@@ -7,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
}
);
@@ -27,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,
@@ -54,7 +72,7 @@ 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,
my.openDialog = function (titleString, msgString, persistent, buttons,
submitFunction, loadedFunction) {
var args = {
title: titleString,
@@ -130,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
};
@@ -141,21 +159,39 @@ 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>');
};

View 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;

View File

@@ -11,6 +11,71 @@ module.exports = {
= 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
*/
getTextWidth: function (el) {
return (el.clientWidth + 1);
},
/**
* Returns the text height for the given element.
*
* @param el the element
*/
getTextHeight: function (el) {
return (el.clientHeight + 1);
},
/**
* Plays the sound given by id.
*
* @param id the identifier of the audio element.
*/
playSoundNotification: function (id) {
document.getElementById(id).play();
},
/**
* Escapes the given text.
*/
escapeHtml: function (unsafeText) {
return $('<div/>').text(unsafeText).html();
},
imageToGrayScale: function (canvas) {
var context = canvas.getContext('2d');
var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
var pixels = imgData.data;
for (var i = 0, n = pixels.length; i < n; i += 4) {
var grayscale
= pixels[i] * .3 + pixels[i+1] * .59 + pixels[i+2] * .11;
pixels[i ] = grayscale; // red
pixels[i+1] = grayscale; // green
pixels[i+2] = grayscale; // blue
// pixels[i+3] is alpha
}
// redraw the image in black & white
context.putImageData(imgData, 0, 0);
},
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");
}
};

View File

@@ -64,6 +64,8 @@ ConnectionIndicator.getStringFromArray = function (array) {
ConnectionIndicator.prototype.generateText = function () {
var downloadBitrate, uploadBitrate, packetLoss, resolution, i;
var translate = APP.translation.translateString;
if(this.bitrate === null)
{
downloadBitrate = "N/A";
@@ -103,7 +105,7 @@ ConnectionIndicator.prototype.generateText = function () {
}
else if(keys.length > 1)
{
var displayedSsrc = simulcast.getReceivingSSRC(this.jid);
var displayedSsrc = APP.simulcast.getReceivingSSRC(this.jid);
resolutionValue = this.resolution[displayedSsrc];
}
}
@@ -145,22 +147,28 @@ ConnectionIndicator.prototype.generateText = function () {
var result = "<table style='width:100%'>" +
"<tr>" +
"<td><span class='jitsipopover_blue'>Bitrate:</span></td>" +
"<td><span class='jitsipopover_blue' data-i18n='connectionindicator.bitrate'>" +
translate("connectionindicator.bitrate") + "</span></td>" +
"<td><span class='jitsipopover_green'>&darr;</span>" +
downloadBitrate + " <span class='jitsipopover_orange'>&uarr;</span>" +
uploadBitrate + "</td>" +
"</tr><tr>" +
"<td><span class='jitsipopover_blue'>Packet loss: </span></td>" +
"<td><span class='jitsipopover_blue' data-i18n='connectionindicator.packetloss'>" +
translate("connectionindicator.packetloss") + "</span></td>" +
"<td>" + packetLoss + "</td>" +
"</tr><tr>" +
"<td><span class='jitsipopover_blue'>Resolution:</span></td>" +
"<td><span class='jitsipopover_blue' data-i18n='connectionindicator.resolution'>" +
translate("connectionindicator.resolution") + "</span></td>" +
"<td>" + resolution + "</td></tr></table>";
if(this.videoContainer.id == "localVideoContainer")
if(this.videoContainer.id == "localVideoContainer") {
result += "<div class=\"jitsipopover_showmore\" " +
"onclick = \"UI.connectionIndicatorShowMore('" +
this.videoContainer.id + "')\">" +
(this.showMoreValue? "Show less" : "Show More") + "</div><br />";
"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)
{
@@ -183,7 +191,9 @@ ConnectionIndicator.prototype.generateText = function () {
if(!this.transport || this.transport.length === 0)
{
transport = "<tr>" +
"<td><span class='jitsipopover_blue'>Address:</span></td>" +
"<td><span class='jitsipopover_blue' " +
"data-i18n='connectionindicator.address'>" +
translate("connectionindicator.address") + "</span></td>" +
"<td> N/A</td></tr>";
}
else
@@ -218,40 +228,44 @@ ConnectionIndicator.prototype.generateText = function () {
}
}
var local_address_key = "connectionindicator.localaddress";
var remote_address_key = "connectionindicator.remoteaddress";
var localTransport =
"<tr><td><span class='jitsipopover_blue'>Local address" +
(data.localIP.length > 1? "es" : "") + ": </span></td><td> " +
"<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'>Remote address"+
(data.remoteIP.length > 1? "es" : "") + ":</span></td><td> " +
"<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>";
if(this.transport.length > 1)
{
transport += "<tr>" +
"<td>" +
"<span class='jitsipopover_blue'>Remote ports:</span>" +
"</td><td>";
localTransport += "<tr>" +
"<td>" +
"<span class='jitsipopover_blue'>Local ports:</span>" +
"</td><td>";
}
else
{
transport +=
"<tr>" +
"<td>" +
"<span class='jitsipopover_blue'>Remote port:</span>" +
"</td><td>";
localTransport +=
"<tr>" +
"<td>" +
"<span class='jitsipopover_blue'>Local port:</span>" +
"</td><td>";
}
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);
@@ -260,7 +274,8 @@ ConnectionIndicator.prototype.generateText = function () {
transport += "</td></tr>";
transport += localTransport + "</td></tr>";
transport +="<tr>" +
"<td><span class='jitsipopover_blue'>Transport:</span></td>" +
"<td><span class='jitsipopover_blue' data-i18n='connectionindicator.transport'>" +
translate("connectionindicator.transport") + "</span></td>" +
"<td>" + this.transport[0].type + "</td></tr>";
}
@@ -268,7 +283,8 @@ ConnectionIndicator.prototype.generateText = function () {
result += "<table style='width:100%'>" +
"<tr>" +
"<td>" +
"<span class='jitsipopover_blue'>Estimated bandwidth:</span>" +
"<span class='jitsipopover_blue' data-i18n='connectionindicator.bandwidth'>" +
translate("connectionindicator.bandwidth") + "</span>" +
"</td><td>" +
"<span class='jitsipopover_green'>&darr;</span>" +
downloadBandwidth +
@@ -313,8 +329,8 @@ ConnectionIndicator.prototype.create = function () {
this.videoContainer.appendChild(this.connectionIndicatorContainer);
this.popover = new JitsiPopover(
$("#" + this.videoContainer.id + " > .connectionindicator"),
{content: "<div class=\"connection_info\">Come back here for " +
"connection information once the conference starts</div>",
{content: "<div class=\"connection_info\" data-i18n='connectionindicator.na'>" +
APP.translation.translateString("connectionindicator.na") + "</div>",
skin: "black"});
this.emptyIcon = this.connectionIndicatorContainer.appendChild(
@@ -388,7 +404,8 @@ ConnectionIndicator.prototype.updateResolution = function (resolution) {
*/
ConnectionIndicator.prototype.updatePopoverData = function () {
this.popover.updateContent(
"<div class=\"connection_info\">" + this.generateText() + "</div>");
"<div class=\"connection_info\">" + this.generateText() + "</div>");
APP.translation.translateElement($(".connection_info"));
};
/**

File diff suppressed because it is too large Load Diff

View File

@@ -37,8 +37,6 @@ function setupWelcomePage()
$("#videoconference_page").hide();
$("#domain_name").text(
window.location.protocol + "//" + window.location.host + "/");
$("span[name='appName']").text(interfaceConfig.APP_NAME);
if (interfaceConfig.SHOW_JITSI_WATERMARK) {
var leftWatermarkDiv
= $("#welcome_page_header div[class='watermark leftwatermark']");

View File

@@ -1,3 +1,8 @@
var EventEmitter = require("events");
var eventEmitter = new EventEmitter();
var CQEvents = require("../../service/connectionquality/CQEvents");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
/**
* local stats
* @type {{}}
@@ -29,8 +34,7 @@ function startSendingStats() {
* Sends statistics to other participants
*/
function sendStats() {
connection.emuc.addConnectionInfoToPresence(convertToMUCStats(stats));
connection.emuc.sendPresence();
APP.xmpp.addToPresence("connectionQuality", convertToMUCStats(stats));
}
/**
@@ -69,13 +73,20 @@ function parseMUCStats(stats) {
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;
UI.updateLocalConnectionStats(100 - stats.packetLoss.total, stats);
eventEmitter.emit(CQEvents.LOCALSTATS_UPDATED, 100 - stats.packetLoss.total, stats);
if (sendIntervalId == null) {
startSendingStats();
}
@@ -88,13 +99,13 @@ var ConnectionQuality = {
*/
updateRemoteStats: function (jid, data) {
if (data == null || data.packetLoss_total == null) {
UI.updateConnectionStats(jid, null, null);
eventEmitter.emit(CQEvents.REMOTESTATS_UPDATED, jid, null, null);
return;
}
remoteStats[jid] = parseMUCStats(data);
UI.updateConnectionStats(jid, 100 - data.packetLoss_total, remoteStats[jid]);
eventEmitter.emit(CQEvents.REMOTESTATS_UPDATED,
jid, 100 - data.packetLoss_total, remoteStats[jid]);
},
/**
@@ -104,7 +115,7 @@ var ConnectionQuality = {
clearInterval(sendIntervalId);
sendIntervalId = null;
//notify UI about stopping statistics gathering
UI.onStatsStop();
eventEmitter.emit(CQEvents.STOP);
},
/**
@@ -112,6 +123,10 @@ var ConnectionQuality = {
*/
getStats: function () {
return stats;
},
addListener: function (type, listener) {
eventEmitter.on(type, listener);
}
};

View File

@@ -1,4 +1,4 @@
/* global $, alert, changeLocalVideo, chrome, config, connection, getConferenceHandler, getUserMediaWithConstraints */
/* 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);
UI.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);
UI.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,73 +221,103 @@ function getSwitchStreamFailed(error) {
}
function streamSwitchDone() {
//window.setTimeout(
// function () {
switchInProgress = false;
UI.changeDesktopSharingButtonState(isUsingScreenStream);
// }, 100
//);
eventEmitter.emit(
DesktopSharingEventTypes.SWITCHING_DONE,
isUsingScreenStream);
}
function newStreamCreated(stream) {
var oldStream = connection.jingle.localVideo;
connection.jingle.localVideo = stream;
UI.changeLocalVideo(stream, !isUsingScreenStream);
if (activecall) {
// FIXME: will block switchInProgress on true value in case of exception
activecall.switchStreams(stream, oldStream, streamSwitchDone);
} else {
// We are done immediately
console.error("No conference handler");
UI.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'
);
}
}
};

View 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;

View File

@@ -1,6 +1,7 @@
var email = '';
var displayName = '';
var userId;
var language = null;
function supportsLocalStorage() {
@@ -15,19 +16,20 @@ function supportsLocalStorage() {
function generateUniqueId() {
function _p8() {
return (Math.random().toString(16)+"000000000").substr(2,8);
return (Math.random().toString(16) + "000000000").substr(2, 8);
}
return _p8() + _p8() + _p8() + _p8();
}
if(supportsLocalStorage()) {
if(!window.localStorage.jitsiMeetId) {
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();
@@ -40,7 +42,7 @@ var Settings =
window.localStorage.displayname = displayName;
return displayName;
},
setEmail: function(newEmail)
setEmail: function (newEmail)
{
email = newEmail;
window.localStorage.email = newEmail;
@@ -50,8 +52,13 @@ var Settings =
return {
email: email,
displayName: displayName,
uid: userId
uid: userId,
language: language
};
},
setLanguage: function (lang) {
language = lang;
window.localStorage.language = lang;
}
};

View File

@@ -1,5 +1,6 @@
var SimulcastLogger = require("./SimulcastLogger");
var SimulcastUtils = require("./SimulcastUtils");
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
function SimulcastReceiver() {
this.simulcastUtils = new SimulcastUtils();
@@ -159,43 +160,19 @@ SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
// If we haven't receiving a "changed" event yet, then we must be receiving
// low quality (that the sender always streams).
if (!ssrc && connection.jingle) {
var session;
var i, j, k;
var keys = Object.keys(connection.jingle.sessions);
for (i = 0; i < keys.length; i++) {
var sid = keys[i];
if (ssrc) {
// stream found, stop.
break;
}
session = connection.jingle.sessions[sid];
if (session.remoteStreams) {
for (j = 0; j < session.remoteStreams.length; j++) {
var remoteStream = session.remoteStreams[j];
if (ssrc) {
// stream found, stop.
break;
}
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 _ssrc = this._remoteMaps.msid2ssrc[msid];
var _jid = ssrc2jid[_ssrc];
var quality = this._remoteMaps.msid2Quality[msid];
if (jid == _jid && quality == 0) {
ssrc = _ssrc;
// stream found, stop.
break;
}
}
}
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;
}
}
}
@@ -206,47 +183,36 @@ SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
{
var session, electedStream;
var sid, electedStream;
var i, j, k;
if (connection.jingle) {
var keys = Object.keys(connection.jingle.sessions);
for (i = 0; i < keys.length; i++) {
var sid = keys[i];
if (electedStream) {
// stream found, stop.
break;
}
session = connection.jingle.sessions[sid];
if (session.remoteStreams) {
for (j = 0; j < session.remoteStreams.length; j++) {
var remoteStream = session.remoteStreams[j];
if (electedStream) {
// stream found, stop.
break;
}
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]);
// stream found, stop.
break;
}
}
}
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 {
session: session,
sid: sid,
stream: electedStream
};
};

View File

@@ -32,7 +32,7 @@ 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
: connection.jingle.localVideo;
: APP.RTC.localVideo.getOriginalStream();
};
function NativeSimulcastSender() {
@@ -47,7 +47,7 @@ NativeSimulcastSender.prototype._localVideoSourceCache = '';
NativeSimulcastSender.prototype.reset = function () {
this._localExplosionMap = {};
this._isUsingScreenStream = isUsingScreenStream;
this._isUsingScreenStream = APP.desktopsharing.isUsingScreenStream();
};
NativeSimulcastSender.prototype._cacheLocalVideoSources = function (lines) {
@@ -78,10 +78,12 @@ NativeSimulcastSender.prototype._appendSimulcastGroup = function (lines) {
simSSRC = this._generateRandomSSRC();
ssrcGroup.push(simSSRC);
sb.splice.apply(sb, [sb.length, 0].concat(
[["a=ssrc:", simSSRC, " cname:", videoSources.base.cname].join(''),
["a=ssrc:", simSSRC, " msid:", videoSources.base.msid].join('')]
));
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(''));

View File

@@ -6,6 +6,7 @@ var NoSimulcastSender = SimulcastSender["no"];
var NativeSimulcastSender = SimulcastSender["native"];
var SimulcastReceiver = require("./SimulcastReceiver");
var SimulcastUtils = require("./SimulcastUtils");
var RTCEvents = require("../../service/RTC/RTCEvents");
/**
@@ -46,6 +47,22 @@ function SimulcastManager() {
}
}
APP.RTC.addListener(RTCEvents.SIMULCAST_LAYER_CHANGED,
function (endpointSimulcastLayers) {
endpointSimulcastLayers.forEach(function (esl) {
var ssrc = esl.simulcastLayer.primarySSRC;
simulcast._setReceivingVideoStream(esl.endpoint, ssrc);
});
});
APP.RTC.addListener(RTCEvents.SIMULCAST_START, function (simulcastLayer) {
var ssrc = simulcastLayer.primarySSRC;
simulcast._setLocalVideoStreamEnabled(ssrc, true);
});
APP.RTC.addListener(RTCEvents.SIMULCAST_STOP, function (simulcastLayer) {
var ssrc = simulcastLayer.primarySSRC;
simulcast._setLocalVideoStreamEnabled(ssrc, false);
});
}
/**
@@ -180,24 +197,6 @@ SimulcastManager.prototype.resetSender = function() {
}
};
$(document).bind('simulcastlayerschanged', function (event, endpointSimulcastLayers) {
endpointSimulcastLayers.forEach(function (esl) {
var ssrc = esl.simulcastLayer.primarySSRC;
simulcast._setReceivingVideoStream(esl.endpoint, ssrc);
});
});
$(document).bind('startsimulcastlayer', function (event, simulcastLayer) {
var ssrc = simulcastLayer.primarySSRC;
simulcast._setLocalVideoStreamEnabled(ssrc, true);
});
$(document).bind('stopsimulcastlayer', function (event, simulcastLayer) {
var ssrc = simulcastLayer.primarySSRC;
simulcast._setLocalVideoStreamEnabled(ssrc, false);
});
var simulcast = new SimulcastManager();
module.exports = simulcast;

View File

@@ -84,7 +84,7 @@ function LocalStatsCollector(stream, interval, statisticsService, eventEmitter)
* Starts the collecting the statistics.
*/
LocalStatsCollector.prototype.start = function () {
if (!window.AudioContext)
if (config.disableAudioLevels || !window.AudioContext)
return;
var context = new AudioContext();

View File

@@ -1,5 +1,8 @@
/* global focusMucJid, ssrc2jid */
/* global ssrc2jid */
/* jshint -W117 */
var RTCBrowserType = require("../../service/RTC/RTCBrowserType");
/**
* Calculates packet lost percent using the number of lost packets and the
* number of all packet.
@@ -14,10 +17,10 @@ function calculatePacketLoss(lostPackets, totalPackets) {
}
function getStatValue(item, name) {
if(!keyMap[RTC.getBrowserType()][name])
if(!keyMap[APP.RTC.getBrowserType()][name])
throw "The property isn't supported!";
var key = keyMap[RTC.getBrowserType()][name];
return RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_CHROME? item.stat(key) : item[key];
var key = keyMap[APP.RTC.getBrowserType()][name];
return APP.RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_CHROME? item.stat(key) : item[key];
}
/**
@@ -108,9 +111,13 @@ PeerStats.prototype.setSsrcBitrate = function (ssrc, bitrate)
PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel)
{
// Range limit 0 - 1
this.ssrc2AudioLevel[ssrc] = Math.min(Math.max(audioLevel, 0), 1);
this.ssrc2AudioLevel[ssrc] = formatAudioLevel(audioLevel);
};
function formatAudioLevel(audioLevel) {
return Math.min(Math.max(audioLevel, 0), 1);
}
/**
* Array with the transport information.
* @type {Array}
@@ -144,7 +151,7 @@ function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, even
/**
* Gather PeerConnection stats once every this many milliseconds.
*/
this.GATHER_INTERVAL = 10000;
this.GATHER_INTERVAL = 15000;
/**
* Log stats via the focus once every this many milliseconds.
@@ -185,16 +192,26 @@ module.exports = StatsCollector;
/**
* Stops stats updates.
*/
StatsCollector.prototype.stop = function ()
{
if (this.audioLevelsIntervalId)
{
StatsCollector.prototype.stop = function () {
if (this.audioLevelsIntervalId) {
clearInterval(this.audioLevelsIntervalId);
this.audioLevelsIntervalId = null;
}
if (this.statsIntervalId)
{
clearInterval(this.statsIntervalId);
this.statsIntervalId = null;
}
if(this.logStatsIntervalId)
{
clearInterval(this.logStatsIntervalId);
this.logStatsIntervalId = null;
}
if(this.gatherStatsIntervalId)
{
clearInterval(this.gatherStatsIntervalId);
this.gatherStatsIntervalId = null;
}
@@ -216,69 +233,68 @@ StatsCollector.prototype.errorCallback = function (error)
StatsCollector.prototype.start = function ()
{
var self = this;
this.audioLevelsIntervalId = setInterval(
function ()
{
// Interval updates
self.peerconnection.getStats(
function (report)
{
var results = null;
if(!report || !report.result || typeof report.result != 'function')
{
results = report;
}
else
{
results = report.result();
}
//console.error("Got interval report", results);
self.currentAudioLevelsReport = results;
self.processAudioLevelReport();
self.baselineAudioLevelsReport =
self.currentAudioLevelsReport;
},
self.errorCallback
);
},
self.audioLevelsIntervalMilis
);
if(!config.disableAudioLevels) {
console.debug("set audio levels interval");
this.audioLevelsIntervalId = setInterval(
function () {
// Interval updates
self.peerconnection.getStats(
function (report) {
var results = null;
if (!report || !report.result ||
typeof report.result != 'function') {
results = report;
}
else {
results = report.result();
}
//console.error("Got interval report", results);
self.currentAudioLevelsReport = results;
self.processAudioLevelReport();
self.baselineAudioLevelsReport =
self.currentAudioLevelsReport;
},
self.errorCallback
);
},
self.audioLevelsIntervalMilis
);
}
this.statsIntervalId = setInterval(
function () {
// Interval updates
self.peerconnection.getStats(
function (report)
{
var results = null;
if(!report || !report.result || typeof report.result != 'function')
{
//firefox
results = report;
}
else
{
//chrome
results = report.result();
}
//console.error("Got interval report", results);
self.currentStatsReport = results;
try
{
self.processStatsReport();
}
catch (e)
{
console.error("Unsupported key:" + e, e);
}
if(!config.disableStats) {
console.debug("set stats interval");
this.statsIntervalId = setInterval(
function () {
// Interval updates
self.peerconnection.getStats(
function (report) {
var results = null;
if (!report || !report.result ||
typeof report.result != 'function') {
//firefox
results = report;
}
else {
//chrome
results = report.result();
}
//console.error("Got interval report", results);
self.currentStatsReport = results;
try {
self.processStatsReport();
}
catch (e) {
console.error("Unsupported key:" + e, e);
}
self.baselineStatsReport = self.currentStatsReport;
},
self.errorCallback
);
},
self.statsIntervalMilis
);
self.baselineStatsReport = self.currentStatsReport;
},
self.errorCallback
);
},
self.statsIntervalMilis
);
}
if (config.logStats) {
this.gatherStatsIntervalId = setInterval(
@@ -300,6 +316,38 @@ StatsCollector.prototype.start = function ()
}
};
/**
* Checks whether a certain record should be included in the logged statistics.
*/
function acceptStat(reportId, reportType, statName) {
if (reportType == "googCandidatePair" && statName == "googChannelId")
return false;
if (reportType == "ssrc") {
if (statName == "googTrackId" ||
statName == "transportId" ||
statName == "ssrc")
return false;
}
return true;
}
/**
* Checks whether a certain record should be included in the logged statistics.
*/
function acceptReport(id, type) {
if (id.substring(0, 15) == "googCertificate" ||
id.substring(0, 9) == "googTrack" ||
id.substring(0, 20) == "googLibjingleSession")
return false;
if (type == "googComponent")
return false;
return true;
}
/**
* Converts the stats to the format used for logging, and saves the data in
* this.statsToBeLogged.
@@ -310,12 +358,16 @@ StatsCollector.prototype.addStatsToBeLogged = function (reports) {
var num_records = this.statsToBeLogged.timestamps.length;
this.statsToBeLogged.timestamps.push(new Date().getTime());
reports.map(function (report) {
if (!acceptReport(report.id, report.type))
return;
var stat = self.statsToBeLogged.stats[report.id];
if (!stat) {
stat = self.statsToBeLogged.stats[report.id] = {};
}
stat.type = report.type;
report.names().map(function (name) {
if (!acceptStat(report.id, report.type, name))
return;
var values = stat[name];
if (!values) {
values = stat[name] = [];
@@ -329,30 +381,9 @@ StatsCollector.prototype.addStatsToBeLogged = function (reports) {
};
StatsCollector.prototype.logStats = function () {
if (!focusMucJid) {
if(!APP.xmpp.sendLogs(this.statsToBeLogged))
return;
}
var deflate = true;
var content = JSON.stringify(this.statsToBeLogged);
if (deflate) {
content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
}
content = Base64.encode(content);
// XEP-0337-ish
var message = $msg({to: focusMucJid, type: 'normal'});
message.c('log', { xmlns: 'urn:xmpp:eventlog',
id: 'PeerConnectionStats'});
message.c('message').t(content).up();
if (deflate) {
message.c('tag', {name: "deflated", value: "true"}).up();
}
message.up();
connection.send(message);
// Reset the stats
this.statsToBeLogged.stats = {};
this.statsToBeLogged.timestamps = [];
@@ -465,7 +496,7 @@ StatsCollector.prototype.processStatsReport = function () {
var ssrc = getStatValue(now, 'ssrc');
if(!ssrc)
continue;
var jid = ssrc2jid[ssrc];
var jid = APP.xmpp.getJidFromSSRC(ssrc);
if (!jid && (Date.now() - now.timestamp) < 3000) {
console.warn("No jid for ssrc: " + ssrc);
continue;
@@ -666,7 +697,7 @@ StatsCollector.prototype.processAudioLevelReport = function ()
}
var ssrc = getStatValue(now, 'ssrc');
var jid = ssrc2jid[ssrc];
var jid = APP.xmpp.getJidFromSSRC(ssrc);
if (!jid && (Date.now() - now.timestamp) < 3000)
{
console.warn("No jid for ssrc: " + ssrc);
@@ -700,11 +731,11 @@ StatsCollector.prototype.processAudioLevelReport = function ()
// but it seems to vary between 0 and around 32k.
audioLevel = audioLevel / 32767;
jidStats.setSsrcAudioLevel(ssrc, audioLevel);
if(jid != connection.emuc.myroomjid)
if(jid != APP.xmpp.myJid())
this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
}
}
};
};

View File

@@ -1,8 +0,0 @@
{
"name": "statistics",
"version": "0.0.1",
"main": "statistics.js",
"description": "Statistics module for Jitsi Meet",
"dependencies": {
}
}

View File

@@ -4,10 +4,8 @@
var LocalStats = require("./LocalStatsCollector.js");
var RTPStats = require("./RTPStatsCollector.js");
var EventEmitter = require("events");
//These lines should be uncommented when require works in app.js
//var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
//var RTCBrowserType = require("../../service/RTC/RTCBrowserType");
//var XMPPEvents = require("../service/xmpp/XMPPEvents");
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var eventEmitter = new EventEmitter();
@@ -35,18 +33,14 @@ function stopRemote()
}
function startRemoteStats (peerconnection) {
if (config.enableRtpStats)
if(rtpStats)
{
if(rtpStats)
{
rtpStats.stop();
rtpStats = null;
}
rtpStats = new RTPStats(peerconnection, 200, 2000, eventEmitter);
rtpStats.start();
rtpStats.stop();
rtpStats = null;
}
rtpStats = new RTPStats(peerconnection, 200, 2000, eventEmitter);
rtpStats.start();
}
function onStreamCreated(stream)
@@ -54,11 +48,19 @@ function onStreamCreated(stream)
if(stream.getOriginalStream().getAudioTracks().length === 0)
return;
localStats = new LocalStats(stream.getOriginalStream(), 100, statistics,
localStats = new LocalStats(stream.getOriginalStream(), 200, statistics,
eventEmitter);
localStats.start();
}
function onDisposeConference(onUnload) {
stopRemote();
if(onUnload) {
stopLocal();
eventEmitter.removeAllListeners();
}
}
var statistics =
{
@@ -113,23 +115,13 @@ var statistics =
stopRemote();
},
onConferenceCreated: function (event) {
startRemoteStats(event.peerconnection);
},
onDisposeConference: function (onUnload) {
stopRemote();
if(onUnload) {
stopLocal();
eventEmitter.removeAllListeners();
}
},
start: function () {
this.addConnectionStatsListener(connectionquality.updateLocalStats);
this.addRemoteStatsStopListener(connectionquality.stopSendingStats);
RTC.addStreamListener(onStreamCreated,
APP.RTC.addStreamListener(onStreamCreated,
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) {
startRemoteStats(event.peerconnection);
});
}
};

View File

@@ -0,0 +1,132 @@
var i18n = require("i18next-client");
var languages = require("../../service/translation/languages");
var Settings = require("../settings/Settings");
var DEFAULT_LANG = languages.EN;
i18n.addPostProcessor("resolveAppName", function(value, key, options) {
return value.replace("__app__", interfaceConfig.APP_NAME);
});
var defaultOptions = {
detectLngQS: "lang",
useCookie: false,
fallbackLng: DEFAULT_LANG,
load: "unspecific",
resGetPath: 'lang/__ns__-__lng__.json',
ns: {
namespaces: ['main', 'languages'],
defaultNs: 'main'
},
lngWhitelist : languages.getLanguages(),
fallbackOnNull: true,
fallbackOnEmpty: true,
useDataAttrOptions: true,
defaultValueFromContent: false,
app: interfaceConfig.APP_NAME,
getAsync: false,
defaultValueFromContent: false,
customLoad: function(lng, ns, options, done) {
var resPath = "lang/__ns__-__lng__.json";
if(lng === languages.EN)
resPath = "lang/__ns__.json";
var url = i18n.functions.applyReplacement(resPath, { lng: lng, ns: ns });
i18n.functions.ajax({
url: url,
success: function(data, status, xhr) {
i18n.functions.log('loaded: ' + url);
done(null, data);
},
error : function(xhr, status, error) {
if ((status && status == 200) ||
(xhr && xhr.status && xhr.status == 200)) {
// file loaded but invalid json, stop waste time !
i18n.functions.error('There is a typo in: ' + url);
} else if ((status && status == 404) ||
(xhr && xhr.status && xhr.status == 404)) {
i18n.functions.log('Does not exist: ' + url);
} else {
var theStatus = status ? status :
((xhr && xhr.status) ? xhr.status : null);
i18n.functions.log(theStatus + ' when loading ' + url);
}
done(error, {});
},
dataType: "json",
async : options.getAsync
});
}
// options for caching
// useLocalStorage: true,
// localStorageExpirationTime: 86400000 // in ms, default 1 week
};
function initCompleted(t)
{
$("[data-i18n]").i18n();
}
function checkForParameter() {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == "lang")
{
return pair[1];
}
}
return null;
}
module.exports = {
init: function (lang) {
var options = defaultOptions;
if(!lang)
{
lang = checkForParameter();
if(!lang)
{
var settings = Settings.getSettings();
if(settings)
lang = settings.language;
}
}
if(lang) {
options.lng = lang;
}
i18n.init(options, initCompleted);
},
translateString: function (key, options) {
return i18n.t(key, options);
},
setLanguage: function (lang) {
if(!lang)
lang = DEFAULT_LANG;
i18n.setLng(lang, defaultOptions, initCompleted);
},
getCurrentLanguage: function () {
return i18n.lng();
},
translateElement: function (selector) {
selector.i18n();
},
generateTranslatonHTML: function (key, options) {
var str = "<span data-i18n=\"" + key + "\"";
if(options)
{
str += " data-i18n-options=\"" + JSON.stringify(options) + "\"";
}
str += ">";
str += this.translateString(key, options);
str += "</span>";
return str;
}
};

View File

@@ -1,6 +1,12 @@
/* jshint -W117 */
var TraceablePeerConnection = require("./TraceablePeerConnection");
var SDPDiffer = require("./SDPDiffer");
var SDPUtil = require("./SDPUtil");
var SDP = require("./SDP");
var RTCBrowserType = require("../../service/RTC/RTCBrowserType");
// Jingle stuff
function JingleSession(me, sid, connection) {
function JingleSession(me, sid, connection, service) {
this.me = me;
this.sid = sid;
this.connection = connection;
@@ -11,15 +17,14 @@ function JingleSession(me, sid, connection) {
this.state = null;
this.localSDP = null;
this.remoteSDP = null;
this.localStreams = [];
this.relayedStreams = [];
this.remoteStreams = [];
this.startTime = null;
this.stopTime = null;
this.media_constraints = null;
this.pc_constraints = null;
this.ice_config = {};
this.drip_container = [];
this.service = service;
this.usetrickle = true;
this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
@@ -49,6 +54,9 @@ function JingleSession(me, sid, connection) {
this.videoMuteByUser = false;
}
//TODO: this array must be removed when firefox implement multistream support
JingleSession.notReceivedSSRCs = [];
JingleSession.prototype.initiate = function (peerjid, isInitiator) {
var self = this;
if (this.state !== null) {
@@ -74,16 +82,11 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
self.sendIceCandidate(event.candidate);
};
this.peerconnection.onaddstream = function (event) {
self.remoteStreams.push(event.stream);
console.log("REMOTE STREAM ADDED: " + event.stream + " - " + event.stream.id);
$(document).trigger('remotestreamadded.jingle', [event, self.sid]);
self.remoteStreamAdded(event);
};
this.peerconnection.onremovestream = function (event) {
// Remove the stream from remoteStreams
var streamIdx = self.remoteStreams.indexOf(event.stream);
if(streamIdx !== -1){
self.remoteStreams.splice(streamIdx, 1);
}
// FIXME: remotestreamremoved.jingle not defined anywhere(unused)
$(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
};
@@ -100,17 +103,60 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
this.stopTime = new Date();
break;
}
$(document).trigger('iceconnectionstatechange.jingle', [self.sid, self]);
onIceConnectionStateChange(self.sid, self);
};
// add any local and relayed stream
this.localStreams.forEach(function(stream) {
self.peerconnection.addStream(stream);
APP.RTC.localStreams.forEach(function(stream) {
self.peerconnection.addStream(stream.getOriginalStream());
});
this.relayedStreams.forEach(function(stream) {
self.peerconnection.addStream(stream);
});
};
function onIceConnectionStateChange(sid, session) {
switch (session.peerconnection.iceConnectionState) {
case 'checking':
session.timeChecking = (new Date()).getTime();
session.firstconnect = true;
break;
case 'completed': // on caller side
case 'connected':
if (session.firstconnect) {
session.firstconnect = false;
var metadata = {};
metadata.setupTime
= (new Date()).getTime() - session.timeChecking;
session.peerconnection.getStats(function (res) {
if(res && res.result) {
res.result().forEach(function (report) {
if (report.type == 'googCandidatePair' &&
report.stat('googActiveConnection') == 'true') {
metadata.localCandidateType
= report.stat('googLocalCandidateType');
metadata.remoteCandidateType
= report.stat('googRemoteCandidateType');
// log pair as well so we can get nice pie
// charts
metadata.candidatePair
= report.stat('googLocalCandidateType') +
';' +
report.stat('googRemoteCandidateType');
if (report.stat('googRemoteAddress').indexOf('[') === 0)
{
metadata.ipv6 = true;
}
}
});
}
});
}
break;
}
}
JingleSession.prototype.accept = function () {
var self = this;
this.state = 'active';
@@ -131,7 +177,7 @@ JingleSession.prototype.accept = function () {
// FIXME: change any inactive to sendrecv or whatever they were originally
pranswer.sdp = pranswer.sdp.replace('a=inactive', 'a=sendrecv');
}
pranswer = simulcast.reverseTransformLocalDescription(pranswer);
pranswer = APP.simulcast.reverseTransformLocalDescription(pranswer);
var prsdp = new SDP(pranswer.sdp);
var accept = $iq({to: this.peerjid,
type: 'set'})
@@ -146,12 +192,13 @@ JingleSession.prototype.accept = function () {
// FIXME: change any inactive to sendrecv or whatever they were originally
sdp = sdp.replace('a=inactive', 'a=sendrecv');
}
var self = this;
this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
function () {
//console.log('setLocalDescription success');
$(document).trigger('setLocalDescription.jingle', [self.sid]);
self.setLocalDescription();
this.connection.sendIQ(accept,
self.connection.sendIQ(accept,
function () {
var ack = {};
ack.source = 'answer';
@@ -348,8 +395,8 @@ JingleSession.prototype.createdOffer = function (sdp) {
action: 'session-initiate',
initiator: this.initiator,
sid: this.sid});
this.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
this.connection.sendIQ(init,
self.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
self.connection.sendIQ(init,
function () {
var ack = {};
ack.source = 'offer';
@@ -370,13 +417,11 @@ JingleSession.prototype.createdOffer = function (sdp) {
sdp.sdp = this.localSDP.raw;
this.peerconnection.setLocalDescription(sdp,
function () {
if(this.usetrickle)
if(self.usetrickle)
{
sendJingle();
$(document).trigger('setLocalDescription.jingle', [self.sid]);
}
else
$(document).trigger('setLocalDescription.jingle', [self.sid]);
self.setLocalDescription();
//console.log('setLocalDescription success');
},
function (e) {
@@ -585,10 +630,10 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) {
initiator: self.initiator,
responder: self.responder,
sid: self.sid });
var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp);
var publicLocalDesc = APP.simulcast.reverseTransformLocalDescription(sdp);
var publicLocalSDP = new SDP(publicLocalDesc.sdp);
publicLocalSDP.toJingle(accept, self.initiator == self.me ? 'initiator' : 'responder', ssrcs);
this.connection.sendIQ(accept,
self.connection.sendIQ(accept,
function () {
var ack = {};
ack.source = 'answer';
@@ -611,10 +656,8 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) {
//console.log('setLocalDescription success');
if (self.usetrickle && !self.usepranswer) {
sendJingle();
$(document).trigger('setLocalDescription.jingle', [self.sid]);
}
else
$(document).trigger('setLocalDescription.jingle', [self.sid]);
self.setLocalDescription();
},
function (e) {
console.error('setLocalDescription failed', e);
@@ -693,7 +736,7 @@ JingleSession.prototype.addSource = function (elem, fromJid) {
$(elem).each(function (idx, content) {
var name = $(content).attr('name');
var lines = '';
tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
$(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
var semantics = this.getAttribute('semantics');
var ssrcs = $(this).find('>source').map(function () {
return this.getAttribute('ssrc');
@@ -703,7 +746,7 @@ JingleSession.prototype.addSource = function (elem, fromJid) {
lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
}
});
tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
var tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
tmp.each(function () {
var ssrc = $(this).attr('ssrc');
if(mySdp.containsSSRC(ssrc)){
@@ -758,7 +801,7 @@ JingleSession.prototype.removeSource = function (elem, fromJid) {
$(elem).each(function (idx, content) {
var name = $(content).attr('name');
var lines = '';
tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
$(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
var semantics = this.getAttribute('semantics');
var ssrcs = $(this).find('>source').map(function () {
return this.getAttribute('ssrc');
@@ -768,7 +811,7 @@ JingleSession.prototype.removeSource = function (elem, fromJid) {
lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
}
});
tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
var tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
tmp.each(function () {
var ssrc = $(this).attr('ssrc');
// This should never happen, but can be useful for bug detection
@@ -800,7 +843,7 @@ JingleSession.prototype.modifySources = function (successCallback) {
if (this.peerconnection.signalingState == 'closed') return;
if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
// There is nothing to do since scheduled job might have been executed by another succeeding call
$(document).trigger('setLocalDescription.jingle', [self.sid]);
this.setLocalDescription();
if(successCallback){
successCallback();
}
@@ -890,7 +933,7 @@ JingleSession.prototype.modifySources = function (successCallback) {
self.peerconnection.setLocalDescription(modifiedAnswer,
function() {
//console.log('modified setLocalDescription ok');
$(document).trigger('setLocalDescription.jingle', [self.sid]);
self.setLocalDescription();
if(successCallback){
successCallback();
}
@@ -921,9 +964,6 @@ JingleSession.prototype.switchStreams = function (new_stream, oldStream, success
var self = this;
// Stop the stream to trigger onended event for old stream
oldStream.stop();
// Remember SDP to figure out added/removed SSRCs
var oldSdp = null;
if(self.peerconnection) {
@@ -934,14 +974,7 @@ JingleSession.prototype.switchStreams = function (new_stream, oldStream, success
self.peerconnection.addStream(new_stream);
}
self.connection.jingle.localVideo = new_stream;
self.connection.jingle.localStreams = [];
//in firefox we have only one stream object
if(self.connection.jingle.localAudio != self.connection.jingle.localVideo)
self.connection.jingle.localStreams.push(self.connection.jingle.localAudio);
self.connection.jingle.localStreams.push(self.connection.jingle.localVideo);
APP.RTC.switchVideoStreams(new_stream, oldStream);
// Conference is not active
if(!oldSdp || !self.peerconnection) {
@@ -1031,7 +1064,7 @@ JingleSession.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
* disabled; otherwise, <tt>false</tt>
*/
JingleSession.prototype.isVideoMute = function () {
var tracks = connection.jingle.localVideo.getVideoTracks();
var tracks = APP.RTC.localVideo.getVideoTracks();
var mute = true;
for (var i = 0; i < tracks.length; ++i) {
@@ -1075,33 +1108,37 @@ JingleSession.prototype.setVideoMute = function (mute, callback, options) {
} else if (this.videoMuteByUser) {
return;
}
if (mute == this.isVideoMute())
var self = this;
var localCallback = function (mute) {
self.connection.emuc.addVideoInfoToPresence(mute);
self.connection.emuc.sendPresence();
return callback(mute)
};
if (mute == APP.RTC.localVideo.isMuted())
{
// Even if no change occurs, the specified callback is to be executed.
// The specified callback may, optionally, return a successCallback
// which is to be executed as well.
var successCallback = callback(mute);
var successCallback = localCallback(mute);
if (successCallback) {
successCallback();
}
} else {
var tracks = connection.jingle.localVideo.getVideoTracks();
for (var i = 0; i < tracks.length; ++i) {
tracks[i].enabled = !mute;
}
APP.RTC.localVideo.setMute(!mute);
this.hardMuteVideo(mute);
this.modifySources(callback(mute));
this.modifySources(localCallback(mute));
}
};
// SDP-based mute by going recvonly/sendrecv
// FIXME: should probably black out the screen as well
JingleSession.prototype.toggleVideoMute = function (callback) {
setVideoMute(isVideoMute(), callback);
this.service.setVideoMute(APP.RTC.localVideo.isMuted(), callback);
};
JingleSession.prototype.hardMuteVideo = function (muted) {
@@ -1187,8 +1224,162 @@ JingleSession.onJingleError = function (session, error)
JingleSession.onJingleFatalError = function (session, error)
{
sessionTerminated = true;
connection.emuc.doLeave();
UI.messageHandler.showError( "Sorry",
"Internal application error[setRemoteDescription]");
}
this.service.sessionTerminated = true;
this.connection.emuc.doLeave();
APP.UI.messageHandler.showError("dialog.sorry",
"dialog.internalError");
}
JingleSession.prototype.setLocalDescription = function () {
// put our ssrcs into presence so other clients can identify our stream
var newssrcs = [];
var media = APP.simulcast.parseMedia(this.peerconnection.localDescription);
media.forEach(function (media) {
if(Object.keys(media.sources).length > 0) {
// TODO(gp) maybe exclude FID streams?
Object.keys(media.sources).forEach(function (ssrc) {
newssrcs.push({
'ssrc': ssrc,
'type': media.type,
'direction': media.direction
});
});
}
else if(this.localStreamsSSRC && this.localStreamsSSRC[media.type])
{
newssrcs.push({
'ssrc': this.localStreamsSSRC[media.type],
'type': media.type,
'direction': media.direction
});
}
});
console.log('new ssrcs', newssrcs);
// Have to clear presence map to get rid of removed streams
this.connection.emuc.clearPresenceMedia();
if (newssrcs.length > 0) {
for (var i = 1; i <= newssrcs.length; i ++) {
// Change video type to screen
if (newssrcs[i-1].type === 'video' && APP.desktopsharing.isUsingScreenStream()) {
newssrcs[i-1].type = 'screen';
}
this.connection.emuc.addMediaToPresence(i,
newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction);
}
this.connection.emuc.sendPresence();
}
}
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
function sendKeyframe(pc) {
console.log('sendkeyframe', pc.iceConnectionState);
if (pc.iceConnectionState !== 'connected') return; // safe...
pc.setRemoteDescription(
pc.remoteDescription,
function () {
pc.createAnswer(
function (modifiedAnswer) {
pc.setLocalDescription(
modifiedAnswer,
function () {
// noop
},
function (error) {
console.log('triggerKeyframe setLocalDescription failed', error);
APP.UI.messageHandler.showError();
}
);
},
function (error) {
console.log('triggerKeyframe createAnswer failed', error);
APP.UI.messageHandler.showError();
}
);
},
function (error) {
console.log('triggerKeyframe setRemoteDescription failed', error);
APP.UI.messageHandler.showError();
}
);
}
JingleSession.prototype.remoteStreamAdded = function (data, times) {
var self = this;
var thessrc;
var ssrc2jid = this.connection.emuc.ssrc2jid;
// look up an associated JID for a stream id
if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) {
// look only at a=ssrc: and _not_ at a=ssrc-group: lines
var ssrclines
= SDPUtil.find_lines(this.peerconnection.remoteDescription.sdp, 'a=ssrc:');
ssrclines = ssrclines.filter(function (line) {
// NOTE(gp) previously we filtered on the mslabel, but that property
// is not always present.
// return line.indexOf('mslabel:' + data.stream.label) !== -1;
return ((line.indexOf('msid:' + data.stream.id) !== -1));
});
if (ssrclines.length) {
thessrc = ssrclines[0].substring(7).split(' ')[0];
// We signal our streams (through Jingle to the focus) before we set
// our presence (through which peers associate remote streams to
// jids). So, it might arrive that a remote stream is added but
// ssrc2jid is not yet updated and thus data.peerjid cannot be
// successfully set. Here we wait for up to a second for the
// presence to arrive.
if (!ssrc2jid[thessrc]) {
if (typeof times === 'undefined')
{
times = 0;
}
if (times > 10)
{
console.warning('Waiting for jid timed out', thessrc);
}
else
{
setTimeout(function(d) {
return function() {
self.remoteStreamAdded(d, times++);
}
}(data), 250);
}
return;
}
// ok to overwrite the one from focus? might save work in colibri.js
console.log('associated jid', ssrc2jid[thessrc], data.peerjid);
if (ssrc2jid[thessrc]) {
data.peerjid = ssrc2jid[thessrc];
}
}
}
APP.RTC.createRemoteStream(data, this.sid, thessrc);
var isVideo = data.stream.getVideoTracks().length > 0;
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
if (isVideo &&
data.peerjid && this.peerjid === data.peerjid &&
data.stream.getVideoTracks().length === 0 &&
APP.RTC.localVideo.getTracks().length > 0) {
window.setTimeout(function () {
sendKeyframe(self.peerconnection);
}, 3000);
}
}
module.exports = JingleSession;

View File

@@ -1,4 +1,6 @@
/* jshint -W117 */
var SDPUtil = require("./SDPUtil");
// SDP STUFF
function SDP(sdp) {
this.media = sdp.split('\r\nm=');
@@ -71,169 +73,6 @@ SDP.prototype.containsSSRC = function(ssrc) {
return contains;
};
function SDPDiffer(mySDP, otherSDP) {
this.mySDP = mySDP;
this.otherSDP = otherSDP;
}
/**
* Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx.
* @param otherSdp the other SDP to check ssrc with.
*/
SDPDiffer.prototype.getNewMedia = function() {
// this could be useful in Array.prototype.
function arrayEquals(array) {
// if the other array is a falsy value, return
if (!array)
return false;
// compare lengths - can save a lot of time
if (this.length != array.length)
return false;
for (var i = 0, l=this.length; i < l; i++) {
// Check if we have nested arrays
if (this[i] instanceof Array && array[i] instanceof Array) {
// recurse into the nested arrays
if (!this[i].equals(array[i]))
return false;
}
else if (this[i] != array[i]) {
// Warning - two different object instances will never be equal: {x:20} != {x:20}
return false;
}
}
return true;
}
var myMedias = this.mySDP.getMediaSsrcMap();
var othersMedias = this.otherSDP.getMediaSsrcMap();
var newMedia = {};
Object.keys(othersMedias).forEach(function(othersMediaIdx) {
var myMedia = myMedias[othersMediaIdx];
var othersMedia = othersMedias[othersMediaIdx];
if(!myMedia && othersMedia) {
// Add whole channel
newMedia[othersMediaIdx] = othersMedia;
return;
}
// Look for new ssrcs accross the channel
Object.keys(othersMedia.ssrcs).forEach(function(ssrc) {
if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
// Allocate channel if we've found ssrc that doesn't exist in our channel
if(!newMedia[othersMediaIdx]){
newMedia[othersMediaIdx] = {
mediaindex: othersMedia.mediaindex,
mid: othersMedia.mid,
ssrcs: {},
ssrcGroups: []
};
}
newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
}
});
// Look for new ssrc groups across the channels
othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){
// try to match the other ssrc-group with an ssrc-group of ours
var matched = false;
for (var i = 0; i < myMedia.ssrcGroups.length; i++) {
var mySsrcGroup = myMedia.ssrcGroups[i];
if (otherSsrcGroup.semantics == mySsrcGroup.semantics
&& arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
matched = true;
break;
}
}
if (!matched) {
// Allocate channel if we've found an ssrc-group that doesn't
// exist in our channel
if(!newMedia[othersMediaIdx]){
newMedia[othersMediaIdx] = {
mediaindex: othersMedia.mediaindex,
mid: othersMedia.mid,
ssrcs: {},
ssrcGroups: []
};
}
newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
}
});
});
return newMedia;
};
/**
* Sends SSRC update IQ.
* @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
* @param sid session identifier that will be put into the IQ.
* @param initiator initiator identifier.
* @param toJid destination Jid
* @param isAdd indicates if this is remove or add operation.
*/
SDPDiffer.prototype.toJingle = function(modify) {
var sdpMediaSsrcs = this.getNewMedia();
var self = this;
// FIXME: only announce video ssrcs since we mix audio and dont need
// the audio ssrcs therefore
var modified = false;
Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){
modified = true;
var media = sdpMediaSsrcs[mediaindex];
modify.c('content', {name: media.mid});
modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid});
// FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
// generate sources from lines
Object.keys(media.ssrcs).forEach(function(ssrcNum) {
var mediaSsrc = media.ssrcs[ssrcNum];
modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
modify.attrs({ssrc: mediaSsrc.ssrc});
// iterate over ssrc lines
mediaSsrc.lines.forEach(function (line) {
var idx = line.indexOf(' ');
var kv = line.substr(idx + 1);
modify.c('parameter');
if (kv.indexOf(':') == -1) {
modify.attrs({ name: kv });
} else {
modify.attrs({ name: kv.split(':', 2)[0] });
modify.attrs({ value: kv.split(':', 2)[1] });
}
modify.up(); // end of parameter
});
modify.up(); // end of source
});
// generate source groups from lines
media.ssrcGroups.forEach(function(ssrcGroup) {
if (ssrcGroup.ssrcs.length != 0) {
modify.c('ssrc-group', {
semantics: ssrcGroup.semantics,
xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
});
ssrcGroup.ssrcs.forEach(function (ssrc) {
modify.c('source', { ssrc: ssrc })
.up(); // end of source
});
modify.up(); // end of ssrc-group
}
});
modify.up(); // end of description
modify.up(); // end of content
});
return modified;
};
// remove iSAC and CN from SDP
SDP.prototype.mangle = function () {
@@ -396,11 +235,11 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
var msid = null;
if(mline.media == "audio")
{
msid = connection.jingle.localAudio.getAudioTracks()[0].id;
msid = APP.RTC.localAudio.getId();
}
else
{
msid = connection.jingle.localVideo.getVideoTracks()[0].id;
msid = APP.RTC.localVideo.getId();
}
if(msid != null)
{
@@ -776,352 +615,6 @@ SDP.prototype.jingle2media = function (content) {
return media;
};
SDPUtil = {
iceparams: function (mediadesc, sessiondesc) {
var data = null;
if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
data = {
ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
};
}
return data;
},
parse_iceufrag: function (line) {
return line.substring(12);
},
build_iceufrag: function (frag) {
return 'a=ice-ufrag:' + frag;
},
parse_icepwd: function (line) {
return line.substring(10);
},
build_icepwd: function (pwd) {
return 'a=ice-pwd:' + pwd;
},
parse_mid: function (line) {
return line.substring(6);
},
parse_mline: function (line) {
var parts = line.substring(2).split(' '),
data = {};
data.media = parts.shift();
data.port = parts.shift();
data.proto = parts.shift();
if (parts[parts.length - 1] === '') { // trailing whitespace
parts.pop();
}
data.fmt = parts;
return data;
},
build_mline: function (mline) {
return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
},
parse_rtpmap: function (line) {
var parts = line.substring(9).split(' '),
data = {};
data.id = parts.shift();
parts = parts[0].split('/');
data.name = parts.shift();
data.clockrate = parts.shift();
data.channels = parts.length ? parts.shift() : '1';
return data;
},
/**
* Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
* @param line eg. "a=sctpmap:5000 webrtc-datachannel"
* @returns [SCTP port number, protocol, streams]
*/
parse_sctpmap: function (line)
{
var parts = line.substring(10).split(' ');
var sctpPort = parts[0];
var protocol = parts[1];
// Stream count is optional
var streamCount = parts.length > 2 ? parts[2] : null;
return [sctpPort, protocol, streamCount];// SCTP port
},
build_rtpmap: function (el) {
var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
line += '/' + el.getAttribute('channels');
}
return line;
},
parse_crypto: function (line) {
var parts = line.substring(9).split(' '),
data = {};
data.tag = parts.shift();
data['crypto-suite'] = parts.shift();
data['key-params'] = parts.shift();
if (parts.length) {
data['session-params'] = parts.join(' ');
}
return data;
},
parse_fingerprint: function (line) { // RFC 4572
var parts = line.substring(14).split(' '),
data = {};
data.hash = parts.shift();
data.fingerprint = parts.shift();
// TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
return data;
},
parse_fmtp: function (line) {
var parts = line.split(' '),
i, key, value,
data = [];
parts.shift();
parts = parts.join(' ').split(';');
for (i = 0; i < parts.length; i++) {
key = parts[i].split('=')[0];
while (key.length && key[0] == ' ') {
key = key.substring(1);
}
value = parts[i].split('=')[1];
if (key && value) {
data.push({name: key, value: value});
} else if (key) {
// rfc 4733 (DTMF) style stuff
data.push({name: '', value: key});
}
}
return data;
},
parse_icecandidate: function (line) {
var candidate = {},
elems = line.split(' ');
candidate.foundation = elems[0].substring(12);
candidate.component = elems[1];
candidate.protocol = elems[2].toLowerCase();
candidate.priority = elems[3];
candidate.ip = elems[4];
candidate.port = elems[5];
// elems[6] => "typ"
candidate.type = elems[7];
candidate.generation = 0; // default value, may be overwritten below
for (var i = 8; i < elems.length; i += 2) {
switch (elems[i]) {
case 'raddr':
candidate['rel-addr'] = elems[i + 1];
break;
case 'rport':
candidate['rel-port'] = elems[i + 1];
break;
case 'generation':
candidate.generation = elems[i + 1];
break;
case 'tcptype':
candidate.tcptype = elems[i + 1];
break;
default: // TODO
console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
}
}
candidate.network = '1';
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
return candidate;
},
build_icecandidate: function (cand) {
var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
line += ' ';
switch (cand.type) {
case 'srflx':
case 'prflx':
case 'relay':
if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
line += 'raddr';
line += ' ';
line += cand['rel-addr'];
line += ' ';
line += 'rport';
line += ' ';
line += cand['rel-port'];
line += ' ';
}
break;
}
if (cand.hasOwnAttribute('tcptype')) {
line += 'tcptype';
line += ' ';
line += cand.tcptype;
line += ' ';
}
line += 'generation';
line += ' ';
line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
return line;
},
parse_ssrc: function (desc) {
// proprietary mapping of a=ssrc lines
// TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
// and parse according to that
var lines = desc.split('\r\n'),
data = {};
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, 7) == 'a=ssrc:') {
var idx = lines[i].indexOf(' ');
data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
}
}
return data;
},
parse_rtcpfb: function (line) {
var parts = line.substr(10).split(' ');
var data = {};
data.pt = parts.shift();
data.type = parts.shift();
data.params = parts;
return data;
},
parse_extmap: function (line) {
var parts = line.substr(9).split(' ');
var data = {};
data.value = parts.shift();
if (data.value.indexOf('/') != -1) {
data.direction = data.value.substr(data.value.indexOf('/') + 1);
data.value = data.value.substr(0, data.value.indexOf('/'));
} else {
data.direction = 'both';
}
data.uri = parts.shift();
data.params = parts;
return data;
},
find_line: function (haystack, needle, sessionpart) {
var lines = haystack.split('\r\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, needle.length) == needle) {
return lines[i];
}
}
if (!sessionpart) {
return false;
}
// search session part
lines = sessionpart.split('\r\n');
for (var j = 0; j < lines.length; j++) {
if (lines[j].substring(0, needle.length) == needle) {
return lines[j];
}
}
return false;
},
find_lines: function (haystack, needle, sessionpart) {
var lines = haystack.split('\r\n'),
needles = [];
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, needle.length) == needle)
needles.push(lines[i]);
}
if (needles.length || !sessionpart) {
return needles;
}
// search session part
lines = sessionpart.split('\r\n');
for (var j = 0; j < lines.length; j++) {
if (lines[j].substring(0, needle.length) == needle) {
needles.push(lines[j]);
}
}
return needles;
},
candidateToJingle: function (line) {
// a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
// <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
if (line.indexOf('candidate:') === 0) {
line = 'a=' + line;
} else if (line.substring(0, 12) != 'a=candidate:') {
console.log('parseCandidate called with a line that is not a candidate line');
console.log(line);
return null;
}
if (line.substring(line.length - 2) == '\r\n') // chomp it
line = line.substring(0, line.length - 2);
var candidate = {},
elems = line.split(' '),
i;
if (elems[6] != 'typ') {
console.log('did not find typ in the right place');
console.log(line);
return null;
}
candidate.foundation = elems[0].substring(12);
candidate.component = elems[1];
candidate.protocol = elems[2].toLowerCase();
candidate.priority = elems[3];
candidate.ip = elems[4];
candidate.port = elems[5];
// elems[6] => "typ"
candidate.type = elems[7];
candidate.generation = '0'; // default, may be overwritten below
for (i = 8; i < elems.length; i += 2) {
switch (elems[i]) {
case 'raddr':
candidate['rel-addr'] = elems[i + 1];
break;
case 'rport':
candidate['rel-port'] = elems[i + 1];
break;
case 'generation':
candidate.generation = elems[i + 1];
break;
case 'tcptype':
candidate.tcptype = elems[i + 1];
break;
default: // TODO
console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
}
}
candidate.network = '1';
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
return candidate;
},
candidateFromJingle: function (cand) {
var line = 'a=candidate:';
line += cand.getAttribute('foundation');
line += ' ';
line += cand.getAttribute('component');
line += ' ';
line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
line += ' ';
line += cand.getAttribute('priority');
line += ' ';
line += cand.getAttribute('ip');
line += ' ';
line += cand.getAttribute('port');
line += ' ';
line += 'typ';
line += ' ' + cand.getAttribute('type');
line += ' ';
switch (cand.getAttribute('type')) {
case 'srflx':
case 'prflx':
case 'relay':
if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
line += 'raddr';
line += ' ';
line += cand.getAttribute('rel-addr');
line += ' ';
line += 'rport';
line += ' ';
line += cand.getAttribute('rel-port');
line += ' ';
}
break;
}
if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
line += 'tcptype';
line += ' ';
line += cand.getAttribute('tcptype');
line += ' ';
}
line += 'generation';
line += ' ';
line += cand.getAttribute('generation') || '0';
return line + '\r\n';
}
};
module.exports = SDP;

165
modules/xmpp/SDPDiffer.js Normal file
View File

@@ -0,0 +1,165 @@
function SDPDiffer(mySDP, otherSDP) {
this.mySDP = mySDP;
this.otherSDP = otherSDP;
}
/**
* Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx.
* @param otherSdp the other SDP to check ssrc with.
*/
SDPDiffer.prototype.getNewMedia = function() {
// this could be useful in Array.prototype.
function arrayEquals(array) {
// if the other array is a falsy value, return
if (!array)
return false;
// compare lengths - can save a lot of time
if (this.length != array.length)
return false;
for (var i = 0, l=this.length; i < l; i++) {
// Check if we have nested arrays
if (this[i] instanceof Array && array[i] instanceof Array) {
// recurse into the nested arrays
if (!this[i].equals(array[i]))
return false;
}
else if (this[i] != array[i]) {
// Warning - two different object instances will never be equal: {x:20} != {x:20}
return false;
}
}
return true;
}
var myMedias = this.mySDP.getMediaSsrcMap();
var othersMedias = this.otherSDP.getMediaSsrcMap();
var newMedia = {};
Object.keys(othersMedias).forEach(function(othersMediaIdx) {
var myMedia = myMedias[othersMediaIdx];
var othersMedia = othersMedias[othersMediaIdx];
if(!myMedia && othersMedia) {
// Add whole channel
newMedia[othersMediaIdx] = othersMedia;
return;
}
// Look for new ssrcs accross the channel
Object.keys(othersMedia.ssrcs).forEach(function(ssrc) {
if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
// Allocate channel if we've found ssrc that doesn't exist in our channel
if(!newMedia[othersMediaIdx]){
newMedia[othersMediaIdx] = {
mediaindex: othersMedia.mediaindex,
mid: othersMedia.mid,
ssrcs: {},
ssrcGroups: []
};
}
newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
}
});
// Look for new ssrc groups across the channels
othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){
// try to match the other ssrc-group with an ssrc-group of ours
var matched = false;
for (var i = 0; i < myMedia.ssrcGroups.length; i++) {
var mySsrcGroup = myMedia.ssrcGroups[i];
if (otherSsrcGroup.semantics == mySsrcGroup.semantics
&& arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
matched = true;
break;
}
}
if (!matched) {
// Allocate channel if we've found an ssrc-group that doesn't
// exist in our channel
if(!newMedia[othersMediaIdx]){
newMedia[othersMediaIdx] = {
mediaindex: othersMedia.mediaindex,
mid: othersMedia.mid,
ssrcs: {},
ssrcGroups: []
};
}
newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
}
});
});
return newMedia;
};
/**
* Sends SSRC update IQ.
* @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
* @param sid session identifier that will be put into the IQ.
* @param initiator initiator identifier.
* @param toJid destination Jid
* @param isAdd indicates if this is remove or add operation.
*/
SDPDiffer.prototype.toJingle = function(modify) {
var sdpMediaSsrcs = this.getNewMedia();
var self = this;
// FIXME: only announce video ssrcs since we mix audio and dont need
// the audio ssrcs therefore
var modified = false;
Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){
modified = true;
var media = sdpMediaSsrcs[mediaindex];
modify.c('content', {name: media.mid});
modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid});
// FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
// generate sources from lines
Object.keys(media.ssrcs).forEach(function(ssrcNum) {
var mediaSsrc = media.ssrcs[ssrcNum];
modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
modify.attrs({ssrc: mediaSsrc.ssrc});
// iterate over ssrc lines
mediaSsrc.lines.forEach(function (line) {
var idx = line.indexOf(' ');
var kv = line.substr(idx + 1);
modify.c('parameter');
if (kv.indexOf(':') == -1) {
modify.attrs({ name: kv });
} else {
modify.attrs({ name: kv.split(':', 2)[0] });
modify.attrs({ value: kv.split(':', 2)[1] });
}
modify.up(); // end of parameter
});
modify.up(); // end of source
});
// generate source groups from lines
media.ssrcGroups.forEach(function(ssrcGroup) {
if (ssrcGroup.ssrcs.length != 0) {
modify.c('ssrc-group', {
semantics: ssrcGroup.semantics,
xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
});
ssrcGroup.ssrcs.forEach(function (ssrc) {
modify.c('source', { ssrc: ssrc })
.up(); // end of source
});
modify.up(); // end of ssrc-group
}
});
modify.up(); // end of description
modify.up(); // end of content
});
return modified;
};
module.exports = SDPDiffer;

349
modules/xmpp/SDPUtil.js Normal file
View File

@@ -0,0 +1,349 @@
SDPUtil = {
iceparams: function (mediadesc, sessiondesc) {
var data = null;
if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
data = {
ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
};
}
return data;
},
parse_iceufrag: function (line) {
return line.substring(12);
},
build_iceufrag: function (frag) {
return 'a=ice-ufrag:' + frag;
},
parse_icepwd: function (line) {
return line.substring(10);
},
build_icepwd: function (pwd) {
return 'a=ice-pwd:' + pwd;
},
parse_mid: function (line) {
return line.substring(6);
},
parse_mline: function (line) {
var parts = line.substring(2).split(' '),
data = {};
data.media = parts.shift();
data.port = parts.shift();
data.proto = parts.shift();
if (parts[parts.length - 1] === '') { // trailing whitespace
parts.pop();
}
data.fmt = parts;
return data;
},
build_mline: function (mline) {
return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
},
parse_rtpmap: function (line) {
var parts = line.substring(9).split(' '),
data = {};
data.id = parts.shift();
parts = parts[0].split('/');
data.name = parts.shift();
data.clockrate = parts.shift();
data.channels = parts.length ? parts.shift() : '1';
return data;
},
/**
* Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
* @param line eg. "a=sctpmap:5000 webrtc-datachannel"
* @returns [SCTP port number, protocol, streams]
*/
parse_sctpmap: function (line)
{
var parts = line.substring(10).split(' ');
var sctpPort = parts[0];
var protocol = parts[1];
// Stream count is optional
var streamCount = parts.length > 2 ? parts[2] : null;
return [sctpPort, protocol, streamCount];// SCTP port
},
build_rtpmap: function (el) {
var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
line += '/' + el.getAttribute('channels');
}
return line;
},
parse_crypto: function (line) {
var parts = line.substring(9).split(' '),
data = {};
data.tag = parts.shift();
data['crypto-suite'] = parts.shift();
data['key-params'] = parts.shift();
if (parts.length) {
data['session-params'] = parts.join(' ');
}
return data;
},
parse_fingerprint: function (line) { // RFC 4572
var parts = line.substring(14).split(' '),
data = {};
data.hash = parts.shift();
data.fingerprint = parts.shift();
// TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
return data;
},
parse_fmtp: function (line) {
var parts = line.split(' '),
i, key, value,
data = [];
parts.shift();
parts = parts.join(' ').split(';');
for (i = 0; i < parts.length; i++) {
key = parts[i].split('=')[0];
while (key.length && key[0] == ' ') {
key = key.substring(1);
}
value = parts[i].split('=')[1];
if (key && value) {
data.push({name: key, value: value});
} else if (key) {
// rfc 4733 (DTMF) style stuff
data.push({name: '', value: key});
}
}
return data;
},
parse_icecandidate: function (line) {
var candidate = {},
elems = line.split(' ');
candidate.foundation = elems[0].substring(12);
candidate.component = elems[1];
candidate.protocol = elems[2].toLowerCase();
candidate.priority = elems[3];
candidate.ip = elems[4];
candidate.port = elems[5];
// elems[6] => "typ"
candidate.type = elems[7];
candidate.generation = 0; // default value, may be overwritten below
for (var i = 8; i < elems.length; i += 2) {
switch (elems[i]) {
case 'raddr':
candidate['rel-addr'] = elems[i + 1];
break;
case 'rport':
candidate['rel-port'] = elems[i + 1];
break;
case 'generation':
candidate.generation = elems[i + 1];
break;
case 'tcptype':
candidate.tcptype = elems[i + 1];
break;
default: // TODO
console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
}
}
candidate.network = '1';
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
return candidate;
},
build_icecandidate: function (cand) {
var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
line += ' ';
switch (cand.type) {
case 'srflx':
case 'prflx':
case 'relay':
if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
line += 'raddr';
line += ' ';
line += cand['rel-addr'];
line += ' ';
line += 'rport';
line += ' ';
line += cand['rel-port'];
line += ' ';
}
break;
}
if (cand.hasOwnAttribute('tcptype')) {
line += 'tcptype';
line += ' ';
line += cand.tcptype;
line += ' ';
}
line += 'generation';
line += ' ';
line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
return line;
},
parse_ssrc: function (desc) {
// proprietary mapping of a=ssrc lines
// TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
// and parse according to that
var lines = desc.split('\r\n'),
data = {};
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, 7) == 'a=ssrc:') {
var idx = lines[i].indexOf(' ');
data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
}
}
return data;
},
parse_rtcpfb: function (line) {
var parts = line.substr(10).split(' ');
var data = {};
data.pt = parts.shift();
data.type = parts.shift();
data.params = parts;
return data;
},
parse_extmap: function (line) {
var parts = line.substr(9).split(' ');
var data = {};
data.value = parts.shift();
if (data.value.indexOf('/') != -1) {
data.direction = data.value.substr(data.value.indexOf('/') + 1);
data.value = data.value.substr(0, data.value.indexOf('/'));
} else {
data.direction = 'both';
}
data.uri = parts.shift();
data.params = parts;
return data;
},
find_line: function (haystack, needle, sessionpart) {
var lines = haystack.split('\r\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, needle.length) == needle) {
return lines[i];
}
}
if (!sessionpart) {
return false;
}
// search session part
lines = sessionpart.split('\r\n');
for (var j = 0; j < lines.length; j++) {
if (lines[j].substring(0, needle.length) == needle) {
return lines[j];
}
}
return false;
},
find_lines: function (haystack, needle, sessionpart) {
var lines = haystack.split('\r\n'),
needles = [];
for (var i = 0; i < lines.length; i++) {
if (lines[i].substring(0, needle.length) == needle)
needles.push(lines[i]);
}
if (needles.length || !sessionpart) {
return needles;
}
// search session part
lines = sessionpart.split('\r\n');
for (var j = 0; j < lines.length; j++) {
if (lines[j].substring(0, needle.length) == needle) {
needles.push(lines[j]);
}
}
return needles;
},
candidateToJingle: function (line) {
// a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
// <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
if (line.indexOf('candidate:') === 0) {
line = 'a=' + line;
} else if (line.substring(0, 12) != 'a=candidate:') {
console.log('parseCandidate called with a line that is not a candidate line');
console.log(line);
return null;
}
if (line.substring(line.length - 2) == '\r\n') // chomp it
line = line.substring(0, line.length - 2);
var candidate = {},
elems = line.split(' '),
i;
if (elems[6] != 'typ') {
console.log('did not find typ in the right place');
console.log(line);
return null;
}
candidate.foundation = elems[0].substring(12);
candidate.component = elems[1];
candidate.protocol = elems[2].toLowerCase();
candidate.priority = elems[3];
candidate.ip = elems[4];
candidate.port = elems[5];
// elems[6] => "typ"
candidate.type = elems[7];
candidate.generation = '0'; // default, may be overwritten below
for (i = 8; i < elems.length; i += 2) {
switch (elems[i]) {
case 'raddr':
candidate['rel-addr'] = elems[i + 1];
break;
case 'rport':
candidate['rel-port'] = elems[i + 1];
break;
case 'generation':
candidate.generation = elems[i + 1];
break;
case 'tcptype':
candidate.tcptype = elems[i + 1];
break;
default: // TODO
console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
}
}
candidate.network = '1';
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
return candidate;
},
candidateFromJingle: function (cand) {
var line = 'a=candidate:';
line += cand.getAttribute('foundation');
line += ' ';
line += cand.getAttribute('component');
line += ' ';
line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
line += ' ';
line += cand.getAttribute('priority');
line += ' ';
line += cand.getAttribute('ip');
line += ' ';
line += cand.getAttribute('port');
line += ' ';
line += 'typ';
line += ' ' + cand.getAttribute('type');
line += ' ';
switch (cand.getAttribute('type')) {
case 'srflx':
case 'prflx':
case 'relay':
if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
line += 'raddr';
line += ' ';
line += cand.getAttribute('rel-addr');
line += ' ';
line += 'rport';
line += ' ';
line += cand.getAttribute('rel-port');
line += ' ';
}
break;
}
if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
line += 'tcptype';
line += ' ';
line += cand.getAttribute('tcptype');
line += ' ';
}
line += 'generation';
line += ' ';
line += cand.getAttribute('generation') || '0';
return line + '\r\n';
}
};
module.exports = SDPUtil;

Some files were not shown because too many files have changed in this diff Show More