Compare commits

...

40 Commits
481 ... 513

Author SHA1 Message Date
hristoterezov
01deadf078 Moves the sound notification code to be played when the participant joins / leaves the room instead of receiving add / remove stream event. 2015-05-28 14:34:40 +03:00
George Politis
6eaa3cd45d Updates app.bundle.js 2015-05-28 11:46:40 +02:00
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
29 changed files with 3461 additions and 1015 deletions

2
app.js
View File

@@ -37,6 +37,8 @@ function init() {
$(document).ready(function () {
var URLPRocessor = require("./modules/URLProcessor/URLProcessor");
URLPRocessor.setConfigParametersFromUrl();
APP.init();
APP.translation.init();

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

@@ -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):
```

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,7 +19,7 @@
<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=57"></script>
<script src="libs/app.bundle.js?v=77"></script>
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
<link rel="stylesheet" href="css/font.css?v=7"/>
<link rel="stylesheet" href="css/toastr.css?v=1">
@@ -299,6 +299,16 @@
<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" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[data-content]downloadlogs" ><i class="fa fa-cloud-download"></i></a>

View File

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

View File

@@ -1,205 +1,227 @@
{
"contactlist": "",
"connectionsettings": "",
"poweredby": "",
"downloadlogs": "",
"roomUrlDefaultMsg": "",
"participant": "",
"me": "",
"speaker": "",
"defaultNickname": "",
"defaultPreziLink": "",
"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": "",
"roomname": "",
"disable": "",
"go": "Créer",
"roomname": "Saisissez un nom de salle",
"disable": "Ne pas afficher cette page lors de ma prochaine visite",
"feature1": {
"title": "",
"content": ""
"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": "",
"content": ""
"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": "",
"content": ""
"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": "",
"content": ""
"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": "",
"content": ""
"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": "",
"content": ""
"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": "",
"content": ""
"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": "",
"content": ""
"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": "",
"videomute": "",
"mute": "Muet / Actif",
"videomute": "Démarrer / Arrêter la caméra",
"authenticate": "",
"record": "",
"lock": "",
"invite": "",
"record": "Enregistrer",
"lock": "Verrouiller / déverrouiller la salle",
"invite": "Inviter des participants",
"chat": "",
"prezi": "",
"etherpad": "",
"sharescreen": "",
"fullscreen": "",
"sip": "",
"Settings": "",
"hangup": "",
"login": "",
"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": "",
"filmstrip": "",
"contactlist": ""
"chat": "Ouvrir / fermer le chat",
"filmstrip": "Montrer / cacher ma vidéo miniature",
"contactlist": "Ouvrir / fermer ma liste de contacts"
},
"chat": {
"nickname": {
"title": "",
"popover": ""
"title": "Saisissez un pseudonyme dans le champ ci-dessous",
"popover": "Choisissez un pseudonyme"
},
"messagebox": ""
"messagebox": "Saisissez votre texte..."
},
"settings": {
"title": "",
"update": "",
"name": ""
"title": "PARAMÈTRES",
"update": "Mise à jour",
"name": "Nom"
},
"videothumbnail": {
"editnickname": "",
"moderator": "",
"videomute": "",
"mute": "",
"kick": "",
"muted": "",
"domute": ""
"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": "",
"packetloss": "",
"resolution": "",
"less": "",
"more": "",
"address": "",
"remoteport": "",
"remoteport_plural": "",
"localport": "",
"localport_plural": "",
"localaddress": "",
"localaddress_plural": "",
"remoteaddress": "",
"remoteaddress_plural": "",
"transport": "",
"bandwidth": "",
"na": ""
"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": "",
"moderator": "",
"connected": "",
"somebody": "",
"me": "",
"disconnected": "Déconnecté",
"moderator": "Droits modérateur accordés!",
"connected": "Connecté",
"somebody": "Quelqu'un",
"me": "Moi",
"focus": "",
"focusFail": "",
"grantedTo": "",
"grantedToUnknown": ""
"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": "",
"popupError": "",
"passwordError": "",
"passwordError2": "",
"joinError": "",
"connectError": "",
"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": "",
"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": "",
"retry": "",
"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": "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": "",
"subject": "",
"body": "",
"and": ""
"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": "",
"CONNECTING": "",
"CONNFAIL": "",
"AUTHENTICATING": "",
"AUTHFAIL": "",
"CONNECTED": "",
"DISCONNECTED": "",
"DISCONNECTING": "",
"ATTACHED": ""
"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"
}
}

View File

@@ -81,7 +81,9 @@
{
"title": "SETTINGS",
"update": "Update",
"name": "Name"
"name": "Name",
"startAudioMuted": "everyone join audio muted",
"startVideoMuted": "everyone join video muted"
},
"videothumbnail":
{
@@ -123,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!",
@@ -133,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.",

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +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")
{
@@ -37,26 +40,14 @@ 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)
{
if(window.location.protocol != "https:" ||
this.isAudioStream() || this.videoType === "screen")
if((window.location.protocol != "https:" && this.isGUMStream) ||
(this.isAudioStream() && this.isGUMStream) || this.videoType === "screen")
{
var tracks = this.getTracks();
@@ -72,9 +63,18 @@ LocalStream.prototype.setMute = function(mute)
}
else
{
APP.RTC.rtcUtils.obtainAudioAndVideoPermissions(["video"],
var self = this;
APP.RTC.rtcUtils.obtainAudioAndVideoPermissions(
(this.isAudioStream() ? ["audio"] : ["video"]),
function (stream) {
APP.RTC.changeLocalVideo(stream, false, function () {});
if(self.isAudioStream())
{
APP.RTC.changeLocalAudio(stream, function () {});
}
else
{
APP.RTC.changeLocalVideo(stream, false, function () {});
}
});
}
}

View File

@@ -13,11 +13,39 @@ 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: false,
video: false
audio: true,
video: true
},
localStreams: [],
remoteStreams: {},
@@ -35,13 +63,15 @@ 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")
{
@@ -55,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) {
@@ -139,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;
@@ -180,12 +211,20 @@ var RTC = {
callback();
};
}
var videoStream = this.rtcUtils.createVideoStream(stream);
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(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.
* @param videoSrc eg.
@@ -220,7 +259,7 @@ var RTC = {
{
APP.xmpp.sendVideoInfoPresence(mute);
if(callback)
callback();
callback(mute);
}
else
{

View File

@@ -130,7 +130,7 @@ 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 >= 38
if (version >= 40
&& !config.enableSimulcast && config.useBundle && config.useRtcpMux) {
this.peerconnection = mozRTCPeerConnection;
this.browser = RTCBrowserType.RTC_BROWSER_FIREFOX;
@@ -144,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();
};
@@ -156,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;
@@ -182,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'}]};
@@ -292,32 +300,97 @@ RTCUtils.prototype.setAvailableDevices = function (um, available) {
* We ask for audio and video combined stream in order to get permissions and
* not to ask twice.
*/
RTCUtils.prototype.obtainAudioAndVideoPermissions = function(devices, callback) {
RTCUtils.prototype.obtainAudioAndVideoPermissions =
function(devices, callback, usageOptions)
{
var self = this;
// Get AV
var successCallback = function (stream) {
if(callback)
callback(stream, usageOptions);
else
self.successCallback(stream, usageOptions);
};
if(!devices)
devices = ['audio', 'video'];
this.getUserMediaWithConstraints(
devices,
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) {
if(callback)
callback(stream);
else
self.successCallback(stream);
successCallback(stream);
},
function (error) {
self.errorCallback(error);
},
config.resolution || '360');
}
}
RTCUtils.prototype.successCallback = function (stream) {
if(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);
this.handleLocalStream(stream, usageOptions);
};
RTCUtils.prototype.errorCallback = function (error) {
@@ -355,12 +428,15 @@ RTCUtils.prototype.errorCallback = function (error) {
}
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();
audioStream = new webkitMediaStream();
videoStream = new webkitMediaStream();
if(stream) {
var audioTracks = stream.getAudioTracks();
@@ -374,38 +450,47 @@ RTCUtils.prototype.handleLocalStream = function(stream)
videoStream.addTrack(videoTracks[i]);
}
}
this.service.createLocalStream(audioStream, "audio");
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.createVideoStream = function(stream)
RTCUtils.prototype.createStream = function(stream, isVideo)
{
var videoStream = null;
var newStream = null;
if(window.webkitMediaStream)
{
videoStream = new webkitMediaStream();
if(stream)
newStream = new webkitMediaStream();
if(newStream)
{
var videoTracks = stream.getVideoTracks();
var tracks = (isVideo? stream.getVideoTracks() : stream.getAudioTracks());
for (i = 0; i < videoTracks.length; i++) {
videoStream.addTrack(videoTracks[i]);
for (i = 0; i < tracks.length; i++) {
newStream.addTrack(tracks[i]);
}
}
}
else
videoStream = stream;
newStream = stream;
return videoStream;
return newStream;
};
module.exports = RTCUtils;

View File

@@ -32,6 +32,12 @@ 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()
@@ -54,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);
@@ -141,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(
@@ -227,6 +252,9 @@ function registerListeners() {
APP.members.addListener(MemberEvents.DTMF_SUPPORT_CHANGED,
onDtmfSupportChanged);
APP.xmpp.addListener(XMPPEvents.START_MUTED, function (audio, video) {
SettingsMenu.setStartMuted(audio, video);
});
}
@@ -414,6 +442,9 @@ function onMucJoined(jid, info) {
if (displayName)
onDisplayNameChanged('localVideoContainer', displayName);
VideoLayout.mucJoined();
}
function initEtherpad(name) {
@@ -427,6 +458,9 @@ function onMucMemberLeft(jid) {
messageHandler.notify(displayName,'notify.somebody',
'disconnected',
'notify.disconnected');
if(!config.startAudioMuted ||
config.startAudioMuted > APP.members.size())
UIUtil.playSoundNotification('userLeft');
// Need to call this with a slight delay, otherwise the element couldn't be
// found for some reason.
// XXX(gp) it works fine without the timeout for me (with Chrome 38).
@@ -453,6 +487,7 @@ 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();
@@ -520,6 +555,9 @@ function onMucMemberJoined(jid, id, displayName) {
'connected',
'notify.connected');
if(!config.startAudioMuted ||
config.startAudioMuted > APP.members.size())
UIUtil.playSoundNotification('userJoined');
// Add Peer's container
VideoLayout.ensurePeerContainerExists(jid,id);
}
@@ -697,6 +735,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.
*/
@@ -714,9 +758,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");
@@ -764,5 +816,8 @@ UI.setVideoMuteButtonsState = function (mute) {
}
}
UI.setVideoMute = setVideoMute;
module.exports = UI;

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

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

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

@@ -27,12 +27,6 @@ var eventEmitter = null;
*/
var focusedVideoInfo = null;
/**
* Indicates if we have muted our audio before the conference has started.
* @type {boolean}
*/
var preMuted = false;
var mutedAudios = {};
var flipXLocalVideo = true;
@@ -550,24 +544,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 +609,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 +641,14 @@ 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
@@ -706,26 +716,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);
}
};
@@ -755,7 +773,6 @@ var VideoLayout = (function (my) {
container.id = 'mixedstream';
container.className = 'videocontainer';
remotes.appendChild(container);
UIUtil.playSoundNotification('userJoined');
}
if (container) {
@@ -774,11 +791,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
@@ -816,7 +832,6 @@ var VideoLayout = (function (my) {
largeVideoState.updateInProgress = true;
var doUpdate = function () {
Avatar.updateActiveSpeakerAvatarSrc(
APP.xmpp.findJidFromResource(
largeVideoState.userResourceJid));
@@ -1278,7 +1293,6 @@ var VideoLayout = (function (my) {
// Remove whole container
container.remove();
UIUtil.playSoundNotification('userLeft');
VideoLayout.resizeThumbnails();
}
@@ -1661,10 +1675,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

@@ -117,6 +117,12 @@ var Members = {
},
removeListener: function (type, listener) {
eventEmitter.removeListener(type, listener);
},
size: function () {
return Object.keys(members).length;
},
getMembers: function () {
return 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

@@ -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;
@@ -975,7 +1010,8 @@ JingleSession.prototype.switchStreams = function (new_stream, oldStream, success
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) {
@@ -984,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();
@@ -1092,7 +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);
}
}
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

@@ -177,6 +177,20 @@ var Moderator = {
{ name: 'openSctp', value: config.openSctp})
.up();
}
if(config.startAudioMuted !== undefined)
{
elem.c(
'property',
{ name: 'startAudioMuted', value: config.startAudioMuted})
.up();
}
if(config.startVideoMuted !== undefined)
{
elem.c(
'property',
{ name: 'startVideoMuted', value: config.startVideoMuted})
.up();
}
elem.up();
return elem;
},

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,13 @@ 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)
{
@@ -262,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];
@@ -497,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();
@@ -577,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';
},
@@ -621,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');

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

@@ -9,66 +9,145 @@ 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
});
// 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);
}
// fault tolerant connect
faultTolerantConnect.attempt(function () {
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();
}
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();
}
});
});
}
@@ -111,8 +190,13 @@ function registerListeners() {
});
}
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({
@@ -121,24 +205,41 @@ 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 = {
@@ -261,10 +362,10 @@ 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");
@@ -272,6 +373,8 @@ var XMPP = {
}
},
sendVideoInfoPresence: function (mute) {
if(!connection)
return;
connection.emuc.addVideoInfoToPresence(mute);
connection.emuc.sendPresence();
},
@@ -315,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;
},
@@ -393,6 +503,12 @@ var XMPP = {
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: " + name);
return;

View File

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

View File

@@ -1,4 +1,5 @@
var XMPPEvents = {
CONNECTION_FAILED: "xmpp.connection.failed",
CONFERENCE_CREATED: "xmpp.conferenceCreated.jingle",
CALL_TERMINATED: "xmpp.callterminated.jingle",
CALL_INCOMING: "xmpp.callincoming.jingle",
@@ -28,6 +29,7 @@ var XMPPEvents = {
AUTHENTICATION_REQUIRED: "xmpp.authentication_required",
CHAT_ERROR_RECEIVED: "xmpp.chat_error_received",
ETHERPAD: "xmpp.etherpad",
DEVICE_AVAILABLE: "xmpp.device_available"
DEVICE_AVAILABLE: "xmpp.device_available",
START_MUTED: "xmpp.start_muted"
};
module.exports = XMPPEvents;