Compare commits

...

87 Commits
445 ... 511

Author SHA1 Message Date
gpolitis
b0d2a79873 Require FF 40 or above for a good experience. 2015-05-28 11:45:10 +02:00
gpolitis
d94f001f25 A recent JDK and Ant is required to compile JICOFO. 2015-05-27 20:56:10 +02:00
gpolitis
afb85e2fd9 These are Debian instructions really. 2015-05-27 20:49:26 +02:00
gpolitis
fffb5801c5 Makes some nginx directives optional. 2015-05-27 16:56:30 +02:00
gpolitis
d81cd20ee6 Removes otalk modules from the installation procedure. 2015-05-27 16:52:16 +02:00
gpolitis
3e7a9228bc No turn server is necessary with Jitsi Videobridge. 2015-05-27 16:22:16 +02:00
hristoterezov
835e199135 Fixes issue with not removed ssrcs after the participant left the room. 2015-05-27 11:47:06 +03:00
hristoterezov
1d660e1883 Removes join / leave notifications if the participant start audio muted. 2015-05-26 14:18:45 +03:00
hristoterezov
5746261961 Implements the functionality to update config.js parameters via the URL. 2015-05-25 17:42:59 +03:00
hristo
cbeae8eb30 Commit from translate.jitsi.org by user hristo.: 158 of 159 strings translated (0 fuzzy). 2015-05-25 13:12:58 +00:00
George Politis
95b2752d2a Updates libs/app.bundle.js 2015-05-21 10:32:55 +02:00
George Politis
e3da472e7a Adds special handling of recvonly remote streams. 2015-05-21 10:32:00 +02:00
George Politis
43f60ca336 Updates app.bundle.js 2015-05-20 16:27:39 +02:00
George Politis
118a61c416 Disables stats logging in FF. 2015-05-20 16:27:10 +02:00
George Politis
bf99a129bd Depend on sdp-interop@0.1.4 2015-05-20 16:20:31 +02:00
George Politis
fb6ad8cffd Updates app.bundle.js and restores latest sdp-interop. 2015-05-20 15:55:30 +02:00
hristoterezov
21fef57bc4 Adds config property that disables hiding of toolbar. 2015-05-20 15:10:09 +03:00
George Politis
777422c87d Update libs/app.bundle.js. 2015-05-20 14:03:43 +02:00
George Politis
ee6fd63c25 Depend on sdp-interop@0.1.3 2015-05-20 14:03:13 +02:00
George Politis
b9f00b71b2 Fixes conference disposal in FF. 2015-05-20 10:45:00 +02:00
hristoterezov
099e3340bc Implements start muted feature. 2015-05-19 18:03:01 +03:00
George Politis
172c2d3d71 Updates app.bundle.js. 2015-05-18 19:08:20 +02:00
George Politis
854c8e5f2f Removes obsolete/unused variable. 2015-05-18 19:07:56 +02:00
George Politis
b2cff193a9 Updates app.bundle.js. 2015-05-18 19:03:27 +02:00
George Politis
ad1772178d Notifies the focus about newly allocated channel SSRCs on add/remove source. 2015-05-18 19:00:12 +02:00
George Politis
0959b3d5b8 Updates app.bundle.js 2015-05-15 15:43:56 +02:00
George Politis
36f91f7f1e Disables RTP stats when running on FF. 2015-05-15 15:36:29 +02:00
George Politis
2c9d0606c3 Attempts to fix #258 2015-05-15 15:32:01 +02:00
George Politis
1ce22fb8c9 Depend on sdp-interop@0.1.2, sdp-transform@1.4.0 2015-05-15 13:08:04 +02:00
George Politis
e0cba855a6 Implements fault tolerant connect (closes #268). 2015-04-22 23:11:25 +02:00
paweldomas
8af3a65d37 Displays error dialog when BOSH connection fails. 2015-04-22 16:14:16 +02:00
hristoterezov
667f67376e Fixes the issue with black large video. 2015-04-22 15:53:01 +03:00
George Politis
ce7b6be024 Bumps app.bundle.js version. 2015-04-22 14:30:24 +02:00
George Politis
57cd2647f3 Fixes a JS error in the invite prompt when there's no room url. 2015-04-22 14:26:48 +02:00
hristoterezov
efcfe99707 Improves the notification that informs the user if started the conference audio/video muted. 2015-04-22 13:27:14 +03:00
hristoterezov
cc1ad1bc13 Implement the functionality that allows users to start the conversation muted. 2015-04-22 12:31:08 +03:00
Paweł Domas
29f06829e7 Merge pull request #272 from mkeesey/master
Fix stream end/start race condition
2015-04-16 07:54:23 +02:00
Mike Keesey
0fdf5e0102 Fix stream end/start race condition
We need to queue attempts to call modifySources to prevent errors in
setLocalDescription, et al. We need to let the asynchronous function
flow in modifySources finish before we kick off another set.
2015-04-15 08:46:27 -06:00
hristoterezov
5b7083f5f7 Fixes JS error when downloading logs. 2015-04-15 11:17:01 +03:00
Boris Grozev
adb1c572ed Rebuilds app.bundle.js. 2015-04-12 14:26:58 +02:00
Boris Grozev
5d17cd0bcc Merge branch 'dtmf' 2015-04-12 14:24:33 +02:00
Boris Grozev
134d89a3d6 Fix a problem with accessing peerconnection, use duration and pause in the API. 2015-04-12 14:23:54 +02:00
Boris Grozev
0efcbdcd37 Adds a MemberList module and (currently disabled) code to show the dialpad button. 2015-04-12 14:18:24 +02:00
hristoterezov
878713a15d Fixes typo for the user id node in the presence. 2015-04-09 16:57:32 +03:00
hristoterezov
e01713f6f8 Fixes remove video element functionality in UI to remove only the correct video instead of all. 2015-04-09 14:02:33 +03:00
paweldomas
b6155c04ad Modifies "authentication required" dialog. 2015-04-08 14:34:10 +02:00
Boris Grozev
8075d0a0fd Adds a (hidden) dialpad button to the toolbar. 2015-04-08 12:51:29 +02:00
Boris Grozev
029851fe3f Avoid unnecessary jquery calls. 2015-04-08 12:49:49 +02:00
Boris Grozev
886fb2ac43 Fix typos and rename event names for purposes of clarity. 2015-04-08 12:30:48 +02:00
Boris Grozev
99b1a51df0 Fix a typo. 2015-04-08 10:43:43 +02:00
Boris Grozev
795ec24246 Adds a module for sending DTMF tones. 2015-04-07 18:02:52 +02:00
Boris Grozev
ecf9c6fc6b Adds instructions for adding an icon (thanks @hristoterezov). 2015-04-07 17:38:30 +02:00
Boris Grozev
68bc819b89 Adds the dial pad icon. 2015-04-07 17:09:28 +02:00
George Politis
80c5779de9 Adds an SDP transform module. 2015-04-03 13:18:09 +02:00
paweldomas
d175dfdef7 Prevents from sending invalid presence packets, before MUC jid is defined. 2015-04-01 21:12:08 +02:00
hristo
93c13f5a11 Commit from translate.jitsi.org by user hristo.: 156 of 159 strings translated (0 fuzzy). 2015-04-01 13:13:01 +00:00
hristo
ff8b880948 Commit from translate.jitsi.org by user hristo.: 140 of 159 strings translated (0 fuzzy). 2015-04-01 12:39:44 +00:00
hristo
5b550c8a5b Commit from translate.jitsi.org by user hristo.: 113 of 159 strings translated (0 fuzzy). 2015-03-31 17:02:39 +00:00
hristo
ce7d3c5c81 Commit from translate.jitsi.org by user hristo.: 91 of 159 strings translated (0 fuzzy). 2015-03-31 15:10:41 +00:00
jitsi-pootle
c99350308c New files added from translate.jitsi.org based on templates 2015-03-31 11:35:10 +00:00
George Politis
e98c8ada6a Nukes the enableFirefoxSupport config param. 2015-03-31 13:02:59 +02:00
George Politis
ce8aa961ea Lowers FF requirement to v38. 2015-03-31 12:33:04 +02:00
paweldomas
fbd08ba3a6 Adds empty SSI templates to avoid 404. 2015-03-31 11:17:26 +02:00
Paweł Domas
61594cb877 Merge pull request #259 from jitsi/plugin_ssi_includes
Adds additional SSI include tags.
2015-03-31 08:38:01 +02:00
hristoterezov
520e655100 Fixes issue with video mute indicator. 2015-03-30 17:19:35 +03:00
paweldomas
58d1697b00 Adds additional SSI include tags. 2015-03-30 16:00:23 +02:00
hristoterezov
f902b99287 Adds documentation for the translation. 2015-03-30 16:11:16 +03:00
hristoterezov
d25a9b0e41 Fixes issue with desktop sharing when the user click Cancel on the popup window. 2015-03-27 16:23:48 +02:00
hristoterezov
0e0f7d7ccb Fixes issue with available devices icons 2015-03-27 15:56:17 +02:00
hristoterezov
58cc21d417 Changes the implementation to show availability of video and sound devices. 2015-03-27 11:36:39 +02:00
paweldomas
8ac44491d0 Fixes input field focus in call SIP number dialog. 2015-03-26 14:16:20 +01:00
Damian Minkov
a093b455b3 Fixes debian packaging checking wrong template. 2015-03-26 13:19:50 +02:00
George Politis
58494d45db Brings back goog-remb signaling. 2015-03-26 11:29:40 +01:00
paweldomas
f98621173f Fixes uid, email and displayName advertisement in MUC presence. 2015-03-25 12:39:22 +01:00
hristoterezov
dbcfc92dc4 Changes the implementation to allow users without audio and video to join the conferences. Fixes issue with switching off desktop sharing for audio only users. 2015-03-24 17:43:33 +02:00
ibauersachs
b9bd1d599b Commit from translate.jitsi.org by user ibauersachs.: 159 of 159 strings translated (0 fuzzy). 2015-03-24 15:35:05 +00:00
ibauersachs
99b0be91ed Commit from translate.jitsi.org by user ibauersachs.: 159 of 159 strings translated (0 fuzzy). 2015-03-24 13:50:48 +00:00
Ingo Bauersachs
f2ae29d8e4 Add Italian 2015-03-24 14:47:56 +01:00
jitsi-pootle
4c3d415a07 New files added from translate.jitsi.org based on templates 2015-03-24 13:21:29 +00:00
Ingo Bauersachs
7b65798758 Use SSL for the Jitsi homepage link 2015-03-24 10:36:27 +01:00
hristoterezov
c1c5a305c6 Removes the comment from the English translation json. 2015-03-24 11:17:51 +02:00
hristo
291211c029 Commit from translate.jitsi.org by user hristo.: 13 of 149 strings translated (0 fuzzy). 2015-03-23 16:40:26 +00:00
hristoterezov
a3a9e8d951 Adds comment about not changing or creating other language files manually. 2015-03-23 18:35:44 +02:00
hristoterezov
3a0ee11ccd Turns off the camera when video is muted on https connection. 2015-03-23 18:12:24 +02:00
George Politis
2568b07075 enables REMB signaling in the rembson room. 2015-03-23 10:17:02 +01:00
Boris Grozev
e5fa02a1d4 Automates bumping js file versions. Experimental, please revert Makefile
if it causes problems.
2015-03-21 20:04:12 -07:00
Damian Minkov
fb5550bc38 Fixes debian package watch file. 2015-03-21 16:48:53 +02:00
64 changed files with 5623 additions and 1549 deletions

3
.gitignore vendored
View File

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

View File

@@ -21,4 +21,4 @@ clean:
@rm -f $(OUTPUT_DIR)/*.bundle.js
deploy:
@mkdir -p $(DEPLOY_DIR) && cp $(OUTPUT_DIR)/*.bundle.js $(DEPLOY_DIR)
@mkdir -p $(DEPLOY_DIR) && cp $(OUTPUT_DIR)/*.bundle.js $(DEPLOY_DIR) && ./bump-js-versions.sh

5
app.js
View File

@@ -15,6 +15,8 @@ var APP =
this.keyboardshortcut = require("./modules/keyboardshortcut/keyboardshortcut");
this.translation = require("./modules/translation/translation");
this.settings = require("./modules/settings/Settings");
this.DTMF = require("./modules/DTMF/DTMF");
this.members = require("./modules/members/MemberList");
}
};
@@ -29,11 +31,14 @@ function init() {
APP.desktopsharing.init();
APP.keyboardshortcut.init();
APP.members.start();
}
$(document).ready(function () {
var URLPRocessor = require("./modules/URLProcessor/URLProcessor");
URLPRocessor.setConfigParametersFromUrl();
APP.init();
APP.translation.init();

25
bump-js-versions.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/sh
# This script finds all js files included from index.html which have been
# modified and bumps their version (the value of the "v" parameter used
# in index.html)
# contents of index.html at HEAD (excluding not-committed changes)
index=`git show HEAD:index.html`
# js files included from index.html. The sort needed for comm
jsfiles=.bump-js-versions-jsfiles.tmp
echo "$index" | grep '<script src=".*"' -o | sed -e 's/<script src="//' | sed -e 's/\?.*//' | tr -d \" | sort > $jsfiles
# modified files since the last commit
gitmodified=.bump-js-versions-gitmodified.tmp
git ls-files -m | sort > $gitmodified
for file in `comm -12 $jsfiles $gitmodified` ;do
old_version=`echo "$index" | grep "<script src=\"${file}?v=[0-9]*" -o | sed -e 's/.*v=//'| tr -d \"`
new_version=$((1+$old_version))
echo Bumping version of $file from $old_version to $new_version
sed -i.tmp -e "s%script src=\"${file}\?v=.*\"%script src=\"$file?v=$new_version\"%" index.html
done
rm -f $jsfiles $gitmodified index.html.tmp

View File

@@ -26,13 +26,11 @@ var config = {
channelLastN: -1, // The default value of the channel attribute last-n.
adaptiveLastN: false,
adaptiveSimulcast: false,
useRtcpMux: true,
useBundle: true,
useRtcpMux: true, // required for FF support
useBundle: true, // required for FF support
enableRecording: false,
enableWelcomePage: true,
enableSimulcast: false,
enableFirefoxSupport: true, //firefox support is still experimental and
// will work when simulcast is *disabled* and rtcpMux & bundle are *enabled*.
enableSimulcast: false, // blocks FF support
logStats: false, // Enable logging of PeerConnection stats via the focus
/*noticeMessage: 'Service update is scheduled for 16th March 2015. ' +
'During that time service will not be available. ' +

View File

@@ -119,4 +119,8 @@
.icon-settings:before {
content: "\e61b";
}
}
.icon-dialPad:before {
content: "\e61c";
}

View File

@@ -47,3 +47,21 @@
#languages_selectbox{
height: 40px;
}
#startMutedOptions {
padding-left: 10%;
text-indent: -10%;
}
#startAudioMuted {
width: 13px !important;
}
#startVideoMuted {
width: 13px !important;
}
.startMutedLabel {
float: left;
}

View File

@@ -415,3 +415,28 @@
left: 35px;
border-radius: 200px;
}
.noMic {
position: absolute;
border-radius: 8px;
z-index: 1;
width: 100%;
height: 100%;
background-image: url("../images/noMic.png");
background-color: #000;
background-repeat: no-repeat;
background-position: center;
}
.noVideo {
position: absolute;
border-radius: 8px;
z-index: 1;
width: 100%;
height: 100%;
background-image: url("../images/noVideo.png");
background-color: #000;
background-repeat: no-repeat;
background-position: center;
}

View File

@@ -29,7 +29,7 @@ case "$1" in
. /usr/share/debconf/confmodule
# detect dpkg-reconfigure, just delete old links
db_get jitsi-meet/jvb-hostname
db_get jitsi-meet-prosody/jvb-hostname
JVB_HOSTNAME_OLD=$RET
if [ -n "$RET" ] && [ ! "$JVB_HOSTNAME_OLD" = "$JVB_HOSTNAME" ] ; then
rm -f /etc/prosody/conf.d/$JVB_HOSTNAME_OLD.cfg.lua

2
debian/watch vendored
View File

@@ -1,2 +1,2 @@
version=3
https://github.com/jitsi/jitsi-meet/releases/ /jitsi/jitsi-meet/archive/(\S+)\.tar\.gz
opts="uversionmangle=s/^/1.0./" https://github.com/jitsi/jitsi-meet/releases/ /jitsi/jitsi-meet/archive/(\S+)\.tar\.gz

12
doc/adding-an-icon.md Normal file
View File

@@ -0,0 +1,12 @@
### Adding an icon to the font file (e.g. for the floating menu)
1. Go to https://icomoon.io/app/
2. Go to "Manage Projects" from the menu on the top left.
3. Use "Import project" and select <code>fonts/selection.json</code> from Jitsi Meet.
4. Import icons (e.g. svg files) using the "import items" button.
5. Go to "generate font" and make sure the identifiers for the new icons are correct.
6. Download the result in a zip file using the "download" button.
7. Copy <code>selection.json</code> and <code>fonts/jitsi.*</code> from the zip file to <code>fonts/</code> in Jitsi Meet
8. Copy the class for the new icon from <code>style.css</code> in the zip file to <code>css/font.css</code> in Jitsi Meet (do *not* copy the whole file)
Sample commit: https://github.com/jitsi/jitsi-meet/commit/68bc819b89aec12364fcf07b81efa83a1900eed6

View File

@@ -1,12 +1,12 @@
# Server Installation for Jitsi Meet
This describes configuring a server `jitsi.example.com`. You will need to
This describes configuring a server `jitsi.example.com` running Debian or a Debian Derivative. You will need to
change references to that to match your host, and generate some passwords for
`YOURSECRET1`, `YOURSECRET2`, `YOURSECRET3` and `YOURSECRET4`.
There are also some complete [example config files](https://github.com/jitsi/jitsi-meet/tree/master/doc/example-config-files/) available, mentioned in each section.
## Install prosody and otalk modules
## Install prosody
```sh
apt-get install lsb-release
echo deb http://packages.prosody.im/debian $(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list
@@ -14,9 +14,6 @@ wget --no-check-certificate https://prosody.im/files/prosody-debian-packages.key
apt-get update
apt-get install prosody-trunk
apt-get install git lua-zlib lua-sec-prosody lua-dbi-sqlite3 liblua5.1-bitop-dev liblua5.1-bitop0
git clone https://github.com/andyet/otalk-server.git
cd otalk-server
cp -r mod* /usr/lib/prosody/modules
```
## Configure prosody
@@ -84,7 +81,7 @@ prosodyctl restart
apt-get install nginx
```
Add nginx config for domain in `/etc/nginx/nginx.conf`:
Optionally, add nginx config for domain in `/etc/nginx/nginx.conf`:
```
tcp_nopush on;
types_hash_max_size 2048;
@@ -143,6 +140,8 @@ Install JRE if missing:
apt-get install default-jre
```
_NOTE: When installing on older Debian releases keep in mind that you need JRE >= 1.7._
In the user home that will be starting Jitsi Videobridge create `.sip-communicator` folder and add the file `sip-communicator.properties` with one line in it:
```
org.jitsi.impl.neomedia.transform.srtp.SRTPCryptoContext.checkReplay=false
@@ -157,7 +156,15 @@ Or autostart it by adding the line in `/etc/rc.local`:
/bin/bash /root/jitsi-videobridge-linux-{arch-buildnum}/jvb.sh --host=localhost --domain=jitsi.example.com --port=5347 --secret=YOURSECRET1 </dev/null >> /var/log/jvb.log 2>&1
```
## Install Jitsi Conference Focus(jicofo)
## Install Jitsi Conference Focus (jicofo)
Install JDK and Ant if missing:
```
apt-get install default-jdk ant
```
_NOTE: When installing on older Debian releases keep in mind that you need JDK >= 1.7._
Clone source from Github repo:
```sh
git clone https://github.com/jitsi/jicofo.git
@@ -202,54 +209,6 @@ Restart nginx to get the new configuration:
invoke-rc.d nginx restart
```
## Install [Turn server](https://github.com/andyet/otalk-server/tree/master/restund)
```sh
apt-get install make gcc
wget http://creytiv.com/pub/re-0.4.7.tar.gz
tar zxvf re-0.4.7.tar.gz
ln -s re-0.4.7 re
cd re-0.4.7
sudo make install PREFIX=/usr
cd ..
wget http://creytiv.com/pub/restund-0.4.2.tar.gz
wget https://raw.github.com/andyet/otalk-server/master/restund/restund-auth.patch
tar zxvf restund-0.4.2.tar.gz
cd restund-0.4.2/
patch -p1 < ../restund-auth.patch
sudo make install PREFIX=/usr
cp debian/restund.init /etc/init.d/restund
chmod +x /etc/init.d/restund
cd /etc
wget https://raw.github.com/andyet/otalk-server/master/restund/restund.conf
```
Configure addresses and ports as desired, and the password to be configured in prosody:
```
realm jitsi.example.com
# share this with your prosody server
auth_shared YOURSECRET4
# modules
module_path /usr/lib/restund/modules
turn_relay_addr [turn ip address]
```
Configure prosody to use it in `/etc/prosody/prosody.cfg.lua`. Add to your virtual host:
```
turncredentials_secret = "YOURSECRET4";
turncredentials = {
{ type = "turn", host = "turn.address.ip.configured", port = 3478, transport = "tcp" }
}
```
Add turncredentials module in the "modules_enabled" section
Reload prosody if needed
```
prosodyctl restart
```
## Running behind NAT
In case of videobridge being installed on a machine behind NAT, add the following extra lines to the file `~/.sip-communicator/sip-communicator.properties` (in the home of user running the videobridge):
```

Binary file not shown.

View File

@@ -35,4 +35,5 @@
<glyph unicode="&#xe619;" d="M46.993 961.7c461.234 0 553.793 0 1015.024 0 35.919 0 53.356-25.959 53.356-57.959-0.581-303.259-0.325-606.488-0.449-909.809 0-43.984-13.203-57.058-57.703-57.058-443.072-0.126-556.453-0.126-999.553 0-44.534 0-57.799 13.009-57.799 57.058-0.098 303.257 0.485 608.072-0.093 911.329-0.034 26.21 11.301 53.761 47.217 56.439zM311.405 450.298c0-119.045-0.072-172.168 0.057-291.249 0.036-50.043 11.208-61.050 62.12-61.050 233.352 0 137.075 0 370.522 0 47.075 0 59.249 11.982 59.249 58.095 0.126 239.111 0.126 346.338 0 585.389 0 48.138-10.687 58.991-57.768 58.991-235.323 0.101-140.844 0.101-376.157 0-47.044 0-57.93-11.043-57.966-58.89-0.129-119.109-0.057-172.209-0.057-291.287zM153.944 838.566c-74.929-0.062-66.687 5.958-66.845-66.685-0.201-63.95-7.054-63.534 62.528-63.372 72.999 0.194 67.201-3.764 67.302 67.554 0 67.722 4.087 62.595-62.985 62.502zM963.644 838.566c-71.159-0.034-65.56 6.185-65.751-65.364-0.129-67.302-4.508-64.693 64.528-64.693 73.089 0 65.299-2.031 65.299 66.238-0.003 68.646 6.956 63.911-64.076 63.818zM216.828 122.408c0.359 73.094 4.639 66.914-67.358 67.17-68.104 0.191-62.569 2.763-62.407-63.31 0.129-73.476-6.954-66.52 67.074-66.649 66.042-0.065 63.142-6.056 62.691 62.789zM1027.718 124.4c0.134 68.334 6.443 65.304-63.297 65.178-70.132-0.132-66.656 5.793-66.527-65.304 0.129-70.645-4.384-64.721 63.756-64.657 71.995 0.132 66.202-6.698 66.068 64.783zM1027.718 342.077c0 70.55 7.219 66.842-67.485 66.522-0.898 0-1.873 0-2.838 0-59.375 0-59.375 0-59.375-58.023 0-77.922-6.443-69.936 69.293-70.196 66.076-0.387 60.539-3.091 60.405 61.697zM151.307 489.873c68.295-0.163 65.815-5.568 65.624 62.982-0.194 71.128 4.895 64.917-66.014 65.010-69.905 0.101-63.813 4.704-63.885-63.978-0.062-67.431-5.7-64.463 64.275-64.014zM961.263 489.873c72.511-0.258 66.589-4.603 66.455 64.494 0 68.558 6.185 63.537-64.267 63.498-70.196-0.028-65.686 6.053-65.498-65.493 0.132-62.5 0.067-62.5 63.31-62.5zM150.399 280.38c71.004 0 66.659-6.567 66.466 64.528-0.163 63.694-0.036 63.501-65.013 63.756-70.805 0.258-64.822 2.673-64.822-63.756 0.036-69.167-5.919-64.788 63.369-64.528z" horiz-adv-x="1115" />
<glyph unicode="&#xe61a;" d="M3.881 146.835h220.26v-210.835h-220.26v210.835zM308.817 350.143h220.27v-414.143h-220.27v414.143zM613.764 553.412h220.268v-617.412h-220.268v617.412zM918.685 756.715h220.265v-820.715h-220.265v820.715zM1223.629 960h220.263v-1024h-220.263v1024z" horiz-adv-x="1444" />
<glyph unicode="&#xe61b;" d="M526.071 234.749c-28.637-30.869-56.465-60.861-84.282-90.859-51.578-55.636-103.047-111.376-154.842-166.832-7.606-8.135-15.958-16.1-25.317-22.012-28.075-17.708-58.31-18.090-88.472-6.492-59.84 23.028-80.004 90.727-59.734 139.234 5.413 12.95 13.721 23.601 23.709 33.173 70.256 67.351 140.506 134.717 210.76 202.077 15.638 14.993 31.264 29.995 47.364 45.45-9.302 9.529-18.386 18.833-27.451 28.137-12.122 12.442-13.234 20.28-5.067 35.498 4.735 8.816 4.789 8.878-2.627 16.198-20.012 19.72-40.168 39.198-63.498 55.188-27.167 18.624-57.161 24.233-89.083 19.849-53.402-7.328-91.609-38.372-121.413-81.046-12.774-18.299-15.365-40.313-17.517-61.875-3.23-32.245-2.415-64.479 2.209-96.597 1.654-11.515-3.863-16.539-13.835-11.175-8.306 4.448-16.095 11.048-22.115 18.353-15.574 18.89-22.223 42.042-27.474 65.395-12.955 57.652-8.86 114.49 12.191 169.495 32.345 84.537 79.743 159.571 145.953 221.932 13.659 12.857 176.841 180.564 202.944 207.021 7.493 7.599 14.895 7.635 22.393 0.028 43.009-43.641 85.985-87.316 128.927-131.029 8.117-8.267 8.019-15.097-0.222-23.49-26.339-26.834-52.726-53.627-79.106-80.419-6.244-6.334-97.34-82.437-73.027-128.816 22.693-25.090 46.196-49.449 69.575-73.904 1.189-1.238 4.686-1.386 6.523-0.632 3.63 1.499 6.848 3.997 10.248 6.066 9.745 5.94 19.545 4.918 27.812-3.083 11.755-11.381 23.405-22.858 35.392-34.59 4.807 4.575 9.939 9.41 15.027 14.294 27.128 26.039 54.272 52.071 81.351 78.146 16.413 15.778 18.652 28.418 11.038 49.658-10.473 29.221-14.356 59.677-13.85 90.624 1.017 61.045 20.438 115.334 61.003 161.416 32.825 37.286 72.054 64.311 121.643 74.325 35.227 7.101 69.139 4.513 100.663-14.026 6.365-3.752 11.908-9.007 17.455-14.005 3.491-3.125 3.153-6.236-0.565-9.98-42.503-42.885-84.772-86.013-127.154-129.035-12.442-12.638-12.356-23.167 0.196-35.914 40.344-40.978 80.597-82.050 120.936-123.052 10.076-10.233 19.537-10.021 29.504 0.134 43.195 44.077 86.449 88.090 129.706 132.118 1.21 1.233 2.572 2.322 5.135 4.624 5.491-5.893 11.895-10.924 15.961-17.406 19.452-30.944 22.608-64.83 17.073-100.25-14.253-91.080-97.188-175.638-197.712-190.123-39.977-5.764-79.372-2.562-118.067 9.031-5.898 1.775-11.541 4.629-17.538 5.829-12.47 2.474-23.872-0.366-32.74-9.877-30.921-33.168-61.674-66.484-92.474-99.758-0.73-0.805-1.349-1.718-0.181-1.099 8.992-10.006 17.354-20.662 27.061-29.94 81.064-77.54 164.91-151.986 250.882-224.063 9.936-8.347 10.274-15.695 1.040-25.1-42.338-43.068-84.689-86.111-127.059-129.154-9.413-9.575-16.846-9.152-25.291 1.295-76.686 94.78-156.8 186.609-239.707 276.002-1.334 1.453-2.562 3.029-4.257 5.042z" horiz-adv-x="1105" />
<glyph unicode="&#xe61c;" d="M74.418 881.299h239.304v-228.491h-239.304v228.491zM393.455 881.299h239.304v-228.491h-239.304v228.491zM712.494 881.299h239.263v-228.491h-239.263v228.491zM74.418 562.265h239.304v-228.555h-239.304v228.555zM393.455 562.265h239.304v-228.555h-239.304v228.555zM712.494 562.265h239.263v-228.555h-239.263v228.555zM74.418 243.166h239.304v-228.465h-239.304v228.465zM393.455 243.166h239.304v-228.465h-239.304v228.465zM712.494 243.166h239.263v-228.465h-239.263v228.465z" horiz-adv-x="1026" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,6 +1,59 @@
{
"IcoMoonType": "selection",
"icons": [
{
"icon": {
"paths": [
"M74.418 78.701h239.304v228.491h-239.304v-228.491z",
"M393.455 78.701h239.304v228.491h-239.304v-228.491z",
"M712.494 78.701h239.263v228.491h-239.263v-228.491z",
"M74.418 397.735h239.304v228.555h-239.304v-228.555z",
"M393.455 397.735h239.304v228.555h-239.304v-228.555z",
"M712.494 397.735h239.263v228.555h-239.263v-228.555z",
"M74.418 716.834h239.304v228.465h-239.304v-228.465z",
"M393.455 716.834h239.304v228.465h-239.304v-228.465z",
"M712.494 716.834h239.263v228.465h-239.263v-228.465z"
],
"attrs": [
{},
{},
{},
{},
{},
{},
{},
{},
{}
],
"isMulticolor": false,
"width": 1026,
"grid": 0,
"tags": [
"dailPad"
]
},
"attrs": [
{},
{},
{},
{},
{},
{},
{},
{},
{}
],
"properties": {
"order": 29,
"id": 0,
"prevSize": 32,
"code": 58908,
"name": "dialPad"
},
"setIdx": 0,
"setId": 2,
"iconIdx": 0
},
{
"icon": {
"paths": [
@@ -31,7 +84,8 @@
"code": 58907,
"name": "settings"
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 0
},
{
@@ -74,7 +128,8 @@
"name": "webCam",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 1
},
{
@@ -144,7 +199,8 @@
"name": "connection",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 2
},
{
@@ -168,7 +224,8 @@
"name": "filmstrip",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 3
},
{
@@ -194,7 +251,8 @@
"name": "reload",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 4
},
{
@@ -218,7 +276,8 @@
"name": "hangup",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 5
},
{
@@ -242,7 +301,8 @@
"name": "contactList",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 6
},
{
@@ -267,7 +327,8 @@
"name": "avatar",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 7
},
{
@@ -292,7 +353,8 @@
"name": "callRetro",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 8
},
{
@@ -317,7 +379,8 @@
"name": "callModern",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 9
},
{
@@ -343,7 +406,8 @@
"name": "recDisable",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 10
},
{
@@ -370,7 +434,8 @@
"name": "recEnable",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 11
},
{
@@ -395,7 +460,8 @@
"name": "kick1",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 12
},
{
@@ -421,7 +487,8 @@
"name": "kick",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 13
},
{
@@ -447,7 +514,8 @@
"name": "share-desktop",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 14
},
{
@@ -471,7 +539,8 @@
"name": "chat-simple",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 15
},
{
@@ -497,7 +566,8 @@
"name": "full-screen",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 16
},
{
@@ -523,7 +593,8 @@
"name": "exit-full-screen",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 17
},
{
@@ -552,7 +623,8 @@
"name": "prezi",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 18
},
{
@@ -595,7 +667,8 @@
"name": "addNew-V5",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 19
},
{
@@ -621,7 +694,8 @@
"name": "chat",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 20
},
{
@@ -648,7 +722,8 @@
"name": "presentation",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 21
},
{
@@ -672,7 +747,8 @@
"name": "security",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 22
},
{
@@ -697,7 +773,8 @@
"name": "share-doc",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 23
},
{
@@ -721,7 +798,8 @@
"name": "security-locked",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 24
},
{
@@ -746,7 +824,8 @@
"name": "camera-disabled",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 25
},
{
@@ -772,7 +851,8 @@
"name": "mic-disabled",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 26
},
{
@@ -797,7 +877,8 @@
"name": "microphone",
"ligatures": ""
},
"setIdx": 0,
"setIdx": 1,
"setId": 1,
"iconIdx": 27
}
],

BIN
images/noMic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
images/noVideo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -10,7 +10,7 @@
<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=6"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="config.js?v=8"></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>
@@ -19,12 +19,12 @@
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
<script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
<script src="interface_config.js?v=5"></script>
<script src="libs/app.bundle.js?v=42"></script>
<script src="libs/app.bundle.js?v=75"></script>
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
<link rel="stylesheet" href="css/font.css?v=6"/>
<link rel="stylesheet" href="css/font.css?v=7"/>
<link rel="stylesheet" href="css/toastr.css?v=1">
<link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=30"/>
<link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=16" id="videolayout_default"/>
<link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=17" id="videolayout_default"/>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" href="css/jquery-impromptu.css?v=4">
<link rel="stylesheet" href="css/modaldialog.css?v=3">
@@ -43,6 +43,7 @@
<link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/diibjkoicjeejcmhdnailmkgecihlobk">
<script src="libs/jquery-impromptu.js?v=2"></script>
<script src="libs/jquery.autosize.js"></script>
<!--#include virtual="plugin.head.html" -->
</head>
<body>
<div id="welcome_page">
@@ -69,7 +70,9 @@
<div id="brand_header"></div>
<input type='checkbox' name='checkbox' id="disable_welcome"/>
<label for="disable_welcome" class="disable_welcome_position" data-i18n="welcomepage.disable"></label>
<div id="header_text"></div>
<div id="header_text">
<!--#include virtual="plugin.header.text.html" -->
</div>
</div>
<div id="welcome_page_main">
<div id="features">
@@ -117,6 +120,7 @@
</div>
</div>
</div>
<!--#include virtual="plugin.welcomepage.footer.html" -->
</div>
<div id="videoconference_page">
<div style="position: relative;" id="header_container">
@@ -197,6 +201,11 @@
<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>
<span id="dialPadButton" style="display: none">
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_dialpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Open dialpad" data-i18n="[content]toolbar.dialpad">
<i class="icon-dialpad"></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" data-i18n="[content]toolbar.Settings">
<i id="settingsButton" class="icon-settings"></i>
@@ -290,9 +299,19 @@
<div class="arrow-up"></div>
<input type="text" id="setDisplayName" data-i18n="[placeholder]settings.name" placeholder="Name">
<input type="text" id="setEmail" placeholder="E-Mail">
<div id = "startMutedOptions">
<label class = "startMutedLabel">
<input type="checkbox" id="startAudioMuted">
<span data-i18n="settings.startAudioMuted"></span>
</label>
<label class = "startMutedLabel">
<input type="checkbox" id="startVideoMuted">
<span data-i18n="settings.startVideoMuted"></span>
</label>
</div>
<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-i18n="[data-content]downloadlogs" ><i class="fa fa-cloud-download"></i></a>
<a id="downloadlog" 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

@@ -8,7 +8,7 @@ var interfaceConfig = {
DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME: "speaker",
DEFAULT_LOCAL_DISPLAY_NAME: "me",
SHOW_JITSI_WATERMARK: true,
JITSI_WATERMARK_LINK: "http://jitsi.org",
JITSI_WATERMARK_LINK: "https://jitsi.org",
SHOW_BRAND_WATERMARK: false,
BRAND_WATERMARK_LINK: "",
SHOW_POWERED_BY: false,

57
lang/Translation.md Normal file
View File

@@ -0,0 +1,57 @@
Jitsi Meet Translation
==========================
Jitsi Meet uses [i18next](http://i18next.com) library for translation.
i18next uses separate json files for each language.
Translating Jitsi Meet
======================
The translation of Jitsi Meet is integrated with Pootle. You can translate Jitsi Meet via our Pootle user interface on
[http://translate.jitsi.org](http://translate.jitsi.org).
**WARNING: Please don't create or edit manually the language files! Please use our Pootle user interface!**
Development
===========
If you want to add new functionality for Jitsi Meet and you have texts that need to be translated please use our translation module.
It is located in modules/translation. You must add key and value in main.json file in English for each translatable text.
Than you can use the key to get the translated text for the current language.
**WARNING: Please don't change the other language files except main.json! They must be updated and translated via our Pootle user interface!**
You can add translatable text in the HTML:
* **via attribute on HTML element** - add **data-i18n** attribute with value the key of the translatable text.
```
<span data-i18n="dialog.OK">OK</span>
```
You can also use APP.translation.generateTranslatonHTML(key, options) to get this HTML code as Javascript string.
```
APP.translation.generateTranslatonHTML("dialog.OK") // returns <span data-i18n="dialog.OK">OK</span>
```
The value in the options parameter will be added in data-i18n-options attribute of the element.
**Note:** If you dynamically add HTML elements don't forget to call APP.translation.translateElement(jquery_selector) to translate the text initially.
* **via Javascript string** - call APP.translation.translateString(key, options). You can use that method to get the translated string in Javascript and then attach it in the HTML.
```
APP.translation.translateString("dialog.OK") // returns the value for the key of the current language file. "OK" for example.
```
For the available values of ``options`` parameter for the above methods of translation module see [i18next documentation](http://i18next.com/pages/doc_features).
**Note:** It is useful to add attributes in the HTML for persistent HTML elements because when the language is changed the text will be automatically translated.
Otherwise you should call ``APP.translation.translateString`` and manually change the text every time the language is changed.

View File

@@ -2,5 +2,6 @@
"en": "Английски",
"bg": "Български",
"de": "Немски",
"tr": "Турски"
"tr": "Турски",
"it": "Италиански"
}

View File

@@ -2,5 +2,6 @@
"en": "Englisch",
"bg": "Bulgarisch",
"de": "Deutsch",
"tr": "Türkisch"
"tr": "Türkisch",
"it": "Italienisch"
}

7
lang/languages-fr.json Normal file
View File

@@ -0,0 +1,7 @@
{
"en": "Anglais",
"bg": "Bulgare",
"de": "Allemand",
"tr": "Turc",
"it": "Italien"
}

7
lang/languages-it.json Normal file
View File

@@ -0,0 +1,7 @@
{
"en": "Inglese",
"bg": "Bulgaro",
"de": "Tedesco",
"tr": "Turco",
"it": "Italiano"
}

View File

@@ -2,5 +2,6 @@
"en": "English",
"bg": "Bulgarian",
"de": "German",
"tr": "Turkish"
}
"tr": "Turkish",
"it": "Italian"
}

View File

@@ -1,191 +1,200 @@
{
"contactlist": "СПИСЪК С КОНТАКТИ",
"connectionsettings": "Настройки на връзката",
"poweredby": "",
"downloadlogs": "",
"roomUrlDefaultMsg": "",
"poweredby": "powered by",
"downloadlogs": "Изтегли логовете",
"roomUrlDefaultMsg": "Конференцията се създава...",
"participant": "Участник",
"me": "аз",
"speaker": "",
"defaultNickname": "",
"defaultPreziLink": "",
"speaker": "Говорител",
"defaultNickname": "например __name__",
"defaultPreziLink": "например __url__",
"welcomepage": {
"go": "",
"go": "Влез",
"roomname": "Въведете име на стаята",
"disable": "",
"disable": "Не показвай страницата следващия път",
"feature1": {
"title": "",
"content": ""
"title": "Лесен за употреба",
"content": "Не е необходимо да сваляте нищо. _app_ работи директно във вашия браузър. Просто споделете адреса на вашата конференция с другите за да започнете."
},
"feature2": {
"title": "",
"content": ""
"content": "Видео конференциите могат да работят с по-малко от 128Kbps, а аудио конференциите и конференциите с споделен екран дори с по-малко."
},
"feature3": {
"title": "",
"content": ""
"title": "Отворен код",
"content": "__app__ е лицензиран под MIT лиценз. Можете свободно да го изтеглите, използвате, променяте и споделяте според тези лицензи."
},
"feature4": {
"title": "",
"content": ""
"title": "Неограничен брой потребители",
"content": "Няма изкуствени ограничения за броя на потребителите или участниците в конференция. Единствените ограничения са мощността на вашия сървър и качеството на интернет връзката му."
},
"feature5": {
"title": "",
"content": ""
"title": "Споделяне на екрана",
"content": "Лесно е да споделите екрана си с другите. __app__ е идеален за онлайн презентации, лекции и техническа подръжка."
},
"feature6": {
"title": "",
"content": ""
"title": "Сигурни стаи",
"content": "Нуждаете се от уединение? _app__ конферентните стай могат да бъдат защитени от парола за да се препазите от нежелани гости или прекъсвания."
},
"feature7": {
"title": "",
"content": ""
"title": "Споделени бележки",
"content": "__app__ използва Etherpad, с който можете да редактирате текст в реално време заедно."
},
"feature8": {
"title": "",
"content": ""
"title": "Статистики за използване",
"content": "Научете повече за своите потребители като интегрирате лесно Piwik, Google Analytics и други статистики за изполването."
}
},
"toolbar": {
"mute": "",
"videomute": "",
"mute": "Включи / Изключи микрофона",
"videomute": "Спри / пусни камерата",
"authenticate": "",
"record": "",
"lock": "",
"invite": "",
"record": "Запис",
"lock": "Заключи / отключи стаята",
"invite": "Поканване на други",
"chat": "",
"prezi": "",
"etherpad": "",
"sharescreen": "",
"fullscreen": "",
"sip": "",
"Settings": "",
"hangup": "",
"login": "",
"prezi": "Сподели Prezi",
"etherpad": "Споделяне на документ",
"sharescreen": "Споделяне на екрана",
"fullscreen": "Влез / Излез от Пълен екран",
"sip": "Обади се на SIP номер",
"Settings": "Настройки",
"hangup": "Затвори",
"login": "Влез",
"logout": ""
},
"bottomtoolbar": {
"chat": "",
"filmstrip": "",
"contactlist": ""
"chat": "Отвори / затвори чат",
"filmstrip": "Покажи / скрий лентата с видеа",
"contactlist": "Отвори / затвори контакт листа"
},
"chat": {
"nickname": {
"title": "",
"popover": ""
"title": "Въведете име в полето",
"popover": "Избор на име"
},
"messagebox": ""
"messagebox": "Въведете текст..."
},
"settings": {
"title": "НАСТРОЙКИ",
"update": "",
"name": ""
"update": "Актуализиране",
"name": "Име"
},
"videothumbnail": {
"editnickname": "",
"moderator": "",
"videomute": "",
"mute": "",
"kick": "",
"muted": "",
"domute": ""
"editnickname": "Натиснете за да<br/>промените името",
"moderator": "Създателя на<br/>конференцията",
"videomute": "Учасника е спрял<br/>камерата си.",
"mute": "Учасника е с изключен микрофон",
"kick": "Изгони",
"muted": "Изключен микрофон",
"domute": "Изключи микрофона"
},
"connectionindicator": {
"bitrate": "",
"packetloss": "",
"resolution": "",
"less": "",
"more": "",
"address": "",
"remoteport": "",
"remoteport_plural": "",
"localport": "",
"localport_plural": "",
"localaddress": "",
"localaddress_plural": "",
"remoteaddress": "",
"remoteaddress_plural": "",
"transport": "",
"packetloss": "Загуба на пакети:",
"resolution": "Резолюция:",
"less": "Скрий",
"more": "Покажи",
"address": "Адрес:",
"remoteport": "Отдалечен порт:",
"remoteport_plural": "Отдалечени портове:",
"localport": "Локален порт:",
"localport_plural": "Локални портове:",
"localaddress": "Локален адрес:",
"localaddress_plural": "Локални адреси:",
"remoteaddress": "Отдалечен адрес:",
"remoteaddress_plural": "Отдалечени адреси:",
"transport": "Транспорт:",
"bandwidth": "",
"na": ""
"na": "Върнете се тук за информацията относно вашата връзка, когато започне конференцията"
},
"notify": {
"disconnected": "",
"moderator": "",
"connected": "",
"somebody": "",
"me": "",
"focus": "",
"focusFail": "",
"grantedTo": "",
"grantedToUnknown": ""
"disconnected": "връзката е прекъсната",
"moderator": "Придобихте права на модератор!",
"connected": "свързан",
"somebody": "Някой",
"me": "Аз",
"focus": "Конферентен фокус",
"focusFail": "__component__ не е на раположения - следващ опит след __ms__ секунди",
"grantedTo": "Даване на роля модератор на __to__!",
"grantedToUnknown": "Даване на роля модератор на $t(somebody)!"
},
"dialog": {
"kickMessage": "",
"popupError": "",
"passwordError": "",
"passwordError2": "",
"joinError": "",
"connectError": "",
"kickMessage": "Бяхте изгонен от срещата!",
"popupError": "Вашия браузър блокира попъп прозорците от този сайт. Моля позволете попъп прозорците от настройките на браузъра и опитайте пак.",
"passwordError": "Тази конференция е защитена с парола. Само създателя и може да промени паролата.",
"passwordError2": "Тази конференция не е защитена с парола. Само създателя и може да сложи парола.",
"joinError": "Не можете да се присъедините към конференцията. Може би имате проблем с конфигурацията на сифурността. Моля обърнете се към администратора на услугата.",
"connectError": "Опа! Нещо се обърка и не успяхме да се свържем с конференцията.",
"connecting": "",
"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": ""
"detectext": "Възникна грешка при опит да бъде намерено разширението за споделяне на екран.",
"failtoinstall": "Неуспешна инсталация на разширението за споделяне на екрана.",
"failedpermissions": "Неуспешен опит за получаване на права за използване на микрофон и/или камера.",
"bridgeUnavailable": "Jitsi Videobridge не е наличен. Моля опитайте пак!",
"lockTitle": "Неуспешно заключване",
"lockMessage": "Неуспешно заключване на конференцията.",
"warning": "Внимание",
"passwordNotSupported": "В момента не поддържаме стаи с пароли.",
"sorry": "Съжаляваме",
"internalError": "Вътрешна грешка [setRemoteDescription]",
"unableToSwitch": "Неуспешен опит за смяна на видеото.",
"SLDFailure": "Опа! Нещо се обърка и не успяхме да спрем микрофона! (SLD Failure)",
"SRDFailure": "Опа! Нещо се обърка и не успяхме да спрем камерата! (SRD Failure)",
"oops": "Опа!",
"defaultError": "Възникна грешка",
"passwordRequired": "Изисква се парола",
"Ok": "ОК",
"removePreziTitle": "Премахни Prezi",
"removePreziMsg": "Сигурни ли сте, че искате да премахнете Prezi?",
"sharePreziTitle": "Сподели Prezi",
"sharePreziMsg": "Друг участник вече е споделил Prezi. Тази конференция позволява само да се споделя само едно Prezi.",
"Remove": "Премахване",
"Stop": "Спиране",
"AuthMsg": "Нужна е идентификация, за да създадете стая:<br/><b>__room__</b></br> Може да се идентифицирате, за да създадете стая или да изчакате някой друг да го направи.",
"Authenticate": "Идентификация",
"Cancel": "Отказ",
"retry": "Повторен опит",
"logoutTitle": "Изход",
"logoutQuestion": "Сигурни ли сте, че искате да излезете и да прекъснете конференцията?",
"sessTerminated": "Сесията е прекъсната.",
"hungUp": "Вие затворихте обаждането.",
"joinAgain": "Песъединете се отново",
"Share": "Споделяне",
"preziLinkError": "Моля въведете правилен Prezi линк.",
"Save": "Запазване",
"recordingToken": "Въведете код за достъп за запис на конференцията",
"Dial": "Набиране",
"sipMsg": "Въведете SIP номер",
"passwordCheck": "Сигурни ли сте, че искате да махнете паролата?",
"passwordMsg": "Въведете парола, за да заключите стаята",
"Invite": "Покани",
"shareLink": "Сподели този линк с всеки, който искаш да поканиш",
"settings1": "Конфигурирай конференцията",
"settings2": "Участниците се присъединиха с изключен микрофон.",
"settings3": "Изисквай имена<br/><br/>Въведете парола за да заключите стаята:",
"yourPassword": "вашата парола",
"Back": "Назад",
"serviceUnavailable": "Услугата не е налична",
"gracefulShutdown": "Услугата временно не е достъпна поради профилактика. Моля опитайте по-късно.",
"Yes": "Да",
"reservationError": "Грешка в системата за резервации",
"reservationErrorMsg": "Грешка номер: __code__, съобщение: __msg__",
"password": "парола",
"userPassword": "потребителска парола",
"token": "код за достъп"
},
"email": {
"sharedKey": "",
"subject": "",
"sharedKey": [
"Тази конференция е защитена с парола. Моля използвайте следния код за да се присъедините:",
"",
"",
"__sharedKey__",
"",
""
],
"subject": "Покана за __appName__ (__conferenceName__)",
"body": [
"Здравей, Бих искал да те поканя в една __appName__ конференция, която създадох.",
"",
@@ -199,6 +208,17 @@
"",
"Ще се видим след секунда!"
],
"and": ""
"and": "и"
},
"connection": {
"ERROR": "Грешка",
"CONNECTING": "Свързване",
"CONNFAIL": "Връзката е неуспешна",
"AUTHENTICATING": "Идентификация",
"AUTHFAIL": "Неуспешна идентификация",
"CONNECTED": "Свързан",
"DISCONNECTED": "Изключен",
"DISCONNECTING": "Прекъсване на връзката",
"ATTACHED": "Прикрепен"
}
}

View File

@@ -127,6 +127,7 @@
"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.",
"connecting": "",
"error": "Fehler",
"detectext": "Fehler bei der Erkennung der Bildschirmfreigabeerweiterung.",
"failtoinstall": "Die Bildschirmfreigabeerweiterung konnte nicht installiert werden.",
@@ -154,6 +155,7 @@
"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",
"retry": "Wiederholen",
"logoutTitle": "Abmelden",
"logoutQuestion": "Sind Sie sicher dass Sie sich abmelden und die Konferenz verlassen möchten?",
"sessTerminated": "Sitzung beendet",
@@ -213,5 +215,16 @@
"Bis gleich!"
],
"and": "und"
},
"connection": {
"ERROR": "Fehler",
"CONNECTING": "Verbindung wird hergestellt",
"CONNFAIL": "Verbindungsaufbau gescheitert",
"AUTHENTICATING": "Anmeldung läuft",
"AUTHFAIL": "Authentifizierung fehlgeschlagen",
"CONNECTED": "Verbunden",
"DISCONNECTED": "Getrennt",
"DISCONNECTING": "Verbindung wird getrennt",
"ATTACHED": "Angehängt"
}
}

227
lang/main-fr.json Normal file
View File

@@ -0,0 +1,227 @@
{
"contactlist": "Liste de contacts",
"connectionsettings": "Paramètres de connexion",
"poweredby": "propulsé par",
"downloadlogs": "Téléchargement des logs",
"roomUrlDefaultMsg": "Votre conférence est en cours de création...",
"participant": "Participant",
"me": "moi",
"speaker": "Haut-parleur",
"defaultNickname": "ex: __name__",
"defaultPreziLink": "e.g. __url__",
"welcomepage": {
"go": "Créer",
"roomname": "Saisissez un nom de salle",
"disable": "Ne pas afficher cette page lors de ma prochaine visite",
"feature1": {
"title": "Simple à utiliser",
"content": "Aucun téléchargement requis. __app__ s'utilise directement depuis votre navigateur. Partager simplement l'URL de votre conférence avec les autres pour commencer."
},
"feature2": {
"title": "Faible bande passante",
"content": "Les vidéo conférences à plusieurs participants nécessitent moins de 128 kbps. Le partage d'écran et les conférences avec seulement de l'audio sont possibles avec beaucoup moins de débit."
},
"feature3": {
"title": "Open source",
"content": "__app__ est sous licence MIT. Vous êtes libre de le télécharger, l'utiliser, le modifier et le partager sous cette licence."
},
"feature4": {
"title": "Nombre d'utilisateurs illimité",
"content": "Il n'y a pas de restrictions artificielles concernant le nombre d'utilisateurs ou de participants à une conférence. La puissance du serveur et la bande passante sont les seuls facteurs limitants."
},
"feature5": {
"title": "Partage d'écan",
"content": "C'est facile de partager votre écran avec d'autres personnes. __app__ est idéal pour les présentations en ligne, les cours, et les sessions de support technique."
},
"feature6": {
"title": "Salles sécurisées",
"content": "Besoin de confidentialité? Les salles de conférence __app__ peuvent être sécurisées par un mot de passe pour exclure des invités non désirées, et prévenir des interruptions. "
},
"feature7": {
"title": "Notes partagées",
"content": "__app__ propose Etherpad, un éditeur de texte collaboratif en temps réel qui est parfait pour les procès-verbaux, l'édition d'articles et plus encore."
},
"feature8": {
"title": "Statistiques d'utilisation",
"content": "Connaissez mieux vos utilisateurs avec une intégration facile de Piwik, Google Analytics et d'autres systèmes de statistiques et supervision d'utilisation."
}
},
"toolbar": {
"mute": "Muet / Actif",
"videomute": "Démarrer / Arrêter la caméra",
"authenticate": "",
"record": "Enregistrer",
"lock": "Verrouiller / déverrouiller la salle",
"invite": "Inviter des participants",
"chat": "",
"prezi": "Partager une présentation Prezi",
"etherpad": "Partager un document",
"sharescreen": "Partager mon écran",
"fullscreen": "Activer / Désactiver le plein écran",
"sip": "Appeler un numéro SIP",
"Settings": "Paramètres",
"hangup": "Raccrocher",
"login": "Connexion",
"logout": ""
},
"bottomtoolbar": {
"chat": "Ouvrir / fermer le chat",
"filmstrip": "Montrer / cacher ma vidéo miniature",
"contactlist": "Ouvrir / fermer ma liste de contacts"
},
"chat": {
"nickname": {
"title": "Saisissez un pseudonyme dans le champ ci-dessous",
"popover": "Choisissez un pseudonyme"
},
"messagebox": "Saisissez votre texte..."
},
"settings": {
"title": "PARAMÈTRES",
"update": "Mise à jour",
"name": "Nom"
},
"videothumbnail": {
"editnickname": "Cliquez pour modifier<br/>votre nom",
"moderator": "Le propriétaire de<br/>cette conférence",
"videomute": "Un participant a<br/>arrêté sa caméra.",
"mute": "Un participant a coupé son micro",
"kick": "Exclure",
"muted": "Coupé",
"domute": "Couper le son"
},
"connectionindicator": {
"bitrate": "Débit",
"packetloss": "Perte de paquets:",
"resolution": "Résolution:",
"less": "Cacher le détail",
"more": "Montrer le détail",
"address": "Adresse:",
"remoteport": "Port distant:",
"remoteport_plural": "Ports distants:",
"localport": "Port local:",
"localport_plural": "Ports locaux:",
"localaddress": "Adresse locale:",
"localaddress_plural": "Adresses locales:",
"remoteaddress": "Adresse distante:",
"remoteaddress_plural": "Adresses distantes:",
"transport": "Transport:",
"bandwidth": "Bande passante estimée:",
"na": "Revenez ici pour afficher les informations de connexion une fois la conférence démarrée"
},
"notify": {
"disconnected": "Déconnecté",
"moderator": "Droits modérateur accordés!",
"connected": "Connecté",
"somebody": "Quelqu'un",
"me": "Moi",
"focus": "",
"focusFail": "__component__ n'est pas disponible - réessayez dans __ms__ sec",
"grantedTo": "Droits modérateur accordés à __to__!",
"grantedToUnknown": "Droits modérateur accordés à $t(somebody)!"
},
"dialog": {
"kickMessage": "Oups! Vous avez été renvoyé de la réunion!",
"popupError": "Votre navigateur bloque les popups pour ce site. Activez les popups pour ce site dans vos paramètres de sécurité et réessayez.",
"passwordError": "Cette conversation est protégée par un mot de passe. Seul le propriétaire de cette conférence peut paramétrer un mot de passe.",
"passwordError2": "Cette conversation n'est pas protégée par un mot de passe. Seul le propriétaire de cette conférence peut paramétrer un mot de passe.",
"joinError": "Oups! La conférence ne peut être rejointe. Il y a peut-être un souci avec les paramètres de sécurité. Contactez l'administrateur.",
"connectError": "Oups! Un problème est survenu et la connexion à la conférence est impossible.",
"connecting": "",
"error": "",
"detectext": "Une erreur est survenue pendant la détection de l'extension de partage d'écran.",
"failtoinstall": "Échec de l'installation de l'extension de partage d'écran",
"failedpermissions": "Échec d'obtention des permissions pour utiliser le micro et/ou la caméra local(e)",
"bridgeUnavailable": "Le pont de visioconférence Jitsi est indisponible pour le moment. Réessayez plus tard!",
"lockTitle": "Échec du verrouillage",
"lockMessage": "Impossible de verrouiller la conférence.",
"warning": "Avertissement",
"passwordNotSupported": "Les mots de passe de conférence ne sont pas supportés.",
"sorry": "Désolé",
"internalError": "Une erreur interne de l'application est survenue [setRemoteDescription]",
"unableToSwitch": "Impossible de passer le flux vidéo.",
"SLDFailure": "Oups! Un problème est survenu et le micro n'a pas été coupé! (Échec SLD)",
"SRDFailure": "Oups! Un problème est survenu et la caméra n'a pas été coupée! (Échec SRD)",
"oops": "Oups!",
"defaultError": "Une erreur est survenue",
"passwordRequired": "Mot de passe requis",
"Ok": "Ok",
"removePreziTitle": "Supprimer la présentation Prezi",
"removePreziMsg": "Voulez-vous vraiment supprimer votre présentation Prezi?",
"sharePreziTitle": "Partager une présentation Prezi",
"sharePreziMsg": "Un autre participant partage déjà une présentation Prezi. Cette conférence autorise une seule présentation Prezi à la fois.",
"Remove": "Supprimer",
"Stop": "Arrêter",
"AuthMsg": "L'authentification est requise pour créer la conférence:<br/><b>__room__ </b></br> Vous pouvez vous authentifier pour créer la conférence ou attendre que quelqu'un le fasse pour vous.",
"Authenticate": "Authentifiez-vous",
"Cancel": "Annuler",
"retry": "Réessayer",
"logoutTitle": "Déconnexion",
"logoutQuestion": "Voulez-vous vraiment vous déconnecter et arrêter la conférence?",
"sessTerminated": "Session terminée",
"hungUp": "Vous avez raccroché et quitté la conférence",
"joinAgain": "Rejoignez à nouveau la conférence",
"Share": "Partager",
"preziLinkError": "Fournissez s'il vous plaît un lien prezi fonctionnel.",
"Save": "Sauvegarder",
"recordingToken": "Saisissez un jeton d'enregistrement",
"Dial": "Composer",
"sipMsg": "Saisissez un numéro SIP",
"passwordCheck": "Voulez-vous vraiment supprimer votre mot de passe?",
"passwordMsg": "Saisissez un mot de passe pour verrouiller la conférence",
"Invite": "Inviter",
"shareLink": "Partagez ce lien avec toutes les personnes que vous voulez inviter",
"settings1": "Configurez votre conférence",
"settings2": "Les participants rejoignent la conférence en étant muets.",
"settings3": "Pseudonymes requis<br/><br/>Saisissez un mot de passe pour verrouiller la conférence:",
"yourPassword": "Votre mot de passe",
"Back": "Retour",
"serviceUnavailable": "Service indisponible",
"gracefulShutdown": "Le service est actuellement en maintenance. Réessayez plus tard.",
"Yes": "Oui",
"reservationError": "Erreur du système de réservation",
"reservationErrorMsg": "Code d'erreur: __code__, message: __msg__",
"password": "mot de passe",
"userPassword": "mot de passe utilisateur",
"token": "jeton"
},
"email": {
"sharedKey": [
"Cette conférence est protégée par un mot de passe. Utilisez le code suivant pour la rejoindre:",
"",
"",
"__sharedKey__",
"",
""
],
"subject": "Invitation à la conférence __appName__ : __conferenceName__",
"body": [
"Bonjour, je vous invite à la conférence __appName__ que je viens de créer.",
"",
"",
"Cliquez sur le lien suivant pour rejoindre la conférence.",
"",
"",
"__roomUrl__",
"",
"",
"__sharedKeyText__",
" Notez que __appName__ est actuellement seulement supporté par __supportedBrowsers__, vous devez donc utiliser un de ces navigateurs.",
"",
"",
"À tout de suite dans la conférence!"
],
"and": "et"
},
"connection": {
"ERROR": "Erreur",
"CONNECTING": "Connexion en cours...",
"CONNFAIL": "Échec de la Connexion",
"AUTHENTICATING": "Authentification en cours...",
"AUTHFAIL": "Échec de l'authentification",
"CONNECTED": "Connecté",
"DISCONNECTED": "Déconnecté",
"DISCONNECTING": "Déconnexion en cours...",
"ATTACHED": "Attachée"
}
}

227
lang/main-it.json Normal file
View File

@@ -0,0 +1,227 @@
{
"contactlist": "LISTA CONTATTI",
"connectionsettings": "Impostazioni Connessione",
"poweredby": "powered by",
"downloadlogs": "Scarica logs",
"roomUrlDefaultMsg": "La tua conferenza sta per essere creata...",
"participant": "Partecipante",
"me": "io",
"speaker": "Relatore",
"defaultNickname": "es. __nome__",
"defaultPreziLink": "es. __url__",
"welcomepage": {
"go": "VAI",
"roomname": "Inserisci Nome Stanza",
"disable": "Non visualizzare questa pagina la prossima volta",
"feature1": {
"title": "Semplice da usare",
"content": "Nessun download richiesto. __app__ funziona direttamente nel tuo browser. Condividi semplicemente l'URL della tua conferenza con altri per iniziare."
},
"feature2": {
"title": "Poca banda",
"content": "Conferenze video multi utente funzionano con appena 128Kbps. La condivisione dello schermo ed conferenze solo audio sono possibili con molto meno."
},
"feature3": {
"title": "Open source",
"content": "__app__ è sotto licenza MIT. Sei libero di scaricarla, usarla, modificarla e condividerla con la medesima licenza."
},
"feature4": {
"title": "Utenti illimitati",
"content": "Non ci sono restrizioni sul numero di utenti per una conferenza. La potenza del server e la banda a disposizione sono gli unici fattori limitanti."
},
"feature5": {
"title": "Condivisione Schermo",
"content": "é facile condividere il tuo schermo con altri. __app__ è l'ideale per presentazioni online, letture, e sessioni di supporto tecnico."
},
"feature6": {
"title": "Stanze sicure",
"content": "Hai bisogno di più privacy? Le conferenze di __app__ possono essere rese sicure con una password per escludere ospiti non desiderati e prevenire interruzioni."
},
"feature7": {
"title": "Note condivise",
"content": "__app__ utilizza Etherpad, un editor di testo real-time e collaborativo che è ottimo per meeting, scrivere articoli e tanto altro."
},
"feature8": {
"title": "Statistiche di utilizzo",
"content": "Impara come i tuoi utenti lo utilizzano con la facile integrazione con PiWik, Google Analytics, e altri sistemi di statistica e monitor dell'utilizzo."
}
},
"toolbar": {
"mute": "Microfono Attiva / Disattiva",
"videomute": "Abilita / Disabilita video",
"authenticate": "",
"record": "Registra",
"lock": "Blocca / Sblocca Stanza",
"invite": "Invita altri",
"chat": "",
"prezi": "Condividi con Prezi",
"etherpad": "Documento condiviso",
"sharescreen": "Condividi schermo",
"fullscreen": "Entra / Esci da schermo intero",
"sip": "Chiama numero SIP",
"Settings": "Impostazioni",
"hangup": "Termina",
"login": "Login",
"logout": ""
},
"bottomtoolbar": {
"chat": "Apri / Chiudi chat",
"filmstrip": "Mostra / Nascondi miniature",
"contactlist": "Apri / Chiudi la lista contatti"
},
"chat": {
"nickname": {
"title": "Scegli un nickname nel box qui sotto",
"popover": "Scegli un nickname"
},
"messagebox": "Inserisci testo..."
},
"settings": {
"title": "IMPOSTAZIONI",
"update": "Aggiorna",
"name": "Nome"
},
"videothumbnail": {
"editnickname": "Clicca per modificare il tuo<br/>nome visualizzato",
"moderator": "Il proprietario<br/>della conferenza",
"videomute": "Il partecipante ha<br/>fermato il video.",
"mute": "Il partecipante è in muto",
"kick": "Espelli",
"muted": "Audio disattivato",
"domute": "Disattiva audio"
},
"connectionindicator": {
"bitrate": "Bitrate:",
"packetloss": "Perdita pacchetti:",
"resolution": "Risoluzione:",
"less": "Mostra meno",
"more": "Mostra di più",
"address": "Indirizzo:",
"remoteport": "Porta remota:",
"remoteport_plural": "Porte remote:",
"localport": "Porta locale:",
"localport_plural": "Porte locali:",
"localaddress": "Indirizzo locale:",
"localaddress_plural": "Indirizzi locali:",
"remoteaddress": "Indirizzo remoto:",
"remoteaddress_plural": "Indirizzi remoti:",
"transport": "Trasporto:",
"bandwidth": "Banda stimata:",
"na": "Ritorna qui per informazioni sulla connessione una volta che la conferenza inizia"
},
"notify": {
"disconnected": "disconnesso",
"moderator": "Impostati i permessi di moderatore!",
"connected": "connesso",
"somebody": "Qualcuno",
"me": "io",
"focus": "Focus su conferenza",
"focusFail": "__component__ non disponibile - riprova in __ms__ sec",
"grantedTo": "Permessi di moderatore garantiti a __to__!",
"grantedToUnknown": "Permessi di moderatore garantiti a $t(somebody)!"
},
"dialog": {
"kickMessage": "Accidenti! Sei stato espulso dalla conferenza !",
"popupError": "Il tuo browser sta bloccando le finestre popup da questo sito. Abilita i popup tra le impostazioni di sicurezza del tuo browser e riprova.",
"passwordError": "Questa conversazione è protetta da una password. Solo il proprietario della conferenza può impostare una password.",
"passwordError2": "Questa conversazione non è al momento protetta da una password. Solo il proprietario può impostare la password.",
"joinError": "Oops! Non puoi entrare nella conferenza. Ci deve essere qualche problema con la configurazione di sicurezza. Contattare l'amministratore di sistema.",
"connectError": "Oops! Qualcosa è andato storto e non ti puoi collegare alla conferenza.",
"connecting": "",
"error": "",
"detectext": "Errore durante il rilevamento dell'estensione per il desktopsharing.",
"failtoinstall": "Impossibile installare l'estensione per il desktop sharing",
"failedpermissions": "Impossibile ottenere i permessi per usare il microfono e/o il video locale.",
"bridgeUnavailable": "Il Videobridge non è al momento disponibile. Si prega di riprovare più tardi!",
"lockTitle": "Blocco fallito",
"lockMessage": "Impossibile bloccare la conferenza.",
"warning": "Attenzione",
"passwordNotSupported": "Le password sulla stanza non sono al momento supportate.",
"sorry": "Spiacente",
"internalError": "Errore interno dell'applicazione [setRemoteDescription]",
"unableToSwitch": "Impossibile cambiare lo stream video.",
"SLDFailure": "Oops! Qualcosa è andato storto e non è possibile silenziare il microfono! (Errore SLD)",
"SRDFailure": "Oops! Qualcosa è andato storto e non è possibile fermare il video! (Errore SRD)",
"oops": "Oops!",
"defaultError": "C'è stato qualche tipo di errore",
"passwordRequired": "Richiesta password ",
"Ok": "Ok",
"removePreziTitle": "Rimuovi Prezi",
"removePreziMsg": "Sei sicuro di voler rimuovere il tuo Prezi?",
"sharePreziTitle": "Condividi un Prezi",
"sharePreziMsg": "Un altro partecipante sta già condividendo un Prezi. Questa conferenza permette un solo Prezi alla volta.",
"Remove": "Rimuovi",
"Stop": "Ferma",
"AuthMsg": "Autenticazione richiesta per creare la stanza:<br/><b>__room__ </b></br> Puoi autenticarti per creare la stanza o aspettare che qualcun altro lo faccia per te.",
"Authenticate": "Autenticazione",
"Cancel": "Annulla",
"retry": "Riprova",
"logoutTitle": "Logout",
"logoutQuestion": "Vuoi disconnetterti e interrompere la conferenza ?",
"sessTerminated": "Sessione Terminata",
"hungUp": "Hai terminato la conversazione",
"joinAgain": "Entra ancora",
"Share": "Condividi",
"preziLinkError": "Fornire un link Prezi esatto.",
"Save": "Salva",
"recordingToken": "Inserire token di registrazione",
"Dial": "Componi",
"sipMsg": "Inserire numero SIP",
"passwordCheck": "Confermi la rimozione della password?",
"passwordMsg": "Imposta una password per bloccare la stanza",
"Invite": "Invita",
"shareLink": "Condividi questo link con tutte le persone che vuoi invitare",
"settings1": "Configura la tua conferenza",
"settings2": "Partecipanti connessi in muto",
"settings3": "Richiedi nicknames<br/><br/>Imposta una password per bloccare la tua stanza:",
"yourPassword": "la tua password",
"Back": "Indietro",
"serviceUnavailable": "Servizio non disponibile",
"gracefulShutdown": "Il nostro servizio è al momento spento per manutenzione. Si prega di riprovare più tardi.",
"Yes": "Sì",
"reservationError": "Errore di sistema in prenotazione",
"reservationErrorMsg": "Codice di errore: __code__, messaggio: __msg__",
"password": "password",
"userPassword": "password utente",
"token": "token"
},
"email": {
"sharedKey": [
"Questa conferenza è protetta da password. Utilizzare il seguente PIN alla connessione:",
"",
"",
"__sharedKey__",
"",
""
],
"subject": "Invito su __appName__ (__conferenceName__)",
"body": [
"Ciao, Vorrei invitarti alla conferenza che ho appena creato su __appName__ .",
"",
"",
"Cliccare sul seguente link per entrare nella conferenza.",
"",
"",
"__roomUrl__",
"",
"",
"__sharedKeyText__",
"NOTA: __appName__ è al momento supportato da questi browsers: __supportedBrowsers__, è necessario utilizzare uno di questi programmi per poter entrare.",
"",
"",
"Ci sentiamo tra un secondo!"
],
"and": "e"
},
"connection": {
"ERROR": "Errore",
"CONNECTING": "Connessione",
"CONNFAIL": "Connessione non riuscita",
"AUTHENTICATING": "Autenticazione",
"AUTHFAIL": "Autenticazione fallita",
"CONNECTED": "Connesso",
"DISCONNECTED": "Disconnesso",
"DISCONNECTING": "Disconnessione in corso",
"ATTACHED": "Collegato"
}
}

View File

@@ -62,7 +62,8 @@
"Settings": "Settings",
"hangup": "Hang Up",
"login": "Login",
"logout": "Logout"
"logout": "Logout",
"dialpad": "Show dialpad"
},
"bottomtoolbar": {
"chat": "Open / close chat",
@@ -80,7 +81,9 @@
{
"title": "SETTINGS",
"update": "Update",
"name": "Name"
"name": "Name",
"startAudioMuted": "everyone join audio muted",
"startVideoMuted": "everyone join video muted"
},
"videothumbnail":
{
@@ -122,8 +125,9 @@
"focus": "Conference focus",
"focusFail": "__component__ not available - retry in __ms__ sec",
"grantedTo": "Moderator rights granted to __to__!",
"grantedToUnknown": "Moderator rights granted to $t(somebody)!"
"grantedToUnknown": "Moderator rights granted to $t(somebody)!",
"muted": "You have started the conversation muted.",
"mutedTitle": "You're muted!"
},
"dialog": {
"kickMessage": "Ouch! You have been kicked out of the meet!",
@@ -132,6 +136,7 @@
"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.",
"connectErrorWithMsg": "Oops! Something went wrong and we couldn't connect to the conference: __msg__",
"connecting": "Connecting",
"error": "Error",
"detectext": "Error when trying to detect desktopsharing extension.",
@@ -156,9 +161,9 @@
"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",
"WaitingForHost": "Waiting for the host ...",
"WaitForHostMsg": "The conference <b>__room__ </b> has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
"IamHost": "I am the host",
"Cancel": "Cancel",
"retry": "Retry",
"logoutTitle" : "Logout",

File diff suppressed because it is too large Load Diff

View File

@@ -135,7 +135,7 @@ function processMessage(event)
}
function setupListeners() {
APP.xmpp.addListener(XMPPEvents.MUC_ENTER, function (from) {
APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, function (from) {
API.triggerEvent("participantJoined", {jid: from});
});
APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, function (from, nick, txt, myjid) {
@@ -143,7 +143,7 @@ function setupListeners() {
API.triggerEvent("incomingMessage",
{"from": from, "nick": nick, "message": txt});
});
APP.xmpp.addListener(XMPPEvents.MUC_LEFT, function (jid) {
APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, function (jid) {
API.triggerEvent("participantLeft", {jid: jid});
});
APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, function (jid, newDisplayName) {

47
modules/DTMF/DTMF.js Normal file
View File

@@ -0,0 +1,47 @@
/* global APP */
/**
* A module for sending DTMF tones.
*/
var DTMFSender;
var initDtmfSender = function() {
// TODO: This needs to reset this if the peerconnection changes
// (e.g. the call is re-made)
if (DTMFSender)
return;
var localAudio = APP.RTC.localAudio;
if (localAudio && localAudio.getTracks().length > 0)
{
var peerconnection
= APP.xmpp.getConnection().jingle.activecall.peerconnection;
if (peerconnection) {
DTMFSender =
peerconnection.peerconnection
.createDTMFSender(localAudio.getTracks()[0]);
console.log("Initialized DTMFSender");
}
else {
console.log("Failed to initialize DTMFSender: no PeerConnection.");
}
}
else {
console.log("Failed to initialize DTMFSender: no audio track.");
}
};
var DTMF = {
sendTones: function (tones, duration, pause) {
if (!DTMFSender)
initDtmfSender();
if (DTMFSender){
DTMFSender.insertDTMF(tones,
(duration || 200),
(pause || 200));
}
}
};
module.exports = DTMF;

View File

@@ -1,11 +1,15 @@
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
function LocalStream(stream, type, eventEmitter, videoType)
function LocalStream(stream, type, eventEmitter, videoType, isGUMStream)
{
this.stream = stream;
this.eventEmitter = eventEmitter;
this.type = type;
this.videoType = videoType;
this.isGUMStream = true;
if(isGUMStream === false)
this.isGUMStream = isGUMStream;
var self = this;
if(type == "audio")
{
@@ -36,27 +40,43 @@ 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 = this.getTracks();
for (var idx = 0; idx < tracks.length; idx++) {
ismuted = !tracks[idx].enabled;
tracks[idx].enabled = ismuted;
}
return ismuted;
return this.type === "audio";
};
LocalStream.prototype.setMute = function(mute)
{
var tracks = this.getTracks();
for (var idx = 0; idx < tracks.length; idx++) {
tracks[idx].enabled = mute;
if((window.location.protocol != "https:" && this.isGUMStream) ||
(this.isAudioStream() && this.isGUMStream) || this.videoType === "screen")
{
var tracks = this.getTracks();
for (var idx = 0; idx < tracks.length; idx++) {
tracks[idx].enabled = mute;
}
}
else
{
if(mute === false) {
APP.xmpp.removeStream(this.stream);
this.stream.stop();
}
else
{
var self = this;
APP.RTC.rtcUtils.obtainAudioAndVideoPermissions(
(this.isAudioStream() ? ["audio"] : ["video"]),
function (stream) {
if(self.isAudioStream())
{
APP.RTC.changeLocalAudio(stream, function () {});
}
else
{
APP.RTC.changeLocalVideo(stream, false, function () {});
}
});
}
}
};
@@ -68,6 +88,8 @@ LocalStream.prototype.isMuted = function () {
}
else
{
if(this.stream.ended)
return true;
tracks = this.stream.getVideoTracks();
}
for (var idx = 0; idx < tracks.length; idx++) {
@@ -81,6 +103,4 @@ LocalStream.prototype.getId = function () {
return this.stream.getTracks()[0].id;
}
module.exports = LocalStream;

View File

@@ -7,13 +7,46 @@ var DesktopSharingEventTypes
= require("../../service/desktopsharing/DesktopSharingEventTypes");
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
var RTCEvents = require("../../service/RTC/RTCEvents.js");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var UIEvents = require("../../service/UI/UIEvents");
var eventEmitter = new EventEmitter();
function getMediaStreamUsage()
{
var result = {
audio: true,
video: true
};
/** There are some issues with the desktop sharing
* when this property is enabled.
* WARNING: We must change the implementation to start video/audio if we
* receive from the focus that the peer is not muted.
var isSecureConnection = window.location.protocol == "https:";
if(config.disableEarlyMediaPermissionRequests || !isSecureConnection)
{
result = {
audio: false,
video: false
};
}
**/
return result;
}
var RTC = {
rtcUtils: null,
devices: {
audio: true,
video: true
},
localStreams: [],
remoteStreams: {},
localAudio: null,
@@ -30,13 +63,16 @@ var RTC = {
eventEmitter.removeListener(eventType, listener);
},
createLocalStream: function (stream, type, change, videoType) {
createLocalStream: function (stream, type, change, videoType, isMuted, isGUMStream) {
var localStream = new LocalStream(stream, type, eventEmitter, videoType);
var localStream = new LocalStream(stream, type, eventEmitter, videoType, isGUMStream);
//in firefox we have only one stream object
if(this.localStreams.length == 0 ||
this.localStreams[0].getOriginalStream() != stream)
this.localStreams.push(localStream);
if(isMuted === true)
localStream.setMute(false);
if(type == "audio")
{
this.localAudio = localStream;
@@ -49,7 +85,7 @@ var RTC = {
if(change)
eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED;
eventEmitter.emit(eventType, localStream);
eventEmitter.emit(eventType, localStream, isMuted);
return localStream;
},
removeLocalStream: function (stream) {
@@ -111,7 +147,7 @@ var RTC = {
function (stream, isUsingScreenStream, callback) {
self.changeLocalVideo(stream, isUsingScreenStream, callback);
}, DesktopSharingEventTypes.NEW_STREAM_CREATED);
APP.xmpp.addListener(XMPPEvents.CHANGED_STREAMS, function (jid, changedStreams) {
APP.xmpp.addListener(XMPPEvents.STREAMS_CHANGED, function (jid, changedStreams) {
for(var i = 0; i < changedStreams.length; i++) {
var type = changedStreams[i].type;
if (type != "audio") {
@@ -133,7 +169,8 @@ var RTC = {
APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
DataChannels.handlePinnedEndpointEvent);
this.rtcUtils = new RTCUtils(this);
this.rtcUtils.obtainAudioAndVideoPermissions();
this.rtcUtils.obtainAudioAndVideoPermissions(
null, null, getMediaStreamUsage());
},
muteRemoteVideoStream: function (jid, value) {
var stream;
@@ -145,7 +182,7 @@ var RTC = {
}
if(!stream)
return false;
return true;
if (value != stream.muted) {
stream.setMute(value);
@@ -166,10 +203,27 @@ var RTC = {
changeLocalVideo: function (stream, isUsingScreenStream, callback) {
var oldStream = this.localVideo.getOriginalStream();
var type = (isUsingScreenStream? "screen" : "video");
this.localVideo = this.createLocalStream(stream, "video", true, type);
var localCallback = callback;
if(this.localVideo.isMuted() && this.localVideo.videoType !== type)
{
localCallback = function() {
APP.xmpp.setVideoMute(false, APP.UI.setVideoMuteButtonsState);
callback();
};
}
var videoStream = this.rtcUtils.createStream(stream, true);
this.localVideo = this.createLocalStream(videoStream, "video", true, type);
// Stop the stream to trigger onended event for old stream
oldStream.stop();
APP.xmpp.switchStreams(stream, oldStream,callback);
APP.xmpp.switchStreams(videoStream, oldStream,localCallback);
},
changeLocalAudio: function (stream, callback) {
var oldStream = this.localAudio.getOriginalStream();
var newStream = this.rtcUtils.createStream(stream);
this.localAudio = this.createLocalStream(newStream, "audio", true);
// Stop the stream to trigger onended event for old stream
oldStream.stop();
APP.xmpp.switchStreams(newStream, oldStream, callback, true);
},
/**
* Checks if video identified by given src is desktop stream.
@@ -196,6 +250,34 @@ var RTC = {
isDesktop = (stream.videoType === "screen");
return isDesktop;
},
setVideoMute: function(mute, callback, options) {
if(!this.localVideo)
return;
if (mute == APP.RTC.localVideo.isMuted())
{
APP.xmpp.sendVideoInfoPresence(mute);
if(callback)
callback(mute);
}
else
{
APP.RTC.localVideo.setMute(!mute);
APP.xmpp.setVideoMute(
mute,
callback,
options);
}
},
setDeviceAvailability: function (devices) {
if(!devices)
return;
if(devices.audio === true || devices.audio === false)
this.devices.audio = devices.audio;
if(devices.video === true || devices.video === false)
this.devices.video = devices.video;
eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, this.devices);
}
};

View File

@@ -130,7 +130,8 @@ function RTCUtils(RTCService)
if (navigator.mozGetUserMedia) {
console.log('This appears to be Firefox');
var version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
if (version >= 39) {
if (version >= 40
&& !config.enableSimulcast && config.useBundle && config.useRtcpMux) {
this.peerconnection = mozRTCPeerConnection;
this.browser = RTCBrowserType.RTC_BROWSER_FIREFOX;
this.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
@@ -143,6 +144,8 @@ function RTCUtils(RTCService)
//
// https://groups.google.com/forum/#!topic/mozilla.dev.media/pKOiioXonJg
// https://github.com/webrtc/samples/issues/302
if(!element[0])
return;
element[0].mozSrcObject = stream;
element[0].play();
};
@@ -155,10 +158,13 @@ function RTCUtils(RTCService)
return tracks[0].id.replace(/[\{,\}]/g,"");
};
this.getVideoSrc = function (element) {
if(!element)
return null;
return element.mozSrcObject;
};
this.setVideoSrc = function (element, src) {
element.mozSrcObject = src;
if(element)
element.mozSrcObject = src;
};
RTCSessionDescription = mozRTCSessionDescription;
RTCIceCandidate = mozRTCIceCandidate;
@@ -181,10 +187,13 @@ function RTCUtils(RTCService)
return stream.id.replace(/[\{,\}]/g,"");
};
this.getVideoSrc = function (element) {
if(!element)
return null;
return element.getAttribute("src");
};
this.setVideoSrc = function (element, src) {
element.setAttribute("src", src);
if(element)
element.setAttribute("src", src);
};
// DTLS should now be enabled by default but..
this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]};
@@ -225,6 +234,8 @@ RTCUtils.prototype.getUserMediaWithConstraints = function(
var isFF = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
var self = this;
try {
if (config.enableSimulcast
&& constraints.video
@@ -236,10 +247,12 @@ RTCUtils.prototype.getUserMediaWithConstraints = function(
&& !isFF) {
APP.simulcast.getUserMedia(constraints, function (stream) {
console.log('onUserMediaSuccess');
self.setAvailableDevices(um, true);
success_callback(stream);
},
function (error) {
console.warn('Failed to get access to local media. Error ', error);
self.setAvailableDevices(um, false);
if (failure_callback) {
failure_callback(error);
}
@@ -249,9 +262,11 @@ RTCUtils.prototype.getUserMediaWithConstraints = function(
this.getUserMedia(constraints,
function (stream) {
console.log('onUserMediaSuccess');
self.setAvailableDevices(um, true);
success_callback(stream);
},
function (error) {
self.setAvailableDevices(um, false);
console.warn('Failed to get access to local media. Error ',
error, constraints);
if (failure_callback) {
@@ -268,29 +283,114 @@ RTCUtils.prototype.getUserMediaWithConstraints = function(
}
};
RTCUtils.prototype.setAvailableDevices = function (um, available) {
var devices = {};
if(um.indexOf("video") != -1)
{
devices.video = available;
}
if(um.indexOf("audio") != -1)
{
devices.audio = available;
}
this.service.setDeviceAvailability(devices);
}
/**
* We ask for audio and video combined stream in order to get permissions and
* not to ask twice.
*/
RTCUtils.prototype.obtainAudioAndVideoPermissions = function() {
RTCUtils.prototype.obtainAudioAndVideoPermissions =
function(devices, callback, usageOptions)
{
var self = this;
// Get AV
this.getUserMediaWithConstraints(
['audio', 'video'],
var successCallback = function (stream) {
if(callback)
callback(stream, usageOptions);
else
self.successCallback(stream, usageOptions);
};
if(!devices)
devices = ['audio', 'video'];
var newDevices = [];
if(usageOptions)
for(var i = 0; i < devices.length; i++)
{
var device = devices[i];
if(usageOptions[device] === true)
newDevices.push(device);
}
else
newDevices = devices;
if(newDevices.length === 0)
{
successCallback();
return;
}
if (navigator.mozGetUserMedia) {
// With FF we can't split the stream into audio and video because FF
// doesn't support media stream constructors. So, we need to get the
// audio stream separately from the video stream using two distinct GUM
// calls. Not very user friendly :-( but we don't have many other
// options neither.
//
// Note that we pack those 2 streams in a single object and pass it to
// the successCallback method.
self.getUserMediaWithConstraints(
['audio'],
function (audioStream) {
self.getUserMediaWithConstraints(
['video'],
function (videoStream) {
return self.successCallback({
audioStream: audioStream,
videoStream: videoStream
});
},
function (error) {
console.error('failed to obtain video stream - stop',
error);
return self.successCallback(null);
},
config.resolution || '360');
},
function (error) {
console.error('failed to obtain audio stream - stop',
error);
return self.successCallback(null);
}
);
} else {
this.getUserMediaWithConstraints(
newDevices,
function (stream) {
self.successCallback(stream);
successCallback(stream);
},
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.successCallback = function (stream, usageOptions) {
// If this is FF, the stream parameter is *not* a MediaStream object, it's
// an object with two properties: audioStream, videoStream.
if(stream && !navigator.mozGetUserMedia)
console.log('got', stream, stream.getAudioTracks().length,
stream.getVideoTracks().length);
this.handleLocalStream(stream, usageOptions);
};
RTCUtils.prototype.errorCallback = function (error) {
@@ -321,42 +421,76 @@ RTCUtils.prototype.errorCallback = function (error) {
function (error) {
console.error('failed to obtain audio/video stream - stop',
error);
APP.UI.messageHandler.showError("dialog.error",
"dialog.failedpermissions");
return self.successCallback(null);
}
);
}
}
RTCUtils.prototype.handleLocalStream = function(stream)
RTCUtils.prototype.handleLocalStream = function(stream, usageOptions)
{
// If this is FF, the stream parameter is *not* a MediaStream object, it's
// an object with two properties: audioStream, videoStream.
var audioStream, videoStream;
if(window.webkitMediaStream)
{
var audioStream = new webkitMediaStream();
var videoStream = new webkitMediaStream();
var audioTracks = stream.getAudioTracks();
var videoTracks = stream.getVideoTracks();
for (var i = 0; i < audioTracks.length; i++) {
audioStream.addTrack(audioTracks[i]);
audioStream = new webkitMediaStream();
videoStream = new webkitMediaStream();
if(stream) {
var audioTracks = stream.getAudioTracks();
for (var i = 0; i < audioTracks.length; i++) {
audioStream.addTrack(audioTracks[i]);
}
var videoTracks = stream.getVideoTracks();
for (i = 0; i < videoTracks.length; i++) {
videoStream.addTrack(videoTracks[i]);
}
}
this.service.createLocalStream(audioStream, "audio");
for (i = 0; i < videoTracks.length; i++) {
videoStream.addTrack(videoTracks[i]);
}
this.service.createLocalStream(videoStream, "video");
}
else
{//firefox
this.service.createLocalStream(stream, "stream");
audioStream = stream.audioStream;
videoStream = stream.videoStream;
}
var audioMuted = (usageOptions && usageOptions.audio === false),
videoMuted = (usageOptions && usageOptions.video === false);
var audioGUM = (!usageOptions || usageOptions.audio !== false),
videoGUM = (!usageOptions || usageOptions.video !== false);
this.service.createLocalStream(audioStream, "audio", null, null,
audioMuted, audioGUM);
this.service.createLocalStream(videoStream, "video", null, null,
videoMuted, videoGUM);
};
RTCUtils.prototype.createStream = function(stream, isVideo)
{
var newStream = null;
if(window.webkitMediaStream)
{
newStream = new webkitMediaStream();
if(newStream)
{
var tracks = (isVideo? stream.getVideoTracks() : stream.getAudioTracks());
for (i = 0; i < tracks.length; i++) {
newStream.addTrack(tracks[i]);
}
}
}
else
newStream = stream;
return newStream;
};
module.exports = RTCUtils;

View File

@@ -26,11 +26,18 @@ var DesktopSharingEventTypes
var RTCEvents = require("../../service/RTC/RTCEvents");
var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var MemberEvents = require("../../service/members/Events");
var eventEmitter = new EventEmitter();
var roomName = null;
function notifyForInitialMute()
{
messageHandler.notify(null, "notify.mutedTitle", "connected",
"notify.muted", null, {timeOut: 120000});
}
function setupPrezi()
{
$("#reloadPresentationLink").click(function()
@@ -53,24 +60,42 @@ function setupToolbars() {
BottomToolbar.init();
}
function streamHandler(stream) {
function streamHandler(stream, isMuted) {
switch (stream.type)
{
case "audio":
VideoLayout.changeLocalAudio(stream);
VideoLayout.changeLocalAudio(stream, isMuted);
break;
case "video":
VideoLayout.changeLocalVideo(stream);
VideoLayout.changeLocalVideo(stream, isMuted);
break;
case "stream":
VideoLayout.changeLocalStream(stream);
VideoLayout.changeLocalStream(stream, isMuted);
break;
}
}
function onXmppConnectionFailed(stropheErrorMsg) {
var title = APP.translation.generateTranslatonHTML(
"dialog.error");
var message;
if (stropheErrorMsg) {
message = APP.translation.generateTranslatonHTML(
"dialog.connectErrorWithMsg", {msg: stropheErrorMsg});
} else {
message = APP.translation.generateTranslatonHTML(
"dialog.connectError");
}
messageHandler.openDialog(
title, message, true, {}, function (e, v, m, f) { return false; });
}
function onDisposeConference(unload) {
Toolbar.showAuthenticateButton(false);
};
}
function onDisplayNameChanged(jid, displayName) {
ContactList.onDisplayNameChange(jid, displayName);
@@ -105,7 +130,10 @@ function registerListeners() {
function (endpointSimulcastLayers) {
VideoLayout.onSimulcastLayersChanging(endpointSimulcastLayers);
});
APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED,
function (devices) {
VideoLayout.setDeviceAvailabilityIcons(null, devices);
})
APP.statistics.addAudioLevelListener(function(jid, audioLevel)
{
var resourceJid;
@@ -137,6 +165,7 @@ function registerListeners() {
VideoLayout.updateConnectionStats);
APP.connectionquality.addListener(CQEvents.STOP,
VideoLayout.onStatsStop);
APP.xmpp.addListener(XMPPEvents.CONNECTION_FAILED, onXmppConnectionFailed);
APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
APP.xmpp.addListener(XMPPEvents.GRACEFUL_SHUTDOWN, function () {
messageHandler.openMessageDialog(
@@ -182,7 +211,7 @@ function registerListeners() {
APP.xmpp.addListener(XMPPEvents.USER_ID_CHANGED, function (from, id) {
Avatar.setUserAvatar(from, id);
});
APP.xmpp.addListener(XMPPEvents.CHANGED_STREAMS, function (jid, changedStreams) {
APP.xmpp.addListener(XMPPEvents.STREAMS_CHANGED, function (jid, changedStreams) {
for(stream in changedStreams)
{
// might need to update the direction if participant just went from sendrecv to recvonly
@@ -204,19 +233,28 @@ function registerListeners() {
});
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.LOCAL_ROLE_CHANGED, onLocalRoleChanged);
APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, onMucMemberJoined);
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.MUC_MEMBER_LEFT, onMucMemberLeft);
APP.xmpp.addListener(XMPPEvents.PASSWORD_REQUIRED, onPasswordRequired);
APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError);
APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad);
APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED, onAuthenticationRequired);
APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED,
onAuthenticationRequired);
APP.xmpp.addListener(XMPPEvents.DEVICE_AVAILABLE,
function (resource, devices) {
VideoLayout.setDeviceAvailabilityIcons(resource, devices);
});
APP.members.addListener(MemberEvents.DTMF_SUPPORT_CHANGED,
onDtmfSupportChanged);
APP.xmpp.addListener(XMPPEvents.START_MUTED, function (audio, video) {
SettingsMenu.setStartMuted(audio, video);
});
}
@@ -230,21 +268,8 @@ function registerListeners() {
* 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);
}
},
APP.RTC.setVideoMute(mute,
UI.setVideoMuteButtonsState,
options);
}
@@ -417,13 +442,16 @@ function onMucJoined(jid, info) {
if (displayName)
onDisplayNameChanged('localVideoContainer', displayName);
VideoLayout.mucJoined();
}
function initEtherpad(name) {
Etherpad.init(name);
};
function onMucLeft(jid) {
function onMucMemberLeft(jid) {
console.log('left.muc', jid);
var displayName = $('#participant_' + Strophe.getResourceFromJid(jid) +
'>.displayname').html();
@@ -450,12 +478,13 @@ function onMucLeft(jid) {
};
function onLocalRoleChange(jid, info, pres, isModerator)
function onLocalRoleChanged(jid, info, pres, isModerator)
{
console.info("My role changed, new role: " + info.role);
onModeratorStatusChanged(isModerator);
VideoLayout.showModeratorIndicator();
SettingsMenu.onRoleChanged();
if (isModerator) {
Authentication.closeAuthenticationWindow();
@@ -479,7 +508,7 @@ function onModeratorStatusChanged(isModerator) {
}
};
function onPasswordReqiured(callback) {
function onPasswordRequired(callback) {
// password is required
Toolbar.lockLockButton();
var message = '<h2 data-i18n="dialog.passwordRequired">';
@@ -508,7 +537,17 @@ function onPasswordReqiured(callback) {
':input:first'
);
}
function onMucEntered(jid, id, displayName) {
/**
* The dialpad button is shown iff there is at least one member that supports
* DTMF (e.g. jigasi).
*/
function onDtmfSupportChanged(dtmfSupport) {
//TODO: enable when the UI is ready
//Toolbar.showDialPadButton(dtmfSupport);
}
function onMucMemberJoined(jid, id, displayName) {
messageHandler.notify(displayName,'notify.somebody',
'connected',
'notify.connected');
@@ -690,6 +729,12 @@ UI.getRoomName = function () {
return roomName;
};
UI.setInitialMuteFromFocus = function (muteAudio, muteVideo) {
if(muteAudio || muteVideo) notifyForInitialMute();
if(muteAudio) UI.setAudioMuted(true);
if(muteVideo) UI.setVideoMute(true);
}
/**
* Mutes/unmutes the local video.
*/
@@ -707,9 +752,17 @@ UI.toggleAudio = function() {
/**
* Sets muted audio state for the local participant.
*/
UI.setAudioMuted = function (mute) {
if(!APP.xmpp.setAudioMute(mute, function () {
UI.setAudioMuted = function (mute, earlyMute) {
var audioMute = null;
if(earlyMute)
audioMute = function (mute, cb) {
return APP.xmpp.sendAudioInfoPresence(mute, cb);
};
else
audioMute = function (mute, cb) {
return APP.xmpp.setAudioMute(mute, cb);
}
if(!audioMute(mute, function () {
VideoLayout.showLocalAudioIndicator(mute);
UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
@@ -743,5 +796,22 @@ UI.dockToolbar = function (isDock) {
return ToolbarToggler.dockToolbar(isDock);
}
UI.setVideoMuteButtonsState = 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);
}
}
UI.setVideoMute = setVideoMute;
module.exports = UI;

View File

@@ -21,12 +21,14 @@ var Authentication = {
// 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 title
= APP.translation.generateTranslatonHTML("dialog.WaitingForHost");
var msg
= APP.translation.generateTranslatonHTML(
"dialog.WaitForHostMsg", {room: room});
var buttonTxt
= APP.translation.generateTranslatonHTML("dialog.Authenticate");
= APP.translation.generateTranslatonHTML("dialog.IamHost");
var buttons = [];
buttons.push({title: buttonTxt, value: "authNow"});

View File

@@ -20,6 +20,12 @@ function isUserMuted(jid) {
}
}
if(jid && jid == APP.xmpp.myJid())
{
var localVideo = APP.RTC.localVideo;
return (!localVideo || localVideo.isMuted());
}
if (!APP.RTC.remoteStreams[jid] || !APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
return null;
}
@@ -119,8 +125,9 @@ var Avatar = {
} else {
if (video && video.length > 0) {
setVisibility(video, !show);
setVisibility(avatar, show);
}
setVisibility(avatar, show);
}
}
},

View File

@@ -26,7 +26,7 @@ function generateLanguagesSelectBox()
var SettingsMenu = {
init: function () {
$("#updateSettings").before(generateLanguagesSelectBox());
$("#startMutedOptions").before(generateLanguagesSelectBox());
APP.translation.translateElement($("#languages_selectbox"));
$('#settingsmenu>input').keyup(function(event){
if(event.keyCode === 13) {//enter
@@ -34,11 +34,36 @@ var SettingsMenu = {
}
});
if(APP.xmpp.isModerator())
{
$("#startMutedOptions").css("display", "block");
}
else
{
$("#startMutedOptions").css("display", "none");
}
$("#updateSettings").click(function () {
SettingsMenu.update();
});
},
onRoleChanged: function () {
if(APP.xmpp.isModerator())
{
$("#startMutedOptions").css("display", "block");
}
else
{
$("#startMutedOptions").css("display", "none");
}
},
setStartMuted: function (audio, video) {
$("#startAudioMuted").attr("checked", audio);
$("#startVideoMuted").attr("checked", video);
},
update: function() {
var newDisplayName = UIUtil.escapeHtml($('#setDisplayName').get(0).value);
var newEmail = UIUtil.escapeHtml($('#setEmail').get(0).value);
@@ -55,6 +80,10 @@ var SettingsMenu = {
APP.xmpp.addToPresence("email", newEmail);
var email = Settings.setEmail(newEmail);
var startAudioMuted = ($("#startAudioMuted").is(":checked"));
var startVideoMuted = ($("#startVideoMuted").is(":checked"));
APP.xmpp.addToPresence("startMuted",
[startAudioMuted, startVideoMuted]);
Avatar.setUserAvatar(APP.xmpp.myJid(), email);
},

View File

@@ -54,6 +54,9 @@ var buttonHandlers =
"toolbar_button_sip": function () {
return callSipButtonClicked();
},
"toolbar_button_dialpad": function () {
return dialpadButtonClicked();
},
"toolbar_button_settings": function () {
PanelToggler.toggleSettingsMenu();
},
@@ -221,6 +224,11 @@ function inviteParticipants() {
window.open("mailto:?subject=" + subject + "&body=" + body, '_blank');
}
function dialpadButtonClicked()
{
//TODO show the dialpad window
}
function callSipButtonClicked()
{
var defaultNumber
@@ -243,8 +251,7 @@ function callSipButtonClicked()
}
}
},
null,
':input:first'
null, null, ':input:first'
);
}
@@ -326,7 +333,8 @@ var Toolbar = (function (my) {
if (inviteLink) {
inviteLink.value = roomUrl;
inviteLink.select();
document.getElementById('jqi_state0_buttonInvite').disabled = false;
$('#inviteLinkRef').parent()
.find('button[value=true]').prop('disabled', false);
}
};
@@ -419,12 +427,13 @@ var Toolbar = (function (my) {
}
}
},
function () {
function (event) {
if (roomUrl) {
document.getElementById('inviteLinkRef').select();
} else {
document.getElementById('jqi_state0_buttonInvite')
.disabled = true;
if (event && event.target)
$(event.target)
.find('button[value=true]').prop('disabled', true);
}
}
);
@@ -550,12 +559,13 @@ var Toolbar = (function (my) {
// Sets the state of the recording button
my.setRecordingButtonState = function (isRecording) {
var selector = $('#recordButton');
if (isRecording) {
$('#recordButton').removeClass("icon-recEnable");
$('#recordButton').addClass("icon-recEnable active");
selector.removeClass("icon-recEnable");
selector.addClass("icon-recEnable active");
} else {
$('#recordButton').removeClass("icon-recEnable active");
$('#recordButton').addClass("icon-recEnable");
selector.removeClass("icon-recEnable active");
selector.addClass("icon-recEnable");
}
};
@@ -568,14 +578,24 @@ var Toolbar = (function (my) {
}
};
// Shows or hides the dialpad button
my.showDialPadButton = function (show) {
if (show) {
$('#dialPadButton').css({display: "inline-block"});
} else {
$('#dialPadButton').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);
var selector = $('#toolbar_auth_identity');
selector.css({display: "list-item"});
selector.text(authIdentity);
} else {
$('#toolbar_auth_identity').css({display: "none"});
}

View File

@@ -15,6 +15,9 @@ function showDesktopSharingButton() {
* Hides the toolbar.
*/
function hideToolbar() {
if(config.alwaysVisibleToolbar)
return;
var header = $("#header"),
bottomToolbar = $("#bottomToolbar");
var isToolbarHover = false;

View File

@@ -175,7 +175,7 @@ var messageHandler = (function(my) {
};
my.notify = function(displayName, displayNameKey,
cls, messageKey, messageArguments) {
cls, messageKey, messageArguments, options) {
var displayNameSpan = '<span class="nickname" ';
if(displayName)
{
@@ -195,7 +195,7 @@ var messageHandler = (function(my) {
: "") + ">" +
APP.translation.translateString(messageKey,
messageArguments) +
'</span>');
'</span>', null, options);
};
return my;

View File

@@ -550,24 +550,58 @@ var VideoLayout = (function (my) {
|| (lastNEndpointsCache && lastNEndpointsCache.indexOf(resource) !== -1);
};
my.changeLocalStream = function (stream) {
VideoLayout.changeLocalVideo(stream);
my.changeLocalStream = function (stream, isMuted) {
VideoLayout.changeLocalVideo(stream, isMuted);
};
my.changeLocalAudio = function(stream) {
my.changeLocalAudio = function(stream, isMuted) {
if(isMuted)
APP.UI.setAudioMuted(true, true);
APP.RTC.attachMediaStream($('#localAudio'), stream.getOriginalStream());
document.getElementById('localAudio').autoplay = true;
document.getElementById('localAudio').volume = 0;
if (preMuted) {
if(!APP.UI.setAudioMuted(true))
{
preMuted = mute;
}
preMuted = false;
}
};
my.changeLocalVideo = function(stream) {
my.changeLocalVideo = function(stream, isMuted) {
// Set default display name.
setDisplayName('localVideoContainer');
if(!VideoLayout.connectionIndicators["localVideoContainer"]) {
VideoLayout.connectionIndicators["localVideoContainer"]
= new ConnectionIndicator($("#localVideoContainer")[0], null, VideoLayout);
}
AudioLevels.updateAudioLevelCanvas(null, VideoLayout);
var localVideo = null;
function localVideoClick(event) {
event.stopPropagation();
VideoLayout.handleVideoThumbClicked(
APP.RTC.getVideoSrc(localVideo),
false,
APP.xmpp.myResource());
}
$('#localVideoContainer').click(localVideoClick);
// Add hover handler
$('#localVideoContainer').hover(
function() {
VideoLayout.showDisplayName('localVideoContainer', true);
},
function() {
if (!VideoLayout.isLargeVideoVisible()
|| APP.RTC.getVideoSrc(localVideo) !== APP.RTC.getVideoSrc($('#largeVideo')[0]))
VideoLayout.showDisplayName('localVideoContainer', false);
}
);
if(isMuted)
{
APP.UI.setVideoMute(true);
return;
}
var flipX = true;
if(stream.videoType == "screen")
flipX = false;
@@ -581,55 +615,29 @@ var VideoLayout = (function (my) {
var localVideoContainer = document.getElementById('localVideoWrapper');
localVideoContainer.appendChild(localVideo);
// Set default display name.
setDisplayName('localVideoContainer');
if(!VideoLayout.connectionIndicators["localVideoContainer"]) {
VideoLayout.connectionIndicators["localVideoContainer"]
= new ConnectionIndicator($("#localVideoContainer")[0], null, VideoLayout);
}
AudioLevels.updateAudioLevelCanvas(null, VideoLayout);
var localVideoSelector = $('#' + localVideo.id);
function localVideoClick(event) {
event.stopPropagation();
VideoLayout.handleVideoThumbClicked(
APP.RTC.getVideoSrc(localVideo),
false,
APP.xmpp.myResource());
}
// Add click handler to both video and video wrapper elements in case
// there's no video.
localVideoSelector.click(localVideoClick);
$('#localVideoContainer').click(localVideoClick);
// Add hover handler
$('#localVideoContainer').hover(
function() {
VideoLayout.showDisplayName('localVideoContainer', true);
},
function() {
if (!VideoLayout.isLargeVideoVisible()
|| APP.RTC.getVideoSrc(localVideo) !== APP.RTC.getVideoSrc($('#largeVideo')[0]))
VideoLayout.showDisplayName('localVideoContainer', false);
}
);
// Add stream ended handler
stream.getOriginalStream().onended = function () {
localVideoContainer.removeChild(localVideo);
VideoLayout.updateRemovedVideo(APP.RTC.getVideoSrc(localVideo));
};
// Flip video x axis if needed
flipXLocalVideo = flipX;
if (flipX) {
localVideoSelector.addClass("flipVideoX");
}
// Attach WebRTC stream
var videoStream = APP.simulcast.getLocalVideoStream();
APP.RTC.attachMediaStream(localVideoSelector, videoStream);
// Add stream ended handler
stream.getOriginalStream().onended = function () {
localVideoContainer.removeChild(localVideo);
VideoLayout.updateRemovedVideo(APP.RTC.getVideoSrc(localVideo));
};
localVideoSrc = APP.RTC.getVideoSrc(localVideo);
var myResourceJid = APP.xmpp.myResource();
@@ -639,6 +647,56 @@ var VideoLayout = (function (my) {
};
my.mucJoined = function () {
var myResourceJid = APP.xmpp.myResource();
if(!largeVideoState.userResourceJid)
VideoLayout.updateLargeVideo(localVideoSrc, 0,
myResourceJid, true);
};
/**
* Adds or removes icons for not available camera and microphone.
* @param resourceJid the jid of user
* @param devices available devices
*/
my.setDeviceAvailabilityIcons = function (resourceJid, devices) {
if(!devices)
return;
var container = null
if(!resourceJid)
{
container = $("#localVideoContainer")[0];
}
else
{
container = $("#participant_" + resourceJid)[0];
}
if(!container)
return;
$("#" + container.id + " > .noMic").remove();
$("#" + container.id + " > .noVideo").remove();
if(!devices.audio)
{
container.appendChild(document.createElement("div")).setAttribute("class","noMic");
}
if(!devices.video)
{
container.appendChild(document.createElement("div")).setAttribute("class","noVideo");
}
if(!devices.audio && !devices.video)
{
$("#" + container.id + " > .noMic").css("background-position", "75%");
$("#" + container.id + " > .noVideo").css("background-position", "25%");
$("#" + container.id + " > .noVideo").css("background-color", "transparent");
}
}
/**
* Checks if removed video is currently displayed and tries to display
* another one instead.
@@ -664,26 +722,34 @@ var VideoLayout = (function (my) {
}
}
var src = null, volume = null;
// mute if localvideo
if (pick) {
var container = pick.parentNode;
var jid = null;
if(container)
{
if(container.id == "localVideoWrapper")
{
jid = APP.xmpp.myResource();
}
else
{
jid = VideoLayout.getPeerContainerResourceJid(container);
}
}
VideoLayout.updateLargeVideo(APP.RTC.getVideoSrc(pick), pick.volume, jid);
src = APP.RTC.getVideoSrc(pick);
volume = pick.volume;
} else {
console.warn("Failed to elect large video");
container = $('#remoteVideos>span[id!="mixedstream"]:visible:last').get(0);
}
var jid = null;
if(container)
{
if(container.id == "localVideoWrapper")
{
jid = APP.xmpp.myResource();
}
else
{
jid = VideoLayout.getPeerContainerResourceJid(container);
}
}
else
return;
VideoLayout.updateLargeVideo(src, volume, jid);
}
};
@@ -713,7 +779,9 @@ var VideoLayout = (function (my) {
container.id = 'mixedstream';
container.className = 'videocontainer';
remotes.appendChild(container);
UIUtil.playSoundNotification('userJoined');
if(!config.startAudioMuted ||
config.startAudioMuted > APP.members.size())
UIUtil.playSoundNotification('userJoined');
}
if (container) {
@@ -732,11 +800,10 @@ var VideoLayout = (function (my) {
/**
* Updates the large video with the given new video source.
*/
my.updateLargeVideo = function(newSrc, vol, resourceJid) {
console.log('hover in', newSrc);
if (APP.RTC.getVideoSrc($('#largeVideo')[0]) !== newSrc) {
my.updateLargeVideo = function(newSrc, vol, resourceJid, forceUpdate) {
console.log('hover in', newSrc, resourceJid);
if (APP.RTC.getVideoSrc($('#largeVideo')[0]) !== newSrc || forceUpdate) {
$('#activeSpeaker').css('visibility', 'hidden');
// Due to the simulcast the localVideoSrc may have changed when the
// fadeOut event triggers. In that case the getJidFromVideoSrc and
@@ -774,7 +841,6 @@ var VideoLayout = (function (my) {
largeVideoState.updateInProgress = true;
var doUpdate = function () {
Avatar.updateActiveSpeakerAvatarSrc(
APP.xmpp.findJidFromResource(
largeVideoState.userResourceJid));
@@ -1137,7 +1203,7 @@ var VideoLayout = (function (my) {
console.log('stream ended', this);
VideoLayout.removeRemoteStreamElement(
stream, isVideo, container);
stream, isVideo, container, newElementId);
// NOTE(gp) it seems that under certain circumstances, the
// onended event is not fired and thus the contact list is not
@@ -1207,14 +1273,14 @@ var VideoLayout = (function (my) {
* @param isVideo <tt>true</tt> if given <tt>stream</tt> is a video one.
* @param container
*/
my.removeRemoteStreamElement = function (stream, isVideo, container) {
my.removeRemoteStreamElement = function (stream, isVideo, container, id) {
if (!container)
return;
var select = null;
var removedVideoSrc = null;
if (isVideo) {
select = $('#' + container.id + '>video');
select = $('#' + id);
removedVideoSrc = APP.RTC.getVideoSrc(select.get(0));
}
else
@@ -1236,7 +1302,9 @@ var VideoLayout = (function (my) {
// Remove whole container
container.remove();
UIUtil.playSoundNotification('userLeft');
if(!config.startAudioMuted ||
config.startAudioMuted > APP.members.size())
UIUtil.playSoundNotification('userLeft');
VideoLayout.resizeThumbnails();
}
@@ -1619,10 +1687,9 @@ var VideoLayout = (function (my) {
if (videoSpan.classList.contains("dominantspeaker"))
videoSpan.classList.remove("dominantspeaker");
}
Avatar.showUserAvatar(
APP.xmpp.findJidFromResource(resourceJid));
}
Avatar.showUserAvatar(
APP.xmpp.findJidFromResource(resourceJid));
};
/**

View File

@@ -0,0 +1,40 @@
var params = {};
function getConfigParamsFromUrl() {
if(!location.hash)
return {};
var hash = location.hash.substr(1);
var result = {};
hash.split("&").forEach(function(part) {
var item = part.split("=");
result[item[0]] = JSON.parse(
decodeURIComponent(item[1]).replace(/\\&/, "&"));
});
return result;
}
params = getConfigParamsFromUrl();
var URLProcessor = {
setConfigParametersFromUrl: function () {
for(var k in params)
{
if(typeof k !== "string" || k.indexOf("config.") === -1)
continue;
var v = params[k];
var confKey = k.substr(7);
if(config[confKey] && typeof config[confKey] !== typeof v)
{
console.warn("The type of " + k +
" is wrong. That parameter won't be updated in config.js.");
continue;
}
config[confKey] = v;
}
}
};
module.exports = URLProcessor;

View File

@@ -237,7 +237,14 @@ function initInlineInstalls()
$("link[rel=chrome-webstore-item]").attr("href", getWebStoreInstallUrl());
}
function getSwitchStreamFailed(error) {
function getVideoStreamFailed(error) {
console.error("Failed to obtain the stream to switch to", error);
switchInProgress = false;
isUsingScreenStream = false;
newStreamCreated(null);
}
function getDesktopStreamFailed(error) {
console.error("Failed to obtain the stream to switch to", error);
switchInProgress = false;
}
@@ -341,7 +348,7 @@ module.exports = {
);
newStreamCreated(stream);
},
getSwitchStreamFailed);
getDesktopStreamFailed);
} else {
// Disable screen stream
APP.RTC.getUserMediaWithConstraints(
@@ -351,7 +358,7 @@ module.exports = {
isUsingScreenStream = false;
newStreamCreated(stream);
},
getSwitchStreamFailed, config.resolution || '360'
getVideoStreamFailed, config.resolution || '360'
);
}
}

View File

@@ -0,0 +1,129 @@
/* global APP */
/**
* This module is meant to (eventually) contain and manage all information
* about members/participants of the conference, so that other modules don't
* have to do it on their own, and so that other modules can access members'
* information from a single place.
*
* Currently this module only manages information about the support of jingle
* DTMF of the members. Other fields, as well as accessor methods are meant to
* be added as needed.
*/
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var Events = require("../../service/members/Events");
var EventEmitter = require("events");
var eventEmitter = new EventEmitter();
/**
* The actual container.
*/
var members = {};
/**
* There is at least one member that supports DTMF (i.e. is jigasi).
*/
var atLeastOneDtmf = false;
function registerListeners() {
APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, onMucMemberJoined);
APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, onMucMemberLeft);
}
/**
* Handles a new member joining the MUC.
*/
function onMucMemberJoined(jid, id, displayName) {
var member = {
displayName: displayName
};
APP.xmpp.getConnection().disco.info(
jid, "" /* node */, function(iq) { onDiscoInfoReceived(jid, iq); });
members[jid] = member;
}
/**
* Handles a member leaving the MUC.
*/
function onMucMemberLeft(jid) {
delete members[jid];
updateAtLeastOneDtmf();
}
/**
* Handles the reception of a disco#info packet from a particular JID.
* @param jid the JID sending the packet.
* @param iq the packet.
*/
function onDiscoInfoReceived(jid, iq) {
if (!members[jid])
return;
var supportsDtmf
= $(iq).find('>query>feature[var="urn:xmpp:jingle:dtmf:0"]').length > 0;
updateDtmf(jid, supportsDtmf);
}
/**
* Updates the 'supportsDtmf' field for a member.
* @param jid the jid of the member.
* @param newValue the new value for the 'supportsDtmf' field.
*/
function updateDtmf(jid, newValue) {
var oldValue = members[jid].supportsDtmf;
members[jid].supportsDtmf = newValue;
if (newValue != oldValue) {
updateAtLeastOneDtmf();
}
}
/**
* Checks each member's 'supportsDtmf' field and updates
* 'atLastOneSupportsDtmf'.
*/
function updateAtLeastOneDtmf(){
var newAtLeastOneDtmf = false;
for (var key in members) {
if (typeof members[key].supportsDtmf !== 'undefined'
&& members[key].supportsDtmf) {
newAtLeastOneDtmf= true;
break;
}
}
if (atLeastOneDtmf != newAtLeastOneDtmf) {
atLeastOneDtmf = newAtLeastOneDtmf;
eventEmitter.emit(Events.DTMF_SUPPORT_CHANGED, atLeastOneDtmf);
}
}
/**
* Exported interface.
*/
var Members = {
start: function(){
registerListeners();
},
addListener: function(type, listener)
{
eventEmitter.on(type, listener);
},
removeListener: function (type, listener) {
eventEmitter.removeListener(type, listener);
},
size: function () {
return Object.keys(members).length;
},
getMembers: function () {
return members;
}
};
module.exports = Members;

View File

@@ -260,7 +260,7 @@ StatsCollector.prototype.start = function ()
);
}
if(!config.disableStats) {
if(!config.disableStats && !navigator.mozGetUserMedia) {
this.statsIntervalId = setInterval(
function () {
// Interval updates
@@ -294,7 +294,7 @@ StatsCollector.prototype.start = function ()
);
}
if (config.logStats) {
if (config.logStats && !navigator.mozGetUserMedia) {
this.gatherStatsIntervalId = setInterval(
function () {
self.peerconnection.getStats(
@@ -696,9 +696,10 @@ StatsCollector.prototype.processAudioLevelReport = function ()
var ssrc = getStatValue(now, 'ssrc');
var jid = APP.xmpp.getJidFromSSRC(ssrc);
if (!jid && (Date.now() - now.timestamp) < 3000)
if (!jid)
{
console.warn("No jid for ssrc: " + ssrc);
if((Date.now() - now.timestamp) < 3000)
console.warn("No jid for ssrc: " + ssrc);
continue;
}

View File

@@ -0,0 +1,97 @@
var transform = require('sdp-transform');
exports.write = function(session, opts) {
if (typeof session !== 'undefined' &&
typeof session.media !== 'undefined' &&
Array.isArray(session.media)) {
session.media.forEach(function (mLine) {
// expand sources to ssrcs
if (typeof mLine.sources !== 'undefined' &&
Object.keys(mLine.sources).length !== 0) {
mLine.ssrcs = [];
Object.keys(mLine.sources).forEach(function (ssrc) {
var source = mLine.sources[ssrc];
Object.keys(source).forEach(function (attribute) {
mLine.ssrcs.push({
id: ssrc,
attribute: attribute,
value: source[attribute]
});
});
});
delete mLine.sources;
}
// join ssrcs in ssrc groups
if (typeof mLine.ssrcGroups !== 'undefined' &&
Array.isArray(mLine.ssrcGroups)) {
mLine.ssrcGroups.forEach(function (ssrcGroup) {
if (typeof ssrcGroup.ssrcs !== 'undefined' &&
Array.isArray(ssrcGroup.ssrcs)) {
ssrcGroup.ssrcs = ssrcGroup.ssrcs.join(' ');
}
});
}
});
}
// join group mids
if (typeof session !== 'undefined' &&
typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {
session.groups.forEach(function (g) {
if (typeof g.mids !== 'undefined' && Array.isArray(g.mids)) {
g.mids = g.mids.join(' ');
}
});
}
return transform.write(session, opts);
};
exports.parse = function(sdp) {
var session = transform.parse(sdp);
if (typeof session !== 'undefined' && typeof session.media !== 'undefined' &&
Array.isArray(session.media)) {
session.media.forEach(function (mLine) {
// group sources attributes by ssrc
if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {
mLine.sources = {};
mLine.ssrcs.forEach(function (ssrc) {
if (!mLine.sources[ssrc.id])
mLine.sources[ssrc.id] = {};
mLine.sources[ssrc.id][ssrc.attribute] = ssrc.value;
});
delete mLine.ssrcs;
}
// split ssrcs in ssrc groups
if (typeof mLine.ssrcGroups !== 'undefined' &&
Array.isArray(mLine.ssrcGroups)) {
mLine.ssrcGroups.forEach(function (ssrcGroup) {
if (typeof ssrcGroup.ssrcs === 'string') {
ssrcGroup.ssrcs = ssrcGroup.ssrcs.split(' ');
}
});
}
});
}
// split group mids
if (typeof session !== 'undefined' &&
typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {
session.groups.forEach(function (g) {
if (typeof g.mids === 'string') {
g.mids = g.mids.split(' ');
}
});
}
return session;
};

View File

@@ -4,6 +4,7 @@ var SDPDiffer = require("./SDPDiffer");
var SDPUtil = require("./SDPUtil");
var SDP = require("./SDP");
var RTCBrowserType = require("../../service/RTC/RTCBrowserType");
var async = require("async");
// Jingle stuff
function JingleSession(me, sid, connection, service) {
@@ -52,10 +53,21 @@ function JingleSession(me, sid, connection, service) {
* by the application logic.
*/
this.videoMuteByUser = false;
this.modifySourcesQueue = async.queue(this._modifySources.bind(this), 1);
// We start with the queue paused. We resume it when the signaling state is
// stable and the ice connection state is connected.
this.modifySourcesQueue.pause();
}
//TODO: this array must be removed when firefox implement multistream support
JingleSession.notReceivedSSRCs = [];
JingleSession.prototype.updateModifySourcesQueue = function() {
var signalingState = this.peerconnection.signalingState;
var iceConnectionState = this.peerconnection.iceConnectionState;
if (signalingState === 'stable' && iceConnectionState === 'connected') {
this.modifySourcesQueue.resume();
} else {
this.modifySourcesQueue.pause();
}
};
JingleSession.prototype.initiate = function (peerjid, isInitiator) {
var self = this;
@@ -82,8 +94,16 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
self.sendIceCandidate(event.candidate);
};
this.peerconnection.onaddstream = function (event) {
console.log("REMOTE STREAM ADDED: " + event.stream + " - " + event.stream.id);
self.remoteStreamAdded(event);
if (event.stream.id !== 'default') {
console.log("REMOTE STREAM ADDED: " + event.stream + " - " + event.stream.id);
self.remoteStreamAdded(event);
} else {
// This is a recvonly stream. Clients that implement Unified Plan,
// such as Firefox use recvonly "streams/channels/tracks" for
// receiving remote stream/tracks, as opposed to Plan B where there
// are only 3 channels: audio, video and data.
console.log("RECVONLY REMOTE STREAM IGNORED: " + event.stream + " - " + event.stream.id);
}
};
this.peerconnection.onremovestream = function (event) {
// Remove the stream from remoteStreams
@@ -92,9 +112,11 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
};
this.peerconnection.onsignalingstatechange = function (event) {
if (!(self && self.peerconnection)) return;
self.updateModifySourcesQueue();
};
this.peerconnection.oniceconnectionstatechange = function (event) {
if (!(self && self.peerconnection)) return;
self.updateModifySourcesQueue();
switch (self.peerconnection.iceConnectionState) {
case 'connected':
this.startTime = new Date();
@@ -774,7 +796,17 @@ JingleSession.prototype.addSource = function (elem, fromJid) {
});
sdp.raw = sdp.session + sdp.media.join('');
});
this.modifySources();
this.modifySourcesQueue.push(function() {
// When a source is added and if this is FF, a new channel is allocated
// for receiving the added source. We need to diffuse the SSRC of this
// new recvonly channel to the rest of the peers.
console.log('modify sources done');
var newSdp = new SDP(self.peerconnection.localDescription.sdp);
console.log("SDPs", mySdp, newSdp);
self.notifyMySSRCUpdate(mySdp, newSdp);
});
};
JingleSession.prototype.removeSource = function (elem, fromJid) {
@@ -835,11 +867,22 @@ JingleSession.prototype.removeSource = function (elem, fromJid) {
});
sdp.raw = sdp.session + sdp.media.join('');
});
this.modifySources();
this.modifySourcesQueue.push(function() {
// When a source is removed and if this is FF, the recvonly channel that
// receives the remote stream is deactivated . We need to diffuse the
// recvonly SSRC removal to the rest of the peers.
console.log('modify sources done');
var newSdp = new SDP(self.peerconnection.localDescription.sdp);
console.log("SDPs", mySdp, newSdp);
self.notifyMySSRCUpdate(mySdp, newSdp);
});
};
JingleSession.prototype.modifySources = function (successCallback) {
JingleSession.prototype._modifySources = function (successCallback, queueCallback) {
var self = this;
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
@@ -847,21 +890,7 @@ JingleSession.prototype.modifySources = function (successCallback) {
if(successCallback){
successCallback();
}
return;
}
// FIXME: this is a big hack
// https://code.google.com/p/webrtc/issues/detail?id=2688
// ^ has been fixed.
if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')) {
console.warn('modifySources not yet', this.peerconnection.signalingState, this.peerconnection.iceConnectionState);
this.wait = true;
window.setTimeout(function() { self.modifySources(successCallback); }, 250);
return;
}
if (this.wait) {
window.setTimeout(function() { self.modifySources(successCallback); }, 2500);
this.wait = false;
queueCallback();
return;
}
@@ -901,6 +930,7 @@ JingleSession.prototype.modifySources = function (successCallback) {
if(self.signalingState == 'closed') {
console.error("createAnswer attempt on closed state");
queueCallback("createAnswer attempt on closed state");
return;
}
@@ -937,30 +967,35 @@ JingleSession.prototype.modifySources = function (successCallback) {
if(successCallback){
successCallback();
}
queueCallback();
},
function(error) {
console.error('modified setLocalDescription failed', error);
queueCallback(error);
}
);
},
function(error) {
console.error('modified answer failed', error);
queueCallback(error);
}
);
},
function(error) {
console.error('modify failed', error);
queueCallback(error);
}
);
};
/**
* Switches video streams.
* @param new_stream new stream that will be used as video of this session.
* @param oldStream old video stream of this session.
* @param success_callback callback executed after successful stream switch.
*/
JingleSession.prototype.switchStreams = function (new_stream, oldStream, success_callback) {
JingleSession.prototype.switchStreams = function (new_stream, oldStream, success_callback, isAudio) {
var self = this;
@@ -971,10 +1006,12 @@ JingleSession.prototype.switchStreams = function (new_stream, oldStream, success
oldSdp = new SDP(self.peerconnection.localDescription.sdp);
}
self.peerconnection.removeStream(oldStream, true);
self.peerconnection.addStream(new_stream);
if(new_stream)
self.peerconnection.addStream(new_stream);
}
APP.RTC.switchVideoStreams(new_stream, oldStream);
if(!isAudio)
APP.RTC.switchVideoStreams(new_stream, oldStream);
// Conference is not active
if(!oldSdp || !self.peerconnection) {
@@ -983,7 +1020,7 @@ JingleSession.prototype.switchStreams = function (new_stream, oldStream, success
}
self.switchstreams = true;
self.modifySources(function() {
self.modifySourcesQueue.push(function() {
console.log('modify sources done');
success_callback();
@@ -1056,26 +1093,6 @@ JingleSession.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
}
};
/**
* Determines whether the (local) video is mute i.e. all video tracks are
* disabled.
*
* @return <tt>true</tt> if the (local) video is mute i.e. all video tracks are
* disabled; otherwise, <tt>false</tt>
*/
JingleSession.prototype.isVideoMute = function () {
var tracks = APP.RTC.localVideo.getVideoTracks();
var mute = true;
for (var i = 0; i < tracks.length; ++i) {
if (tracks[i].enabled) {
mute = false;
break;
}
}
return mute;
};
/**
* Mutes/unmutes the (local) video i.e. enables/disables all video tracks.
*
@@ -1111,13 +1128,23 @@ JingleSession.prototype.setVideoMute = function (mute, callback, options) {
this.hardMuteVideo(mute);
this.modifySources(callback(mute));
};
var self = this;
var oldSdp = null;
if(self.peerconnection) {
if(self.peerconnection.localDescription) {
oldSdp = new SDP(self.peerconnection.localDescription.sdp);
}
}
// SDP-based mute by going recvonly/sendrecv
// FIXME: should probably black out the screen as well
JingleSession.prototype.toggleVideoMute = function (callback) {
this.service.setVideoMute(APP.RTC.localVideo.isMuted(), callback);
this.modifySourcesQueue.push(function() {
console.log('modify sources done');
callback(mute);
var newSdp = new SDP(self.peerconnection.localDescription.sdp);
console.log("SDPs", oldSdp, newSdp);
self.notifyMySSRCUpdate(oldSdp, newSdp);
});
};
JingleSession.prototype.hardMuteVideo = function (muted) {

View File

@@ -179,7 +179,7 @@ TraceablePeerConnection.prototype.setLocalDescription = function (description, s
this.trace('setLocalDescription::preTransform (Plan B)', dumpSDP(description));
// if we're running on FF, transform to Plan A first.
if (navigator.mozGetUserMedia) {
description = this.interop.toPlanA(description);
description = this.interop.toUnifiedPlan(description);
} else {
description = APP.simulcast.transformLocalDescription(description);
}
@@ -206,7 +206,7 @@ TraceablePeerConnection.prototype.setRemoteDescription = function (description,
this.trace('setRemoteDescription::preTransform (Plan B)', dumpSDP(description));
// if we're running on FF, transform to Plan A first.
if (navigator.mozGetUserMedia) {
description = this.interop.toPlanA(description);
description = this.interop.toUnifiedPlan(description);
}
else {
description = APP.simulcast.transformRemoteDescription(description);

View File

@@ -85,7 +85,7 @@ var Moderator = {
}
},
onMucLeft: function (jid) {
onMucMemberLeft: function (jid) {
console.info("Someone left is it focus ? " + jid);
var resource = Strophe.getResourceFromJid(jid);
if (resource === 'focus' && !this.xmppService.sessionTerminated) {
@@ -177,11 +177,18 @@ var Moderator = {
{ name: 'openSctp', value: config.openSctp})
.up();
}
if (config.enableFirefoxSupport !== undefined) {
if(config.startAudioMuted !== undefined)
{
elem.c(
'property',
{ name: 'enableFirefoxHacks',
value: config.enableFirefoxSupport})
{ name: 'startAudioMuted', value: config.startAudioMuted})
.up();
}
if(config.startVideoMuted !== undefined)
{
elem.c(
'property',
{ name: 'startVideoMuted', value: config.startVideoMuted})
.up();
}
elem.up();

View File

@@ -28,6 +28,15 @@ module.exports = function(XMPP, eventEmitter) {
initPresenceMap: function (myroomjid) {
this.presMap['to'] = myroomjid;
this.presMap['xns'] = 'http://jabber.org/protocol/muc';
if(APP.RTC.localAudio.isMuted())
{
this.addAudioInfoToPresence(true);
}
if(APP.RTC.localVideo.isMuted())
{
this.addVideoInfoToPresence(true);
}
},
doJoin: function (jid, password) {
this.myroomjid = jid;
@@ -143,6 +152,32 @@ module.exports = function(XMPP, eventEmitter) {
$(document).trigger('videomuted.muc', [from, videoMuted.text()]);
}
var startMuted = $(pres).find('>startmuted');
if (startMuted.length)
{
eventEmitter.emit(XMPPEvents.START_MUTED,
startMuted.attr("audio") === "true", startMuted.attr("video") === "true");
}
var devices = $(pres).find('>devices');
if(devices.length)
{
var audio = devices.find('>audio');
var video = devices.find('>video');
var devicesValues = {audio: false, video: false};
if(audio.length && audio.text() === "true")
{
devicesValues.audio = true;
}
if(video.length && video.text() === "true")
{
devicesValues.video = true;
}
eventEmitter.emit(XMPPEvents.DEVICE_AVAILABLE,
Strophe.getResourceFromJid(from), devicesValues);
}
var stats = $(pres).find('>stats');
if (stats.length) {
var statsObj = {};
@@ -182,7 +217,7 @@ module.exports = function(XMPP, eventEmitter) {
if (this.role !== member.role) {
this.role = member.role;
eventEmitter.emit(XMPPEvents.LOCALROLE_CHANGED,
eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED,
from, member, pres, Moderator.isModerator());
}
if (!this.joined) {
@@ -200,12 +235,12 @@ module.exports = function(XMPP, eventEmitter) {
console.info("Ignore focus: " + from + ", real JID: " + member.jid);
}
else {
var id = $(pres).find('>userID').text();
var id = $(pres).find('>userId').text();
var email = $(pres).find('>email');
if (email.length > 0) {
id = email.text();
}
eventEmitter.emit(XMPPEvents.MUC_ENTER, from, id, member.displayName);
eventEmitter.emit(XMPPEvents.MUC_MEMBER_JOINED, from, id, member.displayName);
}
} else {
// Presence update for existing participant
@@ -243,6 +278,15 @@ module.exports = function(XMPP, eventEmitter) {
eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason);
return true;
}
var self = this;
// Remove old ssrcs coming from the jid
Object.keys(this.ssrc2jid).forEach(function (ssrc) {
if (self.ssrc2jid[ssrc] == from) {
delete self.ssrc2jid[ssrc];
}
});
// Status code 110 indicates that this notification is "self-presence".
if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
delete this.members[from];
@@ -386,6 +430,10 @@ module.exports = function(XMPP, eventEmitter) {
});
},
sendPresence: function () {
if (!this.presMap['to']) {
// Too early to send presence - not initialized
return;
}
var pres = $pres({to: this.presMap['to'] });
pres.c('x', {xmlns: this.presMap['xns']});
@@ -422,6 +470,11 @@ module.exports = function(XMPP, eventEmitter) {
.t(this.presMap['displayName']).up();
}
if(this.presMap["devices"])
{
pres.c('devices').c('audio').t(this.presMap['devices'].audio).up()
.c('video').t(this.presMap['devices'].video).up().up();
}
if (this.presMap['audions']) {
pres.c('audiomuted', {xmlns: this.presMap['audions']})
.t(this.presMap['audiomuted']).up();
@@ -469,6 +522,15 @@ module.exports = function(XMPP, eventEmitter) {
|| 'sendrecv' }
).up();
}
pres.up();
}
if(this.presMap["startMuted"] !== undefined)
{
pres.c("startmuted", {audio: this.presMap["startMuted"].audio,
video: this.presMap["startMuted"].video,
xmlns: "http://jitsi.org/jitmeet/start-muted"});
delete this.presMap["startMuted"];
}
pres.up();
@@ -485,6 +547,9 @@ module.exports = function(XMPP, eventEmitter) {
this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
this.presMap['source' + sourceNumber + '_direction'] = direction;
},
addDevicesToPresence: function (devices) {
this.presMap['devices'] = devices;
},
clearPresenceMedia: function () {
var self = this;
Object.keys(this.presMap).forEach(function (key) {
@@ -546,6 +611,9 @@ module.exports = function(XMPP, eventEmitter) {
addUserIdToPresence: function (userId) {
this.presMap['userId'] = userId;
},
addStartMutedToPresence: function (audio, video) {
this.presMap["startMuted"] = {audio: audio, video: video};
},
isModerator: function () {
return this.role === 'moderator';
},
@@ -557,7 +625,7 @@ module.exports = function(XMPP, eventEmitter) {
},
onParticipantLeft: function (jid) {
eventEmitter.emit(XMPPEvents.MUC_LEFT, jid);
eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid);
this.connection.jingle.terminateByJid(jid);
@@ -566,7 +634,7 @@ module.exports = function(XMPP, eventEmitter) {
[jid, this.getPrezi(jid)]);
}
Moderator.onMucLeft(jid);
Moderator.onMucMemberLeft(jid);
},
parsePresence: function (from, memeber, pres) {
if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
@@ -590,8 +658,6 @@ module.exports = function(XMPP, eventEmitter) {
//console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
var ssrcV = ssrc.getAttribute('ssrc');
self.ssrc2jid[ssrcV] = from;
JingleSession.notReceivedSSRCs.push(ssrcV);
var type = ssrc.getAttribute('type');
@@ -601,7 +667,7 @@ module.exports = function(XMPP, eventEmitter) {
});
eventEmitter.emit(XMPPEvents.CHANGED_STREAMS, from, changedStreams);
eventEmitter.emit(XMPPEvents.STREAMS_CHANGED, from, changedStreams);
var displayName = !config.displayJids
? memeber.displayName : Strophe.getResourceFromJid(from);

View File

@@ -108,6 +108,14 @@ module.exports = function(XMPP, eventEmitter)
// see http://xmpp.org/extensions/xep-0166.html#concepts-session
switch (action) {
case 'session-initiate':
var startMuted = $(iq).find('jingle>startmuted');
if(startMuted && startMuted.length > 0)
{
var audioMuted = startMuted.attr("audio");
var videoMuted = startMuted.attr("video");
APP.UI.setInitialMuteFromFocus((audioMuted === "true"),
(videoMuted === "true"));
}
sess = new JingleSession(
$(iq).attr('to'), $(iq).find('jingle').attr('sid'),
this.connection, XMPP);

View File

@@ -3,58 +3,151 @@ var Moderator = require("./moderator");
var EventEmitter = require("events");
var Recording = require("./recording");
var SDP = require("./SDP");
var Settings = require("../settings/Settings");
var Pako = require("pako");
var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
var RTCEvents = require("../../service/RTC/RTCEvents");
var UIEvents = require("../../service/UI/UIEvents");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var retry = require('retry');
var eventEmitter = new EventEmitter();
var connection = null;
var authenticatedUser = false;
function connect(jid, password) {
connection = XMPP.createConnection();
Moderator.setConnection(connection);
if (connection.disco) {
// for chrome, add multistream cap
}
connection.jingle.pc_constraints = APP.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});
}
var faultTolerantConnect = retry.operation({
retries: 3
});
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();
}
// fault tolerant connect
faultTolerantConnect.attempt(function () {
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
XMPP.promptLogin();
}
} else if (status === Strophe.Status.AUTHFAIL) {
// wrong password or username, prompt user
XMPP.promptLogin();
connection = XMPP.createConnection();
Moderator.setConnection(connection);
if (connection.disco) {
// for chrome, add multistream cap
}
connection.jingle.pc_constraints = APP.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});
}
// Include user info in MUC presence
var settings = Settings.getSettings();
if (settings.email) {
connection.emuc.addEmailToPresence(settings.email);
}
if (settings.uid) {
connection.emuc.addUserIdToPresence(settings.uid);
}
if (settings.displayName) {
connection.emuc.addDisplayNameToPresence(settings.displayName);
}
// connection.connect() starts the connection process.
//
// As the connection process proceeds, the user supplied callback will
// be triggered multiple times with status updates. The callback should
// take two arguments - the status code and the error condition.
//
// The status code will be one of the values in the Strophe.Status
// constants. The error condition will be one of the conditions defined
// in RFC 3920 or the condition strophe-parsererror.
//
// The Parameters wait, hold and route are optional and only relevant
// for BOSH connections. Please see XEP 124 for a more detailed
// explanation of the optional parameters.
//
// Connection status constants for use by the connection handler
// callback.
//
// Status.ERROR - An error has occurred (websockets specific)
// Status.CONNECTING - The connection is currently being made
// Status.CONNFAIL - The connection attempt failed
// Status.AUTHENTICATING - The connection is authenticating
// Status.AUTHFAIL - The authentication attempt failed
// Status.CONNECTED - The connection has succeeded
// Status.DISCONNECTED - The connection has been terminated
// Status.DISCONNECTING - The connection is currently being terminated
// Status.ATTACHED - The connection has been attached
var anonymousConnectionFailed = false;
var connectionFailed = false;
var lastErrorMsg;
connection.connect(jid, password, function (status, msg) {
console.log('Strophe status changed to',
Strophe.getStatusString(status), msg);
if (status === Strophe.Status.CONNECTED) {
if (config.useStunTurn) {
connection.jingle.getStunAndTurnCredentials();
}
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 {
connectionFailed = true;
}
lastErrorMsg = msg;
} else if (status === Strophe.Status.DISCONNECTED) {
if (anonymousConnectionFailed) {
// prompt user for username and password
XMPP.promptLogin();
} else {
// Strophe already has built-in HTTP/BOSH error handling and
// request retry logic. Requests are resent automatically
// until their error count reaches 5. Strophe.js disconnects
// if the error count is > 5. We are not replicating this
// here.
//
// The "problem" is that failed HTTP/BOSH requests don't
// trigger a callback with a status update, so when a
// callback with status Strophe.Status.DISCONNECTED arrives,
// we can't be sure if it's a graceful disconnect or if it's
// triggered by some HTTP/BOSH error.
//
// But that's a minor issue in Jitsi Meet as we never
// disconnect anyway, not even when the user closes the
// browser window (which is kind of wrong, but the point is
// that we should never ever get disconnected).
//
// On the other hand, failed connections due to XMPP layer
// errors, trigger a callback with status Strophe.Status.CONNFAIL.
//
// Here we implement retry logic for failed connections due
// to XMPP layer errors and we display an error to the user
// if we get disconnected from the XMPP server permanently.
// If the connection failed, retry.
if (connectionFailed
&& faultTolerantConnect.retry("connection-failed")) {
return;
}
// If we failed to connect to the XMPP server, fire an event
// to let all the interested module now about it.
eventEmitter.emit(XMPPEvents.CONNECTION_FAILED,
msg ? msg : lastErrorMsg);
}
} else if (status === Strophe.Status.AUTHFAIL) {
// wrong password or username, prompt user
XMPP.promptLogin();
}
});
});
}
@@ -89,13 +182,21 @@ function initStrophePlugins()
function registerListeners() {
APP.RTC.addStreamListener(maybeDoJoin,
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) {
XMPP.addToPresence("devices", devices);
})
APP.UI.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) {
XMPP.addToPresence("displayName", nickname);
});
}
function setupEvents() {
$(window).bind('beforeunload', function () {
var unload = (function () {
var unloaded = false;
return function () {
if (unloaded) { return; }
unloaded = true;
if (connection && connection.connected) {
// ensure signout
$.ajax({
@@ -104,27 +205,45 @@ function setupEvents() {
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>",
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 + ')');
textStatus + ' (' + errorThrown + ')');
}
});
}
XMPP.disposeConference(true);
});
};
})();
function setupEvents() {
// In recent versions of FF the 'beforeunload' event is not fired when the
// window or the tab is closed. It is only fired when we leave the page
// (change URL). If this participant doesn't unload properly, then it
// becomes a ghost for the rest of the participants that stay in the
// conference. Thankfully handling the 'unload' event in addition to the
// 'beforeunload' event seems to garante the execution of the 'unload'
// method at least once.
//
// The 'unload' method can safely be run multiple times, it will actually do
// something only the first time that it's run, so we're don't have to worry
// about browsers that fire both events.
$(window).bind('beforeunload', unload);
$(window).bind('unload', unload);
}
var XMPP = {
getConnection: function(){ return connection; },
sessionTerminated: false,
/**
@@ -202,10 +321,12 @@ var XMPP = {
// FIXME: probably removing streams is not required and close() should
// be enough
if (APP.RTC.localAudio) {
handler.peerconnection.removeStream(APP.RTC.localAudio.getOriginalStream(), onUnload);
handler.peerconnection.removeStream(
APP.RTC.localAudio.getOriginalStream(), onUnload);
}
if (APP.RTC.localVideo) {
handler.peerconnection.removeStream(APP.RTC.localVideo.getOriginalStream(), onUnload);
handler.peerconnection.removeStream(
APP.RTC.localVideo.getOriginalStream(), onUnload);
}
handler.peerconnection.close();
}
@@ -241,48 +362,40 @@ var XMPP = {
isExternalAuthEnabled: function () {
return Moderator.isExternalAuthEnabled();
},
switchStreams: function (stream, oldStream, callback) {
switchStreams: function (stream, oldStream, callback, isAudio) {
if (connection && connection.jingle.activecall) {
// FIXME: will block switchInProgress on true value in case of exception
connection.jingle.activecall.switchStreams(stream, oldStream, callback);
connection.jingle.activecall.switchStreams(stream, oldStream, callback, isAudio);
} else {
// We are done immediately
console.warn("No conference handler or conference not started yet");
callback();
}
},
setVideoMute: function (mute, callback, options) {
if(!connection || !APP.RTC.localVideo)
sendVideoInfoPresence: function (mute) {
if(!connection)
return;
connection.emuc.addVideoInfoToPresence(mute);
connection.emuc.sendPresence();
},
setVideoMute: function (mute, callback, options) {
if(!connection)
return;
var self = this;
var localCallback = function (mute) {
connection.emuc.addVideoInfoToPresence(mute);
connection.emuc.sendPresence();
self.sendVideoInfoPresence(mute);
return callback(mute);
};
if (mute == APP.RTC.localVideo.isMuted())
if(connection.jingle.activecall)
{
// 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 = localCallback(mute);
if (successCallback) {
successCallback();
}
} else {
APP.RTC.localVideo.setMute(!mute);
if(connection.jingle.activecall)
{
connection.jingle.activecall.setVideoMute(
mute, localCallback, options);
}
else {
localCallback(mute);
}
connection.jingle.activecall.setVideoMute(
mute, localCallback, options);
}
else {
localCallback(mute);
}
},
setAudioMute: function (mute, callback) {
if (!(connection && APP.RTC.localAudio)) {
@@ -305,10 +418,17 @@ var XMPP = {
// 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.
APP.RTC.localAudio.mute();
APP.RTC.localAudio.setMute(!mute);
// isMuted is the opposite of audioEnabled
connection.emuc.addAudioInfoToPresence(mute);
connection.emuc.sendPresence();
this.sendAudioInfoPresence(mute, callback);
return true;
},
sendAudioInfoPresence: function(mute, callback)
{
if(connection) {
connection.emuc.addAudioInfoToPresence(mute);
connection.emuc.sendPresence();
}
callback();
return true;
},
@@ -379,11 +499,21 @@ var XMPP = {
break;
case "email":
connection.emuc.addEmailToPresence(value);
break;
case "devices":
connection.emuc.addDevicesToPresence(value);
break;
case "startMuted":
if(!Moderator.isModerator())
return;
connection.emuc.addStartMutedToPresence(value[0],
value[1]);
break;
default :
console.log("Unknown tag for presence.");
console.log("Unknown tag for presence: " + name);
return;
}
if(!dontSend)
if (!dontSend)
connection.emuc.sendPresence();
},
/**
@@ -472,8 +602,13 @@ var XMPP = {
},
getSessions: function () {
return connection.jingle.sessions;
},
removeStream: function (stream) {
if(!connection || !connection.jingle.activecall ||
!connection.jingle.activecall.peerconnection)
return;
connection.jingle.activecall.peerconnection.removeStream(stream);
}
};
module.exports = XMPP;

View File

@@ -18,7 +18,10 @@
"events": "*",
"pako": "*",
"i18next-client": "1.7.7",
"sdp-interop": "jitsi/sdp-interop#f65fedfe57a"
"sdp-interop": "0.1.4",
"sdp-transform": "1.4.0",
"async": "0.9.0",
"retry": "0.6.1"
},
"devDependencies": {
},

0
plugin.head.html Normal file
View File

0
plugin.header.text.html Normal file
View File

View File

View File

@@ -5,7 +5,8 @@ var RTCEvents = {
SIMULCAST_LAYER_CHANGED: "rtc.simulcast_layer_changed",
SIMULCAST_LAYER_CHANGING: "rtc.simulcast_layer_changing",
SIMULCAST_START: "rtc.simlcast_start",
SIMULCAST_STOP: "rtc.simlcast_stop"
SIMULCAST_STOP: "rtc.simlcast_stop",
AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed"
};
module.exports = RTCEvents;

View File

@@ -0,0 +1,5 @@
var Events = {
DTMF_SUPPORT_CHANGED: "members.dtmf_support_changed"
};
module.exports = Events;

View File

@@ -1,21 +1,25 @@
var XMPPEvents = {
CONFERENCE_CERATED: "xmpp.conferenceCreated.jingle",
CONNECTION_FAILED: "xmpp.connection.failed",
CONFERENCE_CREATED: "xmpp.conferenceCreated.jingle",
CALL_TERMINATED: "xmpp.callterminated.jingle",
CALL_INCOMING: "xmpp.callincoming.jingle",
DISPOSE_CONFERENCE: "xmpp.dispoce_confernce",
DISPOSE_CONFERENCE: "xmpp.dispose_conference",
GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown",
KICKED: "xmpp.kicked",
BRIDGE_DOWN: "xmpp.bridge_down",
USER_ID_CHANGED: "xmpp.user_id_changed",
CHANGED_STREAMS: "xmpp.changed_streams",
STREAMS_CHANGED: "xmpp.streams_changed",
// We joined the MUC
MUC_JOINED: "xmpp.muc_joined",
MUC_ENTER: "xmpp.muc_enter",
// A member joined the MUC
MUC_MEMBER_JOINED: "xmpp.muc_member_joined",
// A member left the MUC
MUC_MEMBER_LEFT: "xmpp.muc_member_left",
MUC_ROLE_CHANGED: "xmpp.muc_role_changed",
MUC_LEFT: "xmpp.muc_left",
MUC_DESTROYED: "xmpp.muc_destroyed",
DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
REMOTE_STATS: "xmpp.remote_stats",
LOCALROLE_CHANGED: "xmpp.localrole_changed",
LOCAL_ROLE_CHANGED: "xmpp.localrole_changed",
PRESENCE_STATUS: "xmpp.presence_status",
RESERVATION_ERROR: "xmpp.room_reservation_error",
SUBJECT_CHANGED: "xmpp.subject_changed",
@@ -24,6 +28,8 @@ var XMPPEvents = {
PASSWORD_REQUIRED: "xmpp.password_required",
AUTHENTICATION_REQUIRED: "xmpp.authentication_required",
CHAT_ERROR_RECEIVED: "xmpp.chat_error_received",
ETHERPAD: "xmpp.etherpad"
ETHERPAD: "xmpp.etherpad",
DEVICE_AVAILABLE: "xmpp.device_available",
START_MUTED: "xmpp.start_muted"
};
module.exports = XMPPEvents;