Compare commits

...

127 Commits
331 ... 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
hristoterezov
faaf24d3c4 Creates simulcast module 2015-01-09 17:32:35 +02:00
129 changed files with 34866 additions and 18870 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="simulcast.js?v=8"></script><!-- simulcast handling -->
<script src="libs/strophe/strophe.jingle.adapter.js?v=4"></script><!-- strophe.jingle bundles -->
<script src="config.js?v=6"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="libs/strophe/strophe.min.js?v=1"></script>
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
<script src="libs/strophe/strophe.jingle.js?v=2"></script>
<script src="libs/strophe/strophe.jingle.sdp.js?v=4"></script>
<script src="libs/strophe/strophe.jingle.session.js?v=4"></script>
<script src="libs/strophe/strophe.util.js"></script>
<script src="libs/jquery-ui.js"></script>
<script src="libs/rayo.js?v=1"></script>
<script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
<script src="libs/pako.bundle.js?v=1"></script><!-- zlib deflate -->
<script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
<script src="interface_config.js?v=5"></script>
<script src="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/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

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

@@ -0,0 +1,32 @@
/**
*
* @constructor
*/
function SimulcastLogger(name, lvl) {
this.name = name;
this.lvl = lvl;
}
SimulcastLogger.prototype.log = function (text) {
if (this.lvl) {
console.log(text);
}
};
SimulcastLogger.prototype.info = function (text) {
if (this.lvl > 1) {
console.info(text);
}
};
SimulcastLogger.prototype.fine = function (text) {
if (this.lvl > 2) {
console.log(text);
}
};
SimulcastLogger.prototype.error = function (text) {
console.error(text);
};
module.exports = SimulcastLogger;

View File

@@ -0,0 +1,268 @@
var SimulcastLogger = require("./SimulcastLogger");
var SimulcastUtils = require("./SimulcastUtils");
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
function SimulcastReceiver() {
this.simulcastUtils = new SimulcastUtils();
this.logger = new SimulcastLogger('SimulcastReceiver', 1);
}
SimulcastReceiver.prototype._remoteVideoSourceCache = '';
SimulcastReceiver.prototype._remoteMaps = {
msid2Quality: {},
ssrc2Msid: {},
msid2ssrc: {},
receivingVideoStreams: {}
};
SimulcastReceiver.prototype._cacheRemoteVideoSources = function (lines) {
this._remoteVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
};
SimulcastReceiver.prototype._restoreRemoteVideoSources = function (lines) {
this.simulcastUtils._replaceVideoSources(lines, this._remoteVideoSourceCache);
};
SimulcastReceiver.prototype._ensureGoogConference = function (lines) {
var sb;
this.logger.info('Ensuring x-google-conference flag...')
if (this.simulcastUtils._indexOfArray('a=x-google-flag:conference', lines) === this.simulcastUtils._emptyCompoundIndex) {
// TODO(gp) do that for the audio as well as suggested by fippo.
// Add the google conference flag
sb = this.simulcastUtils._getVideoSources(lines);
sb = ['a=x-google-flag:conference'].concat(sb);
this.simulcastUtils._replaceVideoSources(lines, sb);
}
};
SimulcastReceiver.prototype._restoreSimulcastGroups = function (sb) {
this._restoreRemoteVideoSources(sb);
};
/**
* Restores the simulcast groups of the remote description. In
* transformRemoteDescription we remove those in order for the set remote
* description to succeed. The focus needs the signal the groups to new
* participants.
*
* @param desc
* @returns {*}
*/
SimulcastReceiver.prototype.reverseTransformRemoteDescription = function (desc) {
var sb;
if (!this.simulcastUtils.isValidDescription(desc)) {
return desc;
}
if (config.enableSimulcast) {
sb = desc.sdp.split('\r\n');
this._restoreSimulcastGroups(sb);
desc = new RTCSessionDescription({
type: desc.type,
sdp: sb.join('\r\n')
});
}
return desc;
};
SimulcastUtils.prototype._ensureOrder = function (lines) {
var videoSources, sb;
videoSources = this.parseMedia(lines, ['video'])[0];
sb = this._compileVideoSources(videoSources);
this._replaceVideoSources(lines, sb);
};
SimulcastReceiver.prototype._updateRemoteMaps = function (lines) {
var remoteVideoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0],
videoSource, quality;
// (re) initialize the remote maps.
this._remoteMaps.msid2Quality = {};
this._remoteMaps.ssrc2Msid = {};
this._remoteMaps.msid2ssrc = {};
var self = this;
if (remoteVideoSources.groups && remoteVideoSources.groups.length !== 0) {
remoteVideoSources.groups.forEach(function (group) {
if (group.semantics === 'SIM' && group.ssrcs && group.ssrcs.length !== 0) {
quality = 0;
group.ssrcs.forEach(function (ssrc) {
videoSource = remoteVideoSources.sources[ssrc];
self._remoteMaps.msid2Quality[videoSource.msid] = quality++;
self._remoteMaps.ssrc2Msid[videoSource.ssrc] = videoSource.msid;
self._remoteMaps.msid2ssrc[videoSource.msid] = videoSource.ssrc;
});
}
});
}
};
SimulcastReceiver.prototype._setReceivingVideoStream = function (resource, ssrc) {
this._remoteMaps.receivingVideoStreams[resource] = ssrc;
};
/**
* Returns a stream with single video track, the one currently being
* received by this endpoint.
*
* @param stream the remote simulcast stream.
* @returns {webkitMediaStream}
*/
SimulcastReceiver.prototype.getReceivingVideoStream = function (stream) {
var tracks, i, electedTrack, msid, quality = 0, receivingTrackId;
var self = this;
if (config.enableSimulcast) {
stream.getVideoTracks().some(function (track) {
return Object.keys(self._remoteMaps.receivingVideoStreams).some(function (resource) {
var ssrc = self._remoteMaps.receivingVideoStreams[resource];
var msid = self._remoteMaps.ssrc2Msid[ssrc];
if (msid == [stream.id, track.id].join(' ')) {
electedTrack = track;
return true;
}
});
});
if (!electedTrack) {
// we don't have an elected track, choose by initial quality.
tracks = stream.getVideoTracks();
for (i = 0; i < tracks.length; i++) {
msid = [stream.id, tracks[i].id].join(' ');
if (this._remoteMaps.msid2Quality[msid] === quality) {
electedTrack = tracks[i];
break;
}
}
// TODO(gp) if the initialQuality could not be satisfied, lower
// the requirement and try again.
}
}
return (electedTrack)
? new webkitMediaStream([electedTrack])
: stream;
};
SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
var resource = Strophe.getResourceFromJid(jid);
var ssrc = this._remoteMaps.receivingVideoStreams[resource];
// If we haven't receiving a "changed" event yet, then we must be receiving
// low quality (that the sender always streams).
if(!ssrc)
{
var remoteStreamObject = APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
var remoteStream = remoteStreamObject.getOriginalStream();
var tracks = remoteStream.getVideoTracks();
if (tracks) {
for (var k = 0; k < tracks.length; k++) {
var track = tracks[k];
var msid = [remoteStream.id, track.id].join(' ');
var _ssrc = this._remoteMaps.msid2ssrc[msid];
var quality = this._remoteMaps.msid2Quality[msid];
if (quality == 0) {
ssrc = _ssrc;
}
}
}
}
return ssrc;
};
SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
{
var sid, electedStream;
var i, j, k;
var jid = APP.xmpp.getJidFromSSRC(ssrc);
if(jid && APP.RTC.remoteStreams[jid])
{
var remoteStreamObject = APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
var remoteStream = remoteStreamObject.getOriginalStream();
var tracks = remoteStream.getVideoTracks();
if (tracks) {
for (k = 0; k < tracks.length; k++) {
var track = tracks[k];
var msid = [remoteStream.id, track.id].join(' ');
var tmp = this._remoteMaps.msid2ssrc[msid];
if (tmp == ssrc) {
electedStream = new webkitMediaStream([track]);
sid = remoteStreamObject.sid;
// stream found, stop.
break;
}
}
}
}
else
{
console.debug(APP.RTC.remoteStreams, jid, ssrc);
}
return {
sid: sid,
stream: electedStream
};
};
/**
* Gets the fully qualified msid (stream.id + track.id) associated to the
* SSRC.
*
* @param ssrc
* @returns {*}
*/
SimulcastReceiver.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
return this._remoteMaps.ssrc2Msid[ssrc];
};
/**
* Removes the ssrc-group:SIM from the remote description bacause Chrome
* either gets confused and thinks this is an FID group or, if an FID group
* is already present, it fails to set the remote description.
*
* @param desc
* @returns {*}
*/
SimulcastReceiver.prototype.transformRemoteDescription = function (desc) {
if (desc && desc.sdp) {
var sb = desc.sdp.split('\r\n');
this._updateRemoteMaps(sb);
this._cacheRemoteVideoSources(sb);
// NOTE(gp) this needs to be called after updateRemoteMaps because we
// need the simulcast group in the _updateRemoteMaps() method.
this.simulcastUtils._removeSimulcastGroup(sb);
if (desc.sdp.indexOf('a=ssrc-group:SIM') !== -1) {
// We don't need the goog conference flag if we're not doing
// simulcast.
this._ensureGoogConference(sb);
}
desc = new RTCSessionDescription({
type: desc.type,
sdp: sb.join('\r\n')
});
this.logger.fine(['Transformed remote description', desc.sdp].join(' '));
}
return desc;
};
module.exports = SimulcastReceiver;

View File

@@ -0,0 +1,521 @@
var SimulcastLogger = require("./SimulcastLogger");
var SimulcastUtils = require("./SimulcastUtils");
function SimulcastSender() {
this.simulcastUtils = new SimulcastUtils();
this.logger = new SimulcastLogger('SimulcastSender', 1);
}
SimulcastSender.prototype.displayedLocalVideoStream = null;
SimulcastSender.prototype._generateGuid = (function () {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return function () {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
};
}());
// Returns a random integer between min (included) and max (excluded)
// Using Math.round() gives a non-uniform distribution!
SimulcastSender.prototype._generateRandomSSRC = function () {
var min = 0, max = 0xffffffff;
return Math.floor(Math.random() * (max - min)) + min;
};
SimulcastSender.prototype.getLocalVideoStream = function () {
return (this.displayedLocalVideoStream != null)
? this.displayedLocalVideoStream
// in case we have no simulcast at all, i.e. we didn't perform the GUM
: APP.RTC.localVideo.getOriginalStream();
};
function NativeSimulcastSender() {
SimulcastSender.call(this); // call the super constructor.
}
NativeSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
NativeSimulcastSender.prototype._localExplosionMap = {};
NativeSimulcastSender.prototype._isUsingScreenStream = false;
NativeSimulcastSender.prototype._localVideoSourceCache = '';
NativeSimulcastSender.prototype.reset = function () {
this._localExplosionMap = {};
this._isUsingScreenStream = APP.desktopsharing.isUsingScreenStream();
};
NativeSimulcastSender.prototype._cacheLocalVideoSources = function (lines) {
this._localVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
};
NativeSimulcastSender.prototype._restoreLocalVideoSources = function (lines) {
this.simulcastUtils._replaceVideoSources(lines, this._localVideoSourceCache);
};
NativeSimulcastSender.prototype._appendSimulcastGroup = function (lines) {
var videoSources, ssrcGroup, simSSRC, numOfSubs = 2, i, sb, msid;
this.logger.info('Appending simulcast group...');
// Get the primary SSRC information.
videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
// Start building the SIM SSRC group.
ssrcGroup = ['a=ssrc-group:SIM'];
// The video source buffer.
sb = [];
// Create the simulcast sub-streams.
for (i = 0; i < numOfSubs; i++) {
// TODO(gp) prevent SSRC collision.
simSSRC = this._generateRandomSSRC();
ssrcGroup.push(simSSRC);
if (videoSources.base) {
sb.splice.apply(sb, [sb.length, 0].concat(
[["a=ssrc:", simSSRC, " cname:", videoSources.base.cname].join(''),
["a=ssrc:", simSSRC, " msid:", videoSources.base.msid].join('')]
));
}
this.logger.info(['Generated substream ', i, ' with SSRC ', simSSRC, '.'].join(''));
}
// Add the group sim layers.
sb.splice(0, 0, ssrcGroup.join(' '))
this.simulcastUtils._replaceVideoSources(lines, sb);
};
// Does the actual patching.
NativeSimulcastSender.prototype._ensureSimulcastGroup = function (lines) {
this.logger.info('Ensuring simulcast group...');
if (this.simulcastUtils._indexOfArray('a=ssrc-group:SIM', lines) === this.simulcastUtils._emptyCompoundIndex) {
this._appendSimulcastGroup(lines);
this._cacheLocalVideoSources(lines);
} else {
// verify that the ssrcs participating in the SIM group are present
// in the SDP (needed for presence).
this._restoreLocalVideoSources(lines);
}
};
/**
* Produces a single stream with multiple tracks for local video sources.
*
* @param lines
* @private
*/
NativeSimulcastSender.prototype._explodeSimulcastSenderSources = function (lines) {
var sb, msid, sid, tid, videoSources, self;
this.logger.info('Exploding local video sources...');
videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
self = this;
if (videoSources.groups && videoSources.groups.length !== 0) {
videoSources.groups.forEach(function (group) {
if (group.semantics === 'SIM') {
group.ssrcs.forEach(function (ssrc) {
// Get the msid for this ssrc..
if (self._localExplosionMap[ssrc]) {
// .. either from the explosion map..
msid = self._localExplosionMap[ssrc];
} else {
// .. or generate a new one (msid).
sid = videoSources.sources[ssrc].msid
.substring(0, videoSources.sources[ssrc].msid.indexOf(' '));
tid = self._generateGuid();
msid = [sid, tid].join(' ');
self._localExplosionMap[ssrc] = msid;
}
// Assign it to the source object.
videoSources.sources[ssrc].msid = msid;
// TODO(gp) Change the msid of associated sources.
});
}
});
}
sb = this.simulcastUtils._compileVideoSources(videoSources);
this.simulcastUtils._replaceVideoSources(lines, sb);
};
/**
* GUM for simulcast.
*
* @param constraints
* @param success
* @param err
*/
NativeSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
// There's nothing special to do for native simulcast, so just do a normal GUM.
navigator.webkitGetUserMedia(constraints, function (hqStream) {
success(hqStream);
}, err);
};
/**
* Prepares the local description for public usage (i.e. to be signaled
* through Jingle to the focus).
*
* @param desc
* @returns {RTCSessionDescription}
*/
NativeSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
var sb;
if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
return desc;
}
sb = desc.sdp.split('\r\n');
this._explodeSimulcastSenderSources(sb);
desc = new RTCSessionDescription({
type: desc.type,
sdp: sb.join('\r\n')
});
this.logger.fine(['Exploded local video sources', desc.sdp].join(' '));
return desc;
};
/**
* Ensures that the simulcast group is present in the answer, _if_ native
* simulcast is enabled,
*
* @param desc
* @returns {*}
*/
NativeSimulcastSender.prototype.transformAnswer = function (desc) {
if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
return desc;
}
var sb = desc.sdp.split('\r\n');
// Even if we have enabled native simulcasting previously
// (with a call to SLD with an appropriate SDP, for example),
// createAnswer seems to consistently generate incomplete SDP
// with missing SSRCS.
//
// So, subsequent calls to SLD will have missing SSRCS and presence
// won't have the complete list of SRCs.
this._ensureSimulcastGroup(sb);
desc = new RTCSessionDescription({
type: desc.type,
sdp: sb.join('\r\n')
});
this.logger.fine(['Transformed answer', desc.sdp].join(' '));
return desc;
};
/**
*
*
* @param desc
* @returns {*}
*/
NativeSimulcastSender.prototype.transformLocalDescription = function (desc) {
return desc;
};
NativeSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
// Nothing to do here, native simulcast does that auto-magically.
};
NativeSimulcastSender.prototype.constructor = NativeSimulcastSender;
function SimpleSimulcastSender() {
SimulcastSender.call(this);
}
SimpleSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
SimpleSimulcastSender.prototype.localStream = null;
SimpleSimulcastSender.prototype._localMaps = {
msids: [],
msid2ssrc: {}
};
/**
* Groups local video sources together in the ssrc-group:SIM group.
*
* @param lines
* @private
*/
SimpleSimulcastSender.prototype._groupLocalVideoSources = function (lines) {
var sb, videoSources, ssrcs = [], ssrc;
this.logger.info('Grouping local video sources...');
videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
for (ssrc in videoSources.sources) {
// jitsi-meet destroys/creates streams at various places causing
// the original local stream ids to change. The only thing that
// remains unchanged is the trackid.
this._localMaps.msid2ssrc[videoSources.sources[ssrc].msid.split(' ')[1]] = ssrc;
}
var self = this;
// TODO(gp) add only "free" sources.
this._localMaps.msids.forEach(function (msid) {
ssrcs.push(self._localMaps.msid2ssrc[msid]);
});
if (!videoSources.groups) {
videoSources.groups = [];
}
videoSources.groups.push({
'semantics': 'SIM',
'ssrcs': ssrcs
});
sb = this.simulcastUtils._compileVideoSources(videoSources);
this.simulcastUtils._replaceVideoSources(lines, sb);
};
/**
* GUM for simulcast.
*
* @param constraints
* @param success
* @param err
*/
SimpleSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
// TODO(gp) what if we request a resolution not supported by the hardware?
// TODO(gp) make the lq stream configurable; although this wouldn't work with native simulcast
var lqConstraints = {
audio: false,
video: {
mandatory: {
maxWidth: 320,
maxHeight: 180,
maxFrameRate: 15
}
}
};
this.logger.info('HQ constraints: ', constraints);
this.logger.info('LQ constraints: ', lqConstraints);
// NOTE(gp) if we request the lq stream first webkitGetUserMedia
// fails randomly. Tested with Chrome 37. As fippo suggested, the
// reason appears to be that Chrome only acquires the cam once and
// then downscales the picture (https://code.google.com/p/chromium/issues/detail?id=346616#c11)
var self = this;
navigator.webkitGetUserMedia(constraints, function (hqStream) {
self.localStream = hqStream;
// reset local maps.
self._localMaps.msids = [];
self._localMaps.msid2ssrc = {};
// add hq trackid to local map
self._localMaps.msids.push(hqStream.getVideoTracks()[0].id);
navigator.webkitGetUserMedia(lqConstraints, function (lqStream) {
self.displayedLocalVideoStream = lqStream;
// NOTE(gp) The specification says Array.forEach() will visit
// the array elements in numeric order, and that it doesn't
// visit elements that don't exist.
// add lq trackid to local map
self._localMaps.msids.splice(0, 0, lqStream.getVideoTracks()[0].id);
self.localStream.addTrack(lqStream.getVideoTracks()[0]);
success(self.localStream);
}, err);
}, err);
};
/**
* Prepares the local description for public usage (i.e. to be signaled
* through Jingle to the focus).
*
* @param desc
* @returns {RTCSessionDescription}
*/
SimpleSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
var sb;
if (!this.simulcastUtils.isValidDescription(desc)) {
return desc;
}
sb = desc.sdp.split('\r\n');
this._groupLocalVideoSources(sb);
desc = new RTCSessionDescription({
type: desc.type,
sdp: sb.join('\r\n')
});
this.logger.fine('Grouped local video sources');
this.logger.fine(desc.sdp);
return desc;
};
/**
* Ensures that the simulcast group is present in the answer, _if_ native
* simulcast is enabled,
*
* @param desc
* @returns {*}
*/
SimpleSimulcastSender.prototype.transformAnswer = function (desc) {
return desc;
};
/**
*
*
* @param desc
* @returns {*}
*/
SimpleSimulcastSender.prototype.transformLocalDescription = function (desc) {
var sb = desc.sdp.split('\r\n');
this.simulcastUtils._removeSimulcastGroup(sb);
desc = new RTCSessionDescription({
type: desc.type,
sdp: sb.join('\r\n')
});
this.logger.fine('Transformed local description');
this.logger.fine(desc.sdp);
return desc;
};
SimpleSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
var trackid;
var self = this;
this.logger.log(['Requested to', enabled ? 'enable' : 'disable', ssrc].join(' '));
if (Object.keys(this._localMaps.msid2ssrc).some(function (tid) {
// Search for the track id that corresponds to the ssrc
if (self._localMaps.msid2ssrc[tid] == ssrc) {
trackid = tid;
return true;
}
}) && self.localStream.getVideoTracks().some(function (track) {
// Start/stop the track that corresponds to the track id
if (track.id === trackid) {
track.enabled = enabled;
return true;
}
})) {
this.logger.log([trackid, enabled ? 'enabled' : 'disabled'].join(' '));
$(document).trigger(enabled
? 'simulcastlayerstarted'
: 'simulcastlayerstopped');
} else {
this.logger.error("I don't have a local stream with SSRC " + ssrc);
}
};
SimpleSimulcastSender.prototype.constructor = SimpleSimulcastSender;
function NoSimulcastSender() {
SimulcastSender.call(this);
}
NoSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
/**
* GUM for simulcast.
*
* @param constraints
* @param success
* @param err
*/
NoSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
navigator.webkitGetUserMedia(constraints, function (hqStream) {
success(hqStream);
}, err);
};
/**
* Prepares the local description for public usage (i.e. to be signaled
* through Jingle to the focus).
*
* @param desc
* @returns {RTCSessionDescription}
*/
NoSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
return desc;
};
/**
* Ensures that the simulcast group is present in the answer, _if_ native
* simulcast is enabled,
*
* @param desc
* @returns {*}
*/
NoSimulcastSender.prototype.transformAnswer = function (desc) {
return desc;
};
/**
*
*
* @param desc
* @returns {*}
*/
NoSimulcastSender.prototype.transformLocalDescription = function (desc) {
return desc;
};
NoSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
};
NoSimulcastSender.prototype.constructor = NoSimulcastSender;
module.exports = {
"native": NativeSimulcastSender,
"no": NoSimulcastSender
}

View File

@@ -0,0 +1,233 @@
var SimulcastLogger = require("./SimulcastLogger");
/**
*
* @constructor
*/
function SimulcastUtils() {
this.logger = new SimulcastLogger("SimulcastUtils", 1);
}
/**
*
* @type {{}}
* @private
*/
SimulcastUtils.prototype._emptyCompoundIndex = {};
/**
*
* @param lines
* @param videoSources
* @private
*/
SimulcastUtils.prototype._replaceVideoSources = function (lines, videoSources) {
var i, inVideo = false, index = -1, howMany = 0;
this.logger.info('Replacing video sources...');
for (i = 0; i < lines.length; i++) {
if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
// Out of video.
break;
}
if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
// In video.
inVideo = true;
}
if (inVideo && (lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:'
|| lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:')) {
if (index === -1) {
index = i;
}
howMany++;
}
}
// efficiency baby ;)
lines.splice.apply(lines,
[index, howMany].concat(videoSources));
};
SimulcastUtils.prototype.isValidDescription = function (desc)
{
return desc && desc != null
&& desc.type && desc.type != ''
&& desc.sdp && desc.sdp != '';
};
SimulcastUtils.prototype._getVideoSources = function (lines) {
var i, inVideo = false, sb = [];
this.logger.info('Getting video sources...');
for (i = 0; i < lines.length; i++) {
if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
// Out of video.
break;
}
if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
// In video.
inVideo = true;
}
if (inVideo && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
// In SSRC.
sb.push(lines[i]);
}
if (inVideo && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
sb.push(lines[i]);
}
}
return sb;
};
SimulcastUtils.prototype.parseMedia = function (lines, mediatypes) {
var i, res = [], type, cur_media, idx, ssrcs, cur_ssrc, ssrc,
ssrc_attribute, group, semantics, skip = true;
this.logger.info('Parsing media sources...');
for (i = 0; i < lines.length; i++) {
if (lines[i].substring(0, 'm='.length) === 'm=') {
type = lines[i]
.substr('m='.length, lines[i].indexOf(' ') - 'm='.length);
skip = mediatypes !== undefined && mediatypes.indexOf(type) === -1;
if (!skip) {
cur_media = {
'type': type,
'sources': {},
'groups': []
};
res.push(cur_media);
}
} else if (!skip && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
idx = lines[i].indexOf(' ');
ssrc = lines[i].substring('a=ssrc:'.length, idx);
if (cur_media.sources[ssrc] === undefined) {
cur_ssrc = {'ssrc': ssrc};
cur_media.sources[ssrc] = cur_ssrc;
}
ssrc_attribute = lines[i].substr(idx + 1).split(':', 2)[0];
cur_ssrc[ssrc_attribute] = lines[i].substr(idx + 1).split(':', 2)[1];
if (cur_media.base === undefined) {
cur_media.base = cur_ssrc;
}
} else if (!skip && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
idx = lines[i].indexOf(' ');
semantics = lines[i].substr(0, idx).substr('a=ssrc-group:'.length);
ssrcs = lines[i].substr(idx).trim().split(' ');
group = {
'semantics': semantics,
'ssrcs': ssrcs
};
cur_media.groups.push(group);
} else if (!skip && (lines[i].substring(0, 'a=sendrecv'.length) === 'a=sendrecv' ||
lines[i].substring(0, 'a=recvonly'.length) === 'a=recvonly' ||
lines[i].substring(0, 'a=sendonly'.length) === 'a=sendonly' ||
lines[i].substring(0, 'a=inactive'.length) === 'a=inactive')) {
cur_media.direction = lines[i].substring('a='.length);
}
}
return res;
};
/**
* The _indexOfArray() method returns the first a CompoundIndex at which a
* given element can be found in the array, or _emptyCompoundIndex if it is
* not present.
*
* Example:
*
* _indexOfArray('3', [ 'this is line 1', 'this is line 2', 'this is line 3' ])
*
* returns {row: 2, column: 14}
*
* @param needle
* @param haystack
* @param start
* @returns {}
* @private
*/
SimulcastUtils.prototype._indexOfArray = function (needle, haystack, start) {
var length = haystack.length, idx, i;
if (!start) {
start = 0;
}
for (i = start; i < length; i++) {
idx = haystack[i].indexOf(needle);
if (idx !== -1) {
return {row: i, column: idx};
}
}
return this._emptyCompoundIndex;
};
SimulcastUtils.prototype._removeSimulcastGroup = function (lines) {
var i;
for (i = lines.length - 1; i >= 0; i--) {
if (lines[i].indexOf('a=ssrc-group:SIM') !== -1) {
lines.splice(i, 1);
}
}
};
SimulcastUtils.prototype._compileVideoSources = function (videoSources) {
var sb = [], ssrc, addedSSRCs = [];
this.logger.info('Compiling video sources...');
// Add the groups
if (videoSources.groups && videoSources.groups.length !== 0) {
videoSources.groups.forEach(function (group) {
if (group.ssrcs && group.ssrcs.length !== 0) {
sb.push([['a=ssrc-group:', group.semantics].join(''), group.ssrcs.join(' ')].join(' '));
// if (group.semantics !== 'SIM') {
group.ssrcs.forEach(function (ssrc) {
addedSSRCs.push(ssrc);
sb.splice.apply(sb, [sb.length, 0].concat([
["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
});
//}
}
});
}
// Then add any free sources.
if (videoSources.sources) {
for (ssrc in videoSources.sources) {
if (addedSSRCs.indexOf(ssrc) === -1) {
sb.splice.apply(sb, [sb.length, 0].concat([
["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
}
}
}
return sb;
};
module.exports = SimulcastUtils;

View File

@@ -0,0 +1,202 @@
/*jslint plusplus: true */
/*jslint nomen: true*/
var SimulcastSender = require("./SimulcastSender");
var NoSimulcastSender = SimulcastSender["no"];
var NativeSimulcastSender = SimulcastSender["native"];
var SimulcastReceiver = require("./SimulcastReceiver");
var SimulcastUtils = require("./SimulcastUtils");
var RTCEvents = require("../../service/RTC/RTCEvents");
/**
*
* @constructor
*/
function SimulcastManager() {
// Create the simulcast utilities.
this.simulcastUtils = new SimulcastUtils();
// Create remote simulcast.
this.simulcastReceiver = new SimulcastReceiver();
// Initialize local simulcast.
// TODO(gp) move into SimulcastManager.prototype.getUserMedia and take into
// account constraints.
if (!config.enableSimulcast) {
this.simulcastSender = new NoSimulcastSender();
} else {
var isChromium = window.chrome,
vendorName = window.navigator.vendor;
if(isChromium !== null && isChromium !== undefined
/* skip opera */
&& vendorName === "Google Inc."
/* skip Chromium as suggested by fippo */
&& !window.navigator.appVersion.match(/Chromium\//) ) {
var ver = parseInt(window.navigator.appVersion.match(/Chrome\/(\d+)\./)[1], 10);
if (ver > 37) {
this.simulcastSender = new NativeSimulcastSender();
} else {
this.simulcastSender = new NoSimulcastSender();
}
} else {
this.simulcastSender = new NoSimulcastSender();
}
}
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);
});
}
/**
* Restores the simulcast groups of the remote description. In
* transformRemoteDescription we remove those in order for the set remote
* description to succeed. The focus needs the signal the groups to new
* participants.
*
* @param desc
* @returns {*}
*/
SimulcastManager.prototype.reverseTransformRemoteDescription = function (desc) {
return this.simulcastReceiver.reverseTransformRemoteDescription(desc);
};
/**
* Removes the ssrc-group:SIM from the remote description bacause Chrome
* either gets confused and thinks this is an FID group or, if an FID group
* is already present, it fails to set the remote description.
*
* @param desc
* @returns {*}
*/
SimulcastManager.prototype.transformRemoteDescription = function (desc) {
return this.simulcastReceiver.transformRemoteDescription(desc);
};
/**
* Gets the fully qualified msid (stream.id + track.id) associated to the
* SSRC.
*
* @param ssrc
* @returns {*}
*/
SimulcastManager.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
return this.simulcastReceiver.getRemoteVideoStreamIdBySSRC(ssrc);
};
/**
* Returns a stream with single video track, the one currently being
* received by this endpoint.
*
* @param stream the remote simulcast stream.
* @returns {webkitMediaStream}
*/
SimulcastManager.prototype.getReceivingVideoStream = function (stream) {
return this.simulcastReceiver.getReceivingVideoStream(stream);
};
/**
*
*
* @param desc
* @returns {*}
*/
SimulcastManager.prototype.transformLocalDescription = function (desc) {
return this.simulcastSender.transformLocalDescription(desc);
};
/**
*
* @returns {*}
*/
SimulcastManager.prototype.getLocalVideoStream = function() {
return this.simulcastSender.getLocalVideoStream();
};
/**
* GUM for simulcast.
*
* @param constraints
* @param success
* @param err
*/
SimulcastManager.prototype.getUserMedia = function (constraints, success, err) {
this.simulcastSender.getUserMedia(constraints, success, err);
};
/**
* Prepares the local description for public usage (i.e. to be signaled
* through Jingle to the focus).
*
* @param desc
* @returns {RTCSessionDescription}
*/
SimulcastManager.prototype.reverseTransformLocalDescription = function (desc) {
return this.simulcastSender.reverseTransformLocalDescription(desc);
};
/**
* Ensures that the simulcast group is present in the answer, _if_ native
* simulcast is enabled,
*
* @param desc
* @returns {*}
*/
SimulcastManager.prototype.transformAnswer = function (desc) {
return this.simulcastSender.transformAnswer(desc);
};
SimulcastManager.prototype.getReceivingSSRC = function (jid) {
return this.simulcastReceiver.getReceivingSSRC(jid);
};
SimulcastManager.prototype.getReceivingVideoStreamBySSRC = function (msid) {
return this.simulcastReceiver.getReceivingVideoStreamBySSRC(msid);
};
/**
*
* @param lines
* @param mediatypes
* @returns {*}
*/
SimulcastManager.prototype.parseMedia = function(lines, mediatypes) {
var sb = lines.sdp.split('\r\n');
return this.simulcastUtils.parseMedia(sb, mediatypes);
};
SimulcastManager.prototype._setReceivingVideoStream = function(resource, ssrc) {
this.simulcastReceiver._setReceivingVideoStream(resource, ssrc);
};
SimulcastManager.prototype._setLocalVideoStreamEnabled = function(ssrc, enabled) {
this.simulcastSender._setLocalVideoStreamEnabled(ssrc, enabled);
};
SimulcastManager.prototype.resetSender = function() {
if (typeof this.simulcastSender.reset === 'function'){
this.simulcastSender.reset();
}
};
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;

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