Compare commits

..

86 Commits
587 ... 628

Author SHA1 Message Date
paweldomas
a2b43843b7 Updates app.bundle.js 2015-08-24 12:00:40 +02:00
paweldomas
2f03a0a7fe Fixes broken last-N 2015-08-24 11:53:13 +02:00
paweldomas
4c2f0d3600 Removed duplicated code for adding video thumbnail's hover handler. 2015-08-24 11:53:12 +02:00
paweldomas
a8a0945d73 Moves method for selecting thumbnail's video element from videolayout to SmallVideo. Fixes issue with muted audio in IE after switching between thumbnails. 2015-08-24 11:53:11 +02:00
paweldomas
a7048fba06 Implements HTTP POST query for fetching app configuration. 2015-08-24 11:53:10 +02:00
Boris Grozev
7b35dd89bb Updates the external api docs. 2015-08-20 15:00:56 -05:00
Boris Grozev
3561204bb5 Allows to overwrite config and interfaceConfig options through the
external API.
2015-08-20 14:57:05 -05:00
ibauersachs
ee50d07dc3 Commit from translate.jitsi.org by user ibauersachs.: 172 of 173 strings translated (0 fuzzy). 2015-08-19 19:58:21 +00:00
ibauersachs
9ec4bc91fc Commit from translate.jitsi.org by user ibauersachs.: 173 of 173 strings translated (0 fuzzy). 2015-08-19 19:58:06 +00:00
Ingo Bauersachs
88071e5258 Add Slovenian (sl) 2015-08-19 21:54:00 +02:00
paweldomas
e79d476d89 Updates app.bundle.js 2015-08-19 18:27:09 +02:00
paweldomas
0fe4999beb Use fadeTo instead of fadeIn/fadeOut to avoid having display: none on large video which causes issues when Temasys plugin is used. 2015-08-19 18:23:18 +02:00
paweldomas
ae96b9f365 Fixes issue in IE where click events on local video thumbnail are captured by local audio object created by Temasys plugin on stream attach. 2015-08-19 18:23:08 +02:00
paweldomas
922d0bd512 New adapter.js for Temasys plugin. 2015-08-19 18:22:07 +02:00
paweldomas
9a7bc4ebab Fixes issue with returning from shared document view. Calls show() before fadeOut() on large video to avoid situation when we end up with black screen and are unable to select new video. Updates app.bundle.js. 2015-08-19 10:55:35 +02:00
damencho
2081757ba1 Enables stats in FF. 2015-08-18 16:42:47 -05:00
jitsi-pootle
e9c9fc5e69 New files added from translate.jitsi.org based on templates 2015-08-18 12:50:42 +00:00
paweldomas
562761196d Updates app.bundle.js 2015-08-18 13:27:29 +02:00
paweldomas
420514b921 Temasys adapter.js ver 0.12.0 with fix for setInterval included 2015-08-18 13:22:17 +02:00
paweldomas
eb63b24a9a Fixes fadeIn/fadeOut large video transitions in Safari. Enables animation on video resize when switching between camera and screen video. 2015-08-18 13:19:54 +02:00
paweldomas
c8bbded994 Removes accidentally committed debug message. 2015-08-18 13:19:44 +02:00
damencho
2a2702c13a Adds params for enabling fake devices on firefox. 2015-08-17 17:05:03 -05:00
Boris Grozev
5fc868ee96 Updates app.bundle.js. 2015-08-17 16:17:47 -05:00
Boris Grozev
502eab7278 Only update the "start muted" settings on precense from a moderator. 2015-08-17 16:17:15 -05:00
Boris Grozev
332aafbe20 Documents some of the XMPP events. Renames some of them. 2015-08-17 16:17:03 -05:00
Boris Grozev
d5258e6197 Remove a double variable declaration. 2015-08-17 16:17:01 -05:00
Boris Grozev
9cc9e6132c Add RTCBrowserType.isAndroid(). 2015-08-17 16:16:34 -05:00
hristoterezov
f60c1d9751 Removes the minimum width and height of the external API iframe in "film strip only" mode. 2015-08-17 13:46:26 -05:00
Boris Grozev
5d32318d93 Updates app.bundle.js. 2015-08-14 10:49:13 -05:00
Boris Grozev
fee8482bae Updates sdp-transform to 1.4.1. 2015-08-14 10:48:21 -05:00
paweldomas
f2b5cdbfb8 Updates app.bundle.js. 2015-08-14 17:06:50 +02:00
paweldomas
60afe2d202 Fixes issue with display name event not being fired on Safari/IE 2015-08-14 17:04:30 +02:00
paweldomas
18f03e296b Fixes broken input fields in Safari. 2015-08-14 17:03:58 +02:00
paweldomas
5cd9db1b6a Missing semicolon... 2015-08-14 17:02:19 +02:00
bgrozev
f83404a99e Merge pull request #341 from pstros/fix-comment
Change the comment to fix npm install
2015-08-13 16:00:56 -05:00
Devin Wilson
7c1ba9242b Change the comment to fix npm install 2015-08-13 08:31:50 -06:00
ibauersachs
bfcc587047 Commit from translate.jitsi.org by user ibauersachs.: 172 of 172 strings translated (0 fuzzy). 2015-08-13 07:35:17 +00:00
Boris Grozev
e90d8f5531 Updates app.bundle.js. 2015-08-12 21:54:15 +02:00
Boris Grozev
59033aab28 Adds what will hopefully be treated as a comment by all npm versions to package.json. 2015-08-12 21:52:49 +02:00
Boris Grozev
7f1eb617c3 Uses npm packeges for socket.io and jsSHA. 2015-08-12 21:50:42 +02:00
hristoterezov
fd7e8c9162 Merge pull request #338 from gerges/issue/toolbar-refactor
Refactors toolbar
2015-08-12 13:55:50 -05:00
Issac Gerges
51e886142b Ensure hangup button selector is specific enough to apply red color and custom size 2015-08-12 13:36:24 -05:00
Issac Gerges
dcc206b2b4 Update non-container toolbar items to be set to inline-block when shown 2015-08-12 13:06:55 -05:00
Issac Gerges
da75e17ff5 Merge remote-tracking branch 'origin/master' into issue/toolbar-refactor 2015-08-12 13:05:20 -05:00
paweldomas
8fea9b76ee Updates app.bundle.js 2015-08-12 14:23:43 +02:00
paweldomas
cb024be2d6 Fixes locking at WaitForPluginReady with Temasys plugin install detection. 2015-08-12 14:21:08 +02:00
paweldomas
4c4e99c51a Updates Temasys adapter.screenshare.js. Browser restart is no longer required after plugin install. 2015-08-12 14:20:51 +02:00
paweldomas
4b8bc398dd Fixes issue with black video when new stream element is inserted after the old one. 2015-08-12 14:20:44 +02:00
paweldomas
466e7dcc91 Fixes crash in LocalSSRCReplacement when null localDescription is passed. 2015-08-12 14:20:37 +02:00
Issac Gerges
de30ce0f5c Merge remote-tracking branch 'origin/master' into issue/toolbar-refactor 2015-08-11 13:30:08 -05:00
Issac Gerges
fc6f5717cb Refactor toolbar to add separators via css and remove unneeded containers 2015-08-11 13:24:53 -05:00
paweldomas
b680ecd2ff Renames getLargeVideoJid to getLargeVideoResource and updates app.bundle.js. 2015-08-11 13:12:55 +02:00
Boris Grozev
2bea2eec74 Updates app.bundle.js. 2015-08-10 16:54:51 -05:00
Boris Grozev
f52b1380ee Continues to separate JingleSessionPC. 2015-08-10 16:38:35 -05:00
Boris Grozev
baf720c553 Starts to abstract JingleSession. 2015-08-10 15:58:50 -05:00
Boris Grozev
deaff6af5b Executes a local (git-ignored) script on "make deploy". 2015-08-10 13:25:21 -05:00
Boris Grozev
6ca1e131af Renames JingleSession to JingleSessionPC. 2015-08-10 13:22:05 -05:00
Boris Grozev
57b9aeb38c Inlines a method for clarity/simplicity. 2015-08-10 13:14:12 -05:00
Boris Grozev
cc20a4d776 Removes an unused variable. 2015-08-10 13:02:39 -05:00
Boris Grozev
fd404b8465 Supports setting interfaceConfig options via URL params. Renames config.filmStripOnly to interfaceConfig.filmStripOnly. 2015-08-10 12:59:12 -05:00
damencho
cc29df6376 Adds params for enabling rec on entering the conference. 2015-08-07 10:31:48 -05:00
paweldomas
44136e8a55 Updates app.bundle.js. 2015-08-07 12:59:43 +02:00
paweldomas
fb875423a9 Fixes SSRC=1 issue. Renames VideoSSRCHack to LocalSSRCReplacement. 2015-08-07 12:58:12 +02:00
paweldomas
ab4c29eddc Fixes video mute in Firefox. Disables VideoSSRCHack for Firefox by default. 2015-08-07 12:58:03 +02:00
paweldomas
95e964a089 Fixes bugs in VideoSSRCHack. Additional log messages. 2015-08-07 12:57:56 +02:00
paweldomas
c288aa6e84 Fixes issue with toggling video mute in FF caused by the fact that it has no 'onended' callback handling implemented. 2015-08-07 12:57:48 +02:00
paweldomas
e5d03d1d11 Fixes GUM failure with the latest FF nightly plus cleanup. 2015-08-07 12:57:43 +02:00
hristoterezov
59147f059d Adds a parameter to API constructor that enables film strip only mode. 2015-08-06 19:01:21 -05:00
hristoterezov
7793d65a99 Renames config.minimized to config.filmStripOnly . 2015-08-06 18:59:51 -05:00
hristoterezov
b77791f4b2 Implements minimized mode - only the thumbnails are visible. 2015-08-06 18:34:40 -05:00
damencho
4092d67853 Updates use of recording states, add some information texts and notifications. 2015-08-05 22:18:45 -05:00
paweldomas
2ea6be9b2c Updates app.bundle.js. 2015-08-05 14:12:10 +02:00
paweldomas
74e7507a73 Re-uses SSRC of the first video stream created for any streams created in future. Does video mute and switching to the screen stream without 'source-add'/'source-remove' signaling. Moves video type signaling from Jingle to MUC presence. 2015-08-05 14:10:08 +02:00
bgrozev
9a31fa3d63 Fixes a bug reported by Pawel Domas. 2015-08-04 09:26:16 -05:00
Дамян Минков
fd44cfa7a0 Typo. 2015-08-03 17:18:32 -05:00
yanas
ab570d63fa Fixes this reference. 2015-08-03 14:08:42 -05:00
Boris Grozev
b4983b2566 Merge branch 'rename-mute-to-toggle' 2015-08-03 11:46:55 -05:00
bgrozev
fdb470d22f Merge pull request #330 from jitsi/remove-rtcp-mux-from-config
Removes the useBundle and useRtcpMux options from config.js. These are
2015-08-03 11:32:43 -05:00
bgrozev
c163a22415 Merge pull request #331 from jitsi/enable-noice-reduction
Re-enables video noise reduction (removes a workaround for M37).
2015-08-03 11:32:38 -05:00
bgrozev
1dea41d3d4 Merge pull request #328 from jitsi/verify-full-jid-for-jingle
Verify full (and not bare) JID of the Jingle sender, since everyone i…
2015-08-03 11:32:32 -05:00
yanas
9d321df49e Adds javadoc for previous commit. 2015-08-03 11:21:56 -05:00
yanas
d92d8e8299 Some additional error handling. 2015-08-03 11:00:16 -05:00
Boris Grozev
4cac7ac97f Re-enables video noise reduction (removes a workaround for M37). 2015-07-29 11:28:58 -05:00
Boris Grozev
46a17948d0 Renames the external API commands from "mute{Audio,Video}" to "toggle{Audio,Video}" since what they do is "toggle". 2015-07-29 11:28:37 -05:00
Boris Grozev
79ac1e800f Verify full (and not bare) JID of the Jingle sender, since everyone in the MUC has the same bare JID. 2015-07-29 11:27:12 -05:00
Boris Grozev
b0c81985d4 Removes the useBundle and useRtcpMux options from config.js. These are
now fully supported by jitsi-videobridge and all browsers which we
support (and if we need to enable them conditionally because of browser
compatibility in the future, we should do it based on run-time browser
detection.)
2015-07-29 11:26:50 -05:00
58 changed files with 30674 additions and 21376 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ node_modules
.idea/
*.iml
.*.tmp
deploy-local.sh

View File

@@ -18,7 +18,10 @@ app:
$(NPM) update && $(BROWSERIFY) $(FLAGS) app.js -s APP -o $(OUTPUT_DIR)/app.bundle.js
clean:
@rm -f $(OUTPUT_DIR)/*.bundle.js
rm -f $(OUTPUT_DIR)/*.bundle.js
deploy:
@mkdir -p $(DEPLOY_DIR) && cp $(OUTPUT_DIR)/*.bundle.js $(DEPLOY_DIR) && ./bump-js-versions.sh
mkdir -p $(DEPLOY_DIR) && \
cp $(OUTPUT_DIR)/*.bundle.js $(DEPLOY_DIR) && \
./bump-js-versions.sh && \
([ ! -x deploy-local.sh ] || ./deploy-local.sh)

33
app.js
View File

@@ -16,6 +16,7 @@ var APP =
this.settings = require("./modules/settings/Settings");
this.DTMF = require("./modules/DTMF/DTMF");
this.members = require("./modules/members/MemberList");
this.configFetch = require("./modules/config/HttpConfigFetch");
}
};
@@ -30,10 +31,38 @@ function init() {
APP.members.start();
}
/**
* If we have HTTP endpoint for getting confgi.json configured we're going to
* read it and override properties from config.js and interfaceConfig.js.
* If there is no endpoint we'll just continue with initialization.
* Keep in mind that if the endpoint has been configured and we fail to obtain
* the config for any reason then the conference won't start and error message
* will be displayed to the user.
*/
function obtainConfigAndInit() {
if (config.configLocation) {
APP.configFetch.obtainConfig(
config.configLocation, APP.UI.getRoomNode(),
// Get config result callback
function(success, error) {
if (success) {
init();
} else {
// Show obtain config error,
// pass the error object for report
APP.UI.messageHandler.openReportDialog(
null, "dialog.connectError", error);
}
});
} else {
init();
}
}
$(document).ready(function () {
var URLProcessor = require("./modules/URLProcessor/URLProcessor");
var URLProcessor = require("./modules/config/URLProcessor");
URLProcessor.setConfigParametersFromUrl();
APP.init();
@@ -42,7 +71,7 @@ $(document).ready(function () {
if(APP.API.isEnabled())
APP.API.init();
APP.UI.start(init);
APP.UI.start(obtainConfigAndInit);
});

View File

@@ -1,4 +1,5 @@
var config = {
// configLocation: './config.json', // see ./modules/HttpConfigFetch.js
hosts: {
domain: 'jitsi-meet.example.com',
//anonymousdomain: 'guest.example.com',
@@ -26,8 +27,6 @@ var config = {
channelLastN: -1, // The default value of the channel attribute last-n.
adaptiveLastN: false,
adaptiveSimulcast: false,
useRtcpMux: true, // required for FF support
useBundle: true, // required for FF support
enableRecording: false,
enableWelcomePage: true,
enableSimulcast: false, // blocks FF support

View File

@@ -38,17 +38,30 @@ html, body{
position: relative;
}
#toolbar a.button::after {
content: '';
display: inline-block;
position: absolute;
left: 40px;
width: 1px;
height: 20px;
background: #373737;
}
#toolbar a.button:last-child::after {
content: none;
}
.button {
display: inline-block;
position: relative;
color: #FFFFFF;
top: 0px;
padding: 10px 0px;
top: 0;
padding: 10px 0;
width: 38px;
cursor: pointer;
font-size: 11pt;
text-align: center;
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.6);
text-shadow: 0 1px 0 rgba(255,255,255,.3), 0 -1px 0 rgba(0,0,0,.6);
z-index: 1;
}
@@ -61,13 +74,13 @@ html, body{
cursor: pointer;
}
#chatButton, #chatBottomButton, #contactListButton, #numberOfParticipants {
#toolbar_button_chat, #chatBottomButton, #contactListButton, #numberOfParticipants {
-webkit-transition: all .5s ease-in-out;
-moz-transition: all .5s ease-in-out;
transition: all .5s ease-in-out;
}
/*#ffde00*/
#chatButton.active, #contactListButton.glowing, #chatBottomButton.glowing {
#toolbar_button_chat.active, #contactListButton.glowing, #chatBottomButton.glowing {
-webkit-text-shadow: -1px 0 10px #00ccff,
0 1px 10px #00ccff,
1px 0 10px #00ccff,
@@ -82,6 +95,11 @@ html, body{
0 -1px 10px #00ccff;
}
#toolbar_button_hangup {
color: #ff0000;
font-size: 1.4em;
}
#numberOfParticipants {
position: absolute;
top: 0px;
@@ -100,13 +118,13 @@ html, body{
color: #00ccff;
}
#recordButton {
#toolbar_button_record {
-webkit-transition: all .5s ease-in-out;
-moz-transition: all .5s ease-in-out;
transition: all .5s ease-in-out;
}
/*#ffde00*/
#recordButton.active {
#toolbar_button_record.active {
-webkit-text-shadow: -1px 0 10px #00ccff,
0 1px 10px #00ccff,
1px 0 10px #00ccff,
@@ -155,6 +173,8 @@ a.bottomToolbarButton:hover {
}
input[type='text'], input[type='password'], textarea {
-webkit-user-select: text;
user-select: text;
display: inline-block;
font-size: 14px;
padding: 5px;

View File

@@ -101,12 +101,14 @@
}
#largeVideo,
#largeVideoWrapper,
#largeVideoContainer {
overflow: hidden;
text-align: center;
}
#largeVideo
#largeVideo,
#largeVideoWrapper
{
object-fit: cover;
}
@@ -116,6 +118,9 @@
#localVideoWrapper>video,
#localVideoWrapper>object,
#localVideoWrapper,
#largeVideoWrapper,
#largeVideoWrapper>video,
#largeVideoWrapper>object,
.videocontainer>video,
.videocontainer>object {
position: absolute;
@@ -135,7 +140,7 @@
z-index: 0;
}
#etherpadButton {
#toolbar_button_etherpad {
display: none;
}
@@ -446,3 +451,29 @@
background-position: center;
}
.videoMessageFilter {
-webkit-filter: grayscale(.5) opacity(0.8);
filter: grayscale(.5) opacity(0.8);
}
.videoProblemFilter {
-webkit-filter: blur(10px) grayscale(.5) opacity(0.8);
filter: blur(10px) grayscale(.5) opacity(0.8);
}
#videoConnectionMessage {
display: none;
position: absolute;
width: 100%;
top:50%;
z-index: 10000;
font-weight: 600;
font-size: 14px;
text-align: center;
color: #FFF;
opacity: .80;
text-shadow: 0px 0px 1px rgba(0,0,0,0.3),
0px 1px 1px rgba(0,0,0,0.3),
1px 0px 1px rgba(0,0,0,0.3),
0px 0px 1px rgba(0,0,0,0.3);
}

View File

@@ -29,6 +29,13 @@ constructor.
```
If you don't specify room the user will enter in new conference with random room name.
You can overwrite options set in config.js and interface_config.js. For example, to enable the film-strip-only interface mode and disable simulcast, you can use:
```javascript
var configOverwrite = {enableSimulcast: false};
var interfaceConfigOverwrite = {filmStripOnly: true};
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, true, configOverwrite, interfaceConfigOverwrite);
```
Controlling embedded Jitsi Meet Conference
=========
@@ -49,13 +56,13 @@ the new display name to be set
```
api.executeCommand('displayName', ['New Nickname']);
```
* **muteAudio** - mutes / unmutes the audio for the local participant. No arguments are required.
* **toggleAudio** - mutes / unmutes the audio for the local participant. No arguments are required.
```
api.executeCommand('muteAudio', [])
api.executeCommand('toggleAudio', [])
```
* **muteVideo** - mutes / unmutes the video for the local participant. No arguments are required.
* **toggleVideo** - mutes / unmutes the video for the local participant. No arguments are required.
```
api.executeCommand('muteVideo', [])
api.executeCommand('toggleVideo', [])
```
* **toggleFilmStrip** - hides / shows the film strip. No arguments are required.
```
@@ -78,7 +85,7 @@ The ```commands``` parameter is object with keys the names of the commands and v
commands.
```
api.executeCommands({displayName: ['nickname'], muteAudio: []});
api.executeCommands({displayName: ['nickname'], toggleAudio: []});
```
You can add event listeners to the embedded Jitsi Meet using ```addEventListener``` method.
@@ -166,4 +173,4 @@ You can remove the embedded Jitsi Meet Conference with the following code:
api.dispose()
```
It is a good practice to remove the conference before the page is unloaded.
It is a good practice to remove the conference before the page is unloaded.

View File

@@ -150,7 +150,7 @@ ant dist.{os-name}
Run jicofo:
```sh
cd dist/{os-name}'
./jicofo.sh --domain=jitsi.exmaple.com --secret=YOURSECRET2 --user_domain=auth.jitsi.example.com --user_name=focus --user_password=YOURSECRET3
./jicofo.sh --domain=jitsi.example.com --secret=YOURSECRET2 --user_domain=auth.jitsi.example.com --user_name=focus --user_password=YOURSECRET3
```
## Deploy Jitsi Meet

View File

@@ -23,12 +23,15 @@ var JitsiMeetExternalAPI = (function()
* @param width width of the iframe
* @param height height of the iframe
* @param parent_node the node that will contain the iframe
* @param filmStripOnly if the value is true only the small videos will be
* visible.
* @constructor
*/
function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode) {
if(!width || width < MIN_WIDTH)
function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
configOverwrite, interfaceConfigOverwrite) {
if((!width || width < MIN_WIDTH) && !filmStripOnly)
width = MIN_WIDTH;
if(!height || height < MIN_HEIGHT)
if((!height || height < MIN_HEIGHT) && !filmStripOnly)
height = MIN_HEIGHT;
this.parentNode = null;
@@ -42,13 +45,35 @@ var JitsiMeetExternalAPI = (function()
this.iframeHolder =
this.parentNode.appendChild(document.createElement("div"));
this.iframeHolder.id = "jitsiConference" + JitsiMeetExternalAPI.id;
this.iframeHolder.style.width = width + "px";
this.iframeHolder.style.height = height + "px";
if(width)
this.iframeHolder.style.width = width + "px";
if(height)
this.iframeHolder.style.height = height + "px";
this.frameName = "jitsiConferenceFrame" + JitsiMeetExternalAPI.id;
this.url = "//" + domain + "/";
if(room_name)
this.url += room_name;
this.url += "#external=true";
var key;
if (configOverwrite) {
for (key in configOverwrite) {
if (!configOverwrite.hasOwnProperty(key) ||
typeof key !== 'string')
continue;
this.url += "&config." + key + "=" + configOverwrite[key];
}
}
if (interfaceConfigOverwrite) {
for (key in interfaceConfigOverwrite) {
if (!interfaceConfigOverwrite.hasOwnProperty(key) ||
typeof key !== 'string')
continue;
this.url += "&interfaceConfig." + key + "=" + interfaceConfigOverwrite[key];
}
}
JitsiMeetExternalAPI.id++;
this.frame = document.createElement("iframe");
@@ -92,8 +117,8 @@ var JitsiMeetExternalAPI = (function()
* Executes command. The available commands are:
* displayName - sets the display name of the local participant to the value
* passed in the arguments array.
* muteAudio - mutes / unmutes audio with no arguments
* muteVideo - mutes / unmutes video with no arguments
* toggleAudio - mutes / unmutes audio with no arguments
* toggleVideo - mutes / unmutes video with no arguments
* filmStrip - hides / shows the film strip with no arguments
* If the command doesn't require any arguments the parameter should be set
* to empty array or it may be omitted.
@@ -114,8 +139,8 @@ var JitsiMeetExternalAPI = (function()
* Executes commands. The available commands are:
* displayName - sets the display name of the local participant to the value
* passed in the arguments array.
* muteAudio - mutes / unmutes audio with no arguments
* muteVideo - mutes / unmutes video with no arguments
* toggleAudio - mutes / unmutes audio with no arguments
* toggleVideo - mutes / unmutes video with no arguments
* filmStrip - hides / shows the film strip with no arguments
* @param object the object with commands to be executed. The keys of the
* object are the commands that will be executed and the values are the

View File

@@ -11,9 +11,7 @@
<meta itemprop="image" content="/images/jitsilogo.png"/>
<script src="https://api.callstats.io/static/callstats.min.js"></script>
<script src="libs/jquery-2.1.1.min.js"></script>
<script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsSHA/1.5.0/sha.js"></script>
<script src="config.js?v=11"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="config.js?v=12"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="libs/strophe/strophe.min.js?v=2"></script>
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
@@ -22,12 +20,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=116"></script>
<script src="libs/app.bundle.js?v=130"></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">
<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=18" id="videolayout_default"/>
<link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=31"/>
<link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=20" 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">
@@ -133,9 +131,7 @@
</div>
<span id="toolbar">
<span id="authentication" class="authentication" style="display: none">
<a class="button" id="toolbar_button_authentication" >
<i id="authButton" class="icon-avatar"></i>
</a>
<a class="button icon-avatar" id="toolbar_button_authentication" data-i18n="[content]toolbar.authenticate"></a>
<ul class="loginmenu">
<span class="loginmenuPadding"></span>
<li id="toolbar_auth_identity" class="identity"></li>
@@ -146,79 +142,23 @@
<a class="authButton" data-i18n="toolbar.logout"></a>
</li>
</ul>
<div class="header_button_separator"></div>
</span>
<a class="button" id="toolbar_button_mute" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="mutePopover" data-i18n="[content]toolbar.mute" content="Mute / Unmute">
<i id="mute" class="icon-microphone"></i>
<a class="button icon-microphone" id="toolbar_button_mute" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="mutePopover" data-i18n="[content]toolbar.mute" content="Mute / Unmute"></a>
<a class="button icon-camera" id="toolbar_button_camera" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleVideoPopover" data-i18n="[content]toolbar.videomute" content="Start / stop camera"></a>
<a class="button icon-recEnable" id="toolbar_button_record" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.record" content="Record" style="display: none"></a>
<a class="button icon-security" id="toolbar_button_security" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.lock" content="Lock / unlock room"></a>
<a class="button icon-link" id="toolbar_button_link" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.invite" content="Invite others"></a>
<a class="button icon-chat" id="toolbar_button_chat" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="bottom" data-i18n="[content]toolbar.chat" content="Open / close chat">
<span id="unreadMessages"></span>
</a>
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_camera" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleVideoPopover" data-i18n="[content]toolbar.videomute" content="Start / stop camera">
<i id="video" class="icon-camera"></i>
</a>
<span id="recording" style="display: none">
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_record" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.record" content="Record">
<i id="recordButton" class="icon-recEnable"></i>
</a>
</span>
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_security" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.lock" content="Lock / unlock room">
<i id="lockIcon" class="icon-security"></i>
</a>
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_link" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.invite" content="Invite others">
<i class="icon-link"></i>
</a>
<div class="header_button_separator"></div>
<span class="toolbar_span">
<a class="button" id="toolbar_button_chat" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="bottom" data-i18n="[content]toolbar.chat" content="Open / close chat">
<i id="chatButton" class="icon-chat">
<span id="unreadMessages"></span>
</i>
</a>
</span>
<span id="prezi_button">
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_prezi" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.prezi" content="Share Prezi">
<i class="icon-prezi"></i>
</a>
</span>
<span id="etherpadButton">
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_etherpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared document" data-i18n="[content]toolbar.etherpad">
<i class="icon-share-doc"></i>
</a>
</span>
<span id="desktopsharing" style="display: none">
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_desktopsharing" data-container="body" data-toggle="popover" data-placement="bottom" content="Share screen" data-i18n="[content]toolbar.sharescreen">
<i class="icon-share-desktop"></i>
</a>
</span>
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_fullScreen" data-container="body" data-toggle="popover" data-placement="bottom" content="Enter / Exit Full Screen" data-i18n="[content]toolbar.fullscreen">
<i id="fullScreen" class="icon-full-screen"></i>
</a>
<span id="sipCallButton" style="display: none">
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_sip" data-container="body" data-toggle="popover" data-placement="bottom" content="Call SIP number" 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>
</a>
<div class="header_button_separator"></div>
<span id="hangup">
<a class="button" id="toolbar_button_hangup" data-container="body" data-toggle="popover" data-placement="bottom" content="Hang Up" data-i18n="[content]toolbar.hangup">
<i class="icon-hangup" style="color:#ff0000;font-size: 1.4em;"></i>
</a>
</span>
<a class="button icon-prezi" id="toolbar_button_prezi" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.prezi" content="Share Prezi"></a>
<a class="button icon-share-doc" id="toolbar_button_etherpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared document" data-i18n="[content]toolbar.etherpad"></a>
<a class="button icon-share-desktop" id="toolbar_button_desktopsharing" data-container="body" data-toggle="popover" data-placement="bottom" content="Share screen" data-i18n="[content]toolbar.sharescreen" style="display: none"></a>
<a class="button icon-full-screen" id="toolbar_button_fullScreen" data-container="body" data-toggle="popover" data-placement="bottom" content="Enter / Exit Full Screen" data-i18n="[content]toolbar.fullscreen"></a>
<a class="button icon-telephone" id="toolbar_button_sip" data-container="body" data-toggle="popover" data-placement="bottom" content="Call SIP number" data-i18n="[content]toolbar.sip" style="display: none"></a>
<a class="button icon-dialpad" id="toolbar_button_dialpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Open dialpad" data-i18n="[content]toolbar.dialpad" style="display: none"></a>
<a class="button icon-settings" id="toolbar_button_settings" data-container="body" data-toggle="popover" data-placement="bottom" content="Settings" data-i18n="[content]toolbar.Settings"></a>
<a class="button icon-hangup" id="toolbar_button_hangup" data-container="body" data-toggle="popover" data-placement="bottom" content="Hang Up" data-i18n="[content]toolbar.hangup"></a>
</span>
</div>
<div id="subject"></div>

View File

@@ -15,5 +15,9 @@ var interfaceConfig = {
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
APP_NAME: "Jitsi Meet",
INVITATION_POWERED_BY: true,
ACTIVE_SPEAKER_AVATAR_SIZE: 100
ACTIVE_SPEAKER_AVATAR_SIZE: 100,
/**
* Whether to only show the filmstrip (and hide the toolbar).
*/
filmStripOnly: false
};

View File

@@ -4,5 +4,6 @@
"de": "Deutsch",
"tr": "Türkisch",
"it": "Italienisch",
"fr": "Französisch"
"fr": "Französisch",
"sl": "Slowenisch"
}

9
lang/languages-sl.json Normal file
View File

@@ -0,0 +1,9 @@
{
"en": "Angleščina",
"bg": "Bolgarščina",
"de": "Nemščina",
"tr": "Turščina",
"it": "Italjanščina",
"fr": "Francoščina",
"sl": ""
}

View File

@@ -4,5 +4,6 @@
"de": "German",
"tr": "Turkish",
"it": "Italian",
"fr": "French"
"fr": "French",
"sl": "Slovenian"
}

View File

@@ -226,6 +226,7 @@
"connection": {
"ERROR": "Fehler",
"CONNECTING": "Verbindung wird hergestellt",
"RECONNECTING": "Es ist ein Netzwerkproblem aufgetreten. Verbinde...",
"CONNFAIL": "Verbindungsaufbau gescheitert",
"AUTHENTICATING": "Anmeldung läuft",
"AUTHFAIL": "Authentifizierung fehlgeschlagen",
@@ -233,5 +234,10 @@
"DISCONNECTED": "Getrennt",
"DISCONNECTING": "Verbindung wird getrennt",
"ATTACHED": "Angehängt"
},
"recording": {
"toaster": "Wird aufgezeichnet",
"pending": "Die Aufzeichnung wird gestartet sobald ein weiterer Teilnehmer beitritt",
"on": "Aufzeichnung wurde gestartet"
}
}

249
lang/main-sl.json Normal file
View File

@@ -0,0 +1,249 @@
{
"contactlist": "STIKI",
"connectionsettings": "Nastavitve povezave",
"poweredby": "poganja",
"downloadlogs": "Shrani zapis",
"roomUrlDefaultMsg": "Ustvarjanje vaše konference ...",
"participant": "Udeleženec",
"me": "jaz",
"speaker": "Govornik",
"defaultNickname": "npr. __name__",
"defaultPreziLink": "npr. __url__",
"welcomepage": {
"go": "POJDI",
"roomname": "Vpišite ime sobe",
"disable": "Prihodnjič ne prikaži te strani",
"feature1": {
"title": "Enostavna uporaba",
"content": "Nič ni treba namestiti. __app__ deluje direktno v vašem brskalniku. Enostavno sporočite ostalim udeležencem URL svoje konference in začnite."
},
"feature2": {
"title": "Ozka pasovna širina",
"content": "Video konferenca z več udeleženci s samo 128Kbps. Deljenje zaslona in samo avdio konference so možne že z veliko nižjo pasovno širino."
},
"feature3": {
"title": "Odprta koda",
"content": "__app__ je objavljen po licenci MIT. Pod pogoji te licence lahko prosto snamete, uporabljate, spreminjate in delite."
},
"feature4": {
"title": "Neomejeno število uporabnikov",
"content": "Nobene umetne omejitve števila uporabnikov ali udeležencev konference. Zmogljivost strežnika in pasovna širina sta edini omejitvi."
},
"feature5": {
"title": "Skupna raba zaslona",
"content": "Skupna raba zaslona z drugimi je zelo enostavna. __app__ je idealna rešitev za spletne predstavitve, predavanja in tehnično podporo."
},
"feature6": {
"title": "Varne sobe",
"content": "Rabite zasebnost? Konferenčne sobe __app__ so lahko zaklenjene z geslom, da preprečite dostop neželenim gostom ter prekinitve."
},
"feature7": {
"title": "Skupna raba zapiskov",
"content": "__app__ vsebuje Etherpad, realnočasovni skupinski urejevalnik besedil, ki je idealen za pisanje zapisnikov sestankov, člankov in še mnogo drugega."
},
"feature8": {
"title": "Statistika uporabe",
"content": "Spoznajte svoje uporabnike z enostavno integracijo v Piwik, Google Analytics ter druge sisteme za nadzor uporabe in statistiko."
}
},
"toolbar": {
"mute": "Utišaj / Povrni glasnost",
"videomute": "Zaženi / Ustavi kamero",
"authenticate": "Overi",
"record": "Snemaj",
"lock": "Zakleni / Odkleni sobo",
"invite": "Povabite ostale",
"chat": "Odpri / zapri klepetalnico",
"prezi": "Skupna raba Prezi",
"etherpad": "Dokument v skupni rabi",
"sharescreen": "Zaslon v souporabi",
"fullscreen": "Vklopi / Izklopi celozaslonski način",
"sip": "Pokliči številko SIP",
"Settings": "Nastavitve",
"hangup": "Odloži",
"login": "Prijava",
"logout": "Odjava",
"dialpad": "Pokaži številčnico"
},
"bottomtoolbar": {
"chat": "Odpri / zapri klepetalnico",
"filmstrip": "Pokaži / Skrij filmski trak",
"contactlist": "Odpri / Zapri stike"
},
"chat": {
"nickname": {
"title": "Vpišite vzdevek v spodnje polje",
"popover": "Izberite vzdevek"
},
"messagebox": "Vnesite besedilo ..."
},
"settings": {
"title": "NASTAVITVE",
"update": "Posodobi",
"name": "Ime",
"startAudioMuted": "začni brez zvoka",
"startVideoMuted": "začni brez slike"
},
"videothumbnail": {
"editnickname": "Kliknite, da spremenite<br/>svoje ime",
"moderator": "Lastnik<br/>konference",
"videomute": "Udeleženec je<br/>izključil kamero.",
"mute": "Udeleženec je utišan",
"kick": "Izženi",
"muted": "Utišan",
"domute": "Utišaj"
},
"connectionindicator": {
"bitrate": "Bitna hitrost:",
"packetloss": "Izgubljeni paketi:",
"resolution": "Ločljivost:",
"less": "Pokaži manj",
"more": "Pokaži več",
"address": "Naslov:",
"remoteport_plural_5": "Oddaljena vrata:",
"remoteport": "Oddaljena vrata:",
"remoteport_plural_2": "Oddaljena vrata:",
"remoteport_plural_3": "Oddaljena vrata:",
"localport_plural_5": "Krajevna vrata:",
"localport": "Krajevna vrata:",
"localport_plural_2": "Krajevna vrata:",
"localport_plural_3": "Krajevna vrata:",
"localaddress_plural_5": "Krajevni naslov:",
"localaddress": "Krajevna naslova:",
"localaddress_plural_2": "Krajevni naslovi:",
"localaddress_plural_3": "Krajevni naslov:",
"remoteaddress_plural_5": "Oddaljeni naslov:",
"remoteaddress": "Oddaljena naslova:",
"remoteaddress_plural_2": "Oddaljeni naslovi:",
"remoteaddress_plural_3": "Oddaljeni naslovi:",
"transport": "Prenos:",
"bandwidth": "Ocenjena pasovna širina:",
"na": "Ko se konferenca začne se vrnite sem za informacije o povezavi"
},
"notify": {
"disconnected": "odklopjeno",
"moderator": "Dodeljene moderatorske pravice!",
"connected": "povezano",
"somebody": "Nekdo",
"me": "Jaz",
"focus": "Fokus na konferenco",
"focusFail": "__component__ ni na razpolago - ponovni poskus čez __ms__ sec",
"grantedTo": "Moderatorske pravice dodeljene uporabniku __to__!",
"grantedToUnknown": "Moderatorske pravice dodeljene uporabniku $t(somebody)!",
"muted": "Pogovor ste začeli utišano.",
"mutedTitle": "Utišani ste!"
},
"dialog": {
"kickMessage": "Ojej! Izgnali so vas iz srečanja!",
"popupError": "Vaš brskalnik ne dovoli pojavnih oken iz te spletne strani. Omogočite prosim pojavna okna v varnostnih nastavitvah svojega brskalnika in ponovno poskusite.",
"passwordError": "Ta pogovor je zaščiten z geslom. Samo lastnik konference lahko nastavi geslo.",
"passwordError2": "Ta pogovor ni zaščiten z geslom. Samo lastnik konference lahko nastavi geslo.",
"joinError": "Ups! Ni se bilo mogoče pridružiti konferenci. Mogoče je kakšna težava z varnostnimi nastavitvami. Pišite prosim administratorju storitve.",
"connectError": "Ups! Nekaj je narobe in se ni bilo mogoče povezati s konferenco.",
"connectErrorWithMsg": "Ups! Nekaj je narobe in se ni bilo mogoče povezati s konferenco: __msg__",
"connecting": "Povezovanje",
"error": "Napaka",
"detectext": "Napaka pri zaznavanju razširitve za skupno uporabo namizja.",
"failtoinstall": "Razširitve za skupno uporabo namizja ni bilo mogoče namestiti",
"failedpermissions": "Ni bilo mogoče pridobiti dovoljenja za uporabo lokalnega mikrofona ali kamere.",
"bridgeUnavailable": "Jitsi Videobridge trenutno ni na razpolago. Prosim poskusite kasneje!",
"lockTitle": "Zaklepanje ni uspelo",
"lockMessage": "Konference ni bilo mogoče zakleniti.",
"warning": "Opozorilo",
"passwordNotSupported": "Trenutno ni mogoče zakleniti sobe z geslom.",
"sorry": "Oprostite",
"internalError": "Notranja napaka [setRemoteDescription]",
"unableToSwitch": "Ni mogoče preklopiti video pretoka.",
"SLDFailure": "Ups! Nekaj je narobe in zvoka se ne da utišati! (Napaka SLD)",
"SRDFailure": "Ups! Nekaj je narobe in slike ni mogoče ustaviti! (Napaka SRD)",
"oops": "Ups!",
"defaultError": "Prišlo je do neke napake",
"passwordRequired": "Potrebno je geslo",
"Ok": "V redu",
"removePreziTitle": "Odstrani Prezi",
"removePreziMsg": "Ali res želite odstraniti Prezi?",
"sharePreziTitle": "Dajte Prezi v skupno rabo",
"sharePreziMsg": "Drug uporabnik je že dal Prezi v skupno rabo. Ta konferenca podpira samo en Prezi naenkrat.",
"Remove": "Odstrani",
"WaitingForHost": "Čakanje na gostitelja ...",
"WaitForHostMsg": "Ta konferenca <b>__room__ </b> se še ni začela. V primeru, da ste vi gostitelj se prosim overite. Drugače počakajte prosim na prihod gostitelja.",
"IamHost": "Jaz sem gostitelj",
"Cancel": "Prekliči",
"retry": "Poskusi ponovno",
"logoutTitle": "Odjava",
"logoutQuestion": "Ali se res želite odjaviti in prekiniti konferenco?",
"sessTerminated": "Seja je končana",
"hungUp": "Prekinili ste klic",
"joinAgain": "Ponovno se pridruži",
"Share": "Souporaba",
"preziLinkError": "Prosim, pravilno vpišite povezavo Prezi.",
"Save": "Shrani",
"recordingToken": "Vnesite žeton za registracijo",
"Dial": "Pokliči",
"sipMsg": "Vnesite številko SIP",
"passwordCheck": "Ali res želite odstraniti geslo?",
"passwordMsg": "Nastavite geslo za zaklepanje sobe",
"Invite": "Povabi",
"shareLink": "To povezavo pošljite vsem, ki jih želite povabiti",
"settings1": "Nastavite svojo konferenco",
"settings2": "Utišaj udeležence ob pristopu",
"settings3": "Zahtevaj vzdevke<br/><br/>Nastavi geslo za zaklep sobe:",
"yourPassword": "vaše geslo",
"Back": "Nazaj",
"serviceUnavailable": "Storitev ni na voljo",
"gracefulShutdown": "Storitev trenutno ni na voljo zaradi vzdrževanja. Poskusite ponovno kasneje.",
"Yes": "Da",
"reservationError": "Napaka v sistemu rezervacije",
"reservationErrorMsg": "Koda napake: __code__, sporočilo: __msg__",
"password": "geslo",
"userPassword": "uporabniško geslo",
"token": "žeton",
"displayNameRequired": "Vpišite svoje ime:"
},
"email": {
"sharedKey": [
"Ta konferenca je zaklenjena z geslom. Uporabite sledeči PIN ko se pridružite:",
"",
"",
"__sharedKey__",
"",
""
],
"subject": "Povabilo na __appName__ (__conferenceName__)",
"body": [
"Pozdravljeni,",
"želim vas povabiti na ravnokar pripravljeno konferenco __appName__.",
"",
"",
"Prosim, kliknite na sledečo povezavo, da se pridružite konferenci.",
"",
"",
"__roomUrl__",
"",
"",
"__sharedKeyText__",
" Opomba: __appName__ trenutno nudi podporo samo za __supportedBrowsers__, uporabiti morate enega izmed teh brskalnikov.",
"",
"",
"Se slišimo čez trenutek!"
],
"and": "in"
},
"connection": {
"ERROR": "Napaka",
"CONNECTING": "Povezovanje",
"RECONNECTING": "Prišlo je do omrežne napake. Ponovni poskus ...",
"CONNFAIL": "Povezovanje je spodletelo",
"AUTHENTICATING": "Overjanje",
"AUTHFAIL": "Overitev je spodletela",
"CONNECTED": "Povezano",
"DISCONNECTED": "Ni povezave",
"DISCONNECTING": "Prekinjanje povezave",
"ATTACHED": "Priključeno"
},
"recording": {
"toaster": "Trenutno poteka snemanje!",
"pending": "Snemanje se bo začelo takoj, ko se bo pridružil drugi udeleženec",
"on": "Snemanje se je začelo"
}
}

View File

@@ -229,6 +229,7 @@
{
"ERROR": "Error",
"CONNECTING": "Connecting",
"RECONNECTING": "A network problem occurred. Reconnecting...",
"CONNFAIL": "Connection failed",
"AUTHENTICATING": "Authenticating",
"AUTHFAIL": "Authentication failed",
@@ -239,5 +240,11 @@
"FETCH_SESSION_ID": "Obtaining session-id...",
"GOT_SESSION_ID": "Obtaining session-id... Done",
"GET_SESSION_ID_ERROR": "Get session-id error: "
},
"recording":
{
"toaster": "Currently recording!",
"pending": "Your recording will start as soon as another participant joins",
"on": "Recording has been started"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,8 +11,8 @@ var XMPPEvents = require("../../service/xmpp/XMPPEvents");
* List of the available commands.
* @type {{
* displayName: inputDisplayNameHandler,
* muteAudio: toggleAudio,
* muteVideo: toggleVideo,
* toggleAudio: toggleAudio,
* toggleVideo: toggleVideo,
* toggleFilmStrip: toggleFilmStrip,
* toggleChat: toggleChat,
* toggleContactList: toggleContactList
@@ -23,8 +23,8 @@ var commands = {};
function initCommands() {
commands = {
displayName: APP.UI.inputDisplayNameHandler,
muteAudio: APP.UI.toggleAudio,
muteVideo: APP.UI.toggleVideo,
toggleAudio: APP.UI.toggleAudio,
toggleVideo: APP.UI.toggleVideo,
toggleFilmStrip: APP.UI.toggleFilmStrip,
toggleChat: APP.UI.toggleChat,
toggleContactList: APP.UI.toggleContactList

View File

@@ -1,6 +1,24 @@
/* global APP */
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
var RTCEvents = require("../../service/RTC/RTCEvents");
var RTCBrowserType = require("./RTCBrowserType");
/**
* This implements 'onended' callback normally fired by WebRTC after the stream
* is stopped. There is no such behaviour yet in FF, so we have to add it.
* @param stream original WebRTC stream object to which 'onended' handling
* will be added.
*/
function implementOnEndedHandling(stream) {
var originalStop = stream.stop;
stream.stop = function () {
originalStop.apply(stream);
if (!stream.ended) {
stream.ended = true;
stream.onended();
}
};
}
function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) {
this.stream = stream;
@@ -21,9 +39,12 @@ function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) {
};
}
this.stream.onended = function() {
this.stream.onended = function () {
self.streamEnded();
};
if (RTCBrowserType.isFirefox()) {
implementOnEndedHandling(this.stream);
}
}
LocalStream.prototype.streamEnded = function () {
@@ -45,9 +66,11 @@ LocalStream.prototype.setMute = function (mute)
var eventType = isAudio ? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE;
if ((window.location.protocol != "https:" && this.isGUMStream) ||
(isAudio && this.isGUMStream) || this.videoType === "screen") {
var tracks = this.getTracks();
(isAudio && this.isGUMStream) || this.videoType === "screen" ||
// FIXME FF does not support 'removeStream' method used to mute
RTCBrowserType.isFirefox()) {
var tracks = this.getTracks();
for (var idx = 0; idx < tracks.length; idx++) {
tracks[idx].enabled = !mute;
}

View File

@@ -198,7 +198,7 @@ var RTC = {
},
changeLocalVideo: function (stream, isUsingScreenStream, callback) {
var oldStream = this.localVideo.getOriginalStream();
var type = (isUsingScreenStream? "screen" : "video");
var type = (isUsingScreenStream ? "screen" : "camera");
var localCallback = callback;
if(this.localVideo.isMuted() && this.localVideo.videoType !== type) {
localCallback = function() {
@@ -242,32 +242,6 @@ var RTC = {
return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted;
}
},
/**
* Checks if video identified by given src is desktop stream.
* @param videoSrc eg.
* blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
* @returns {boolean}
*/
isVideoSrcDesktop: function (jid) {
if(!jid)
return false;
var isDesktop = false;
var stream = null;
if (APP.xmpp.myJid() === jid) {
// local video
stream = this.localVideo;
} else {
var peerStreams = this.remoteStreams[jid];
if(!peerStreams)
return false;
stream = peerStreams[MediaStreamType.VIDEO_TYPE];
}
if(stream)
isDesktop = (stream.videoType === "screen");
return isDesktop;
},
setVideoMute: function (mute, callback, options) {
if (!this.localVideo)
return;

View File

@@ -3,6 +3,8 @@ var currentBrowser;
var browserVersion;
var isAndroid;
var RTCBrowserType = {
RTC_BROWSER_CHROME: "rtc_browser.chrome",
@@ -55,6 +57,13 @@ var RTCBrowserType = {
usesUnifiedPlan: function() {
return RTCBrowserType.isFirefox();
},
/**
* Whether the browser is running on an android device.
*/
isAndroid: function() {
return isAndroid;
}
// Add version getters for other browsers when needed
@@ -157,5 +166,6 @@ function detectBrowser() {
}
browserVersion = detectBrowser();
isAndroid = navigator.userAgent.indexOf('Android') != -1;
module.exports = RTCBrowserType;

View File

@@ -22,22 +22,19 @@ function getPreviousResolution(resolution) {
return resName;
}
function setResolutionConstraints(constraints, resolution, isAndroid) {
if (resolution && !constraints.video || isAndroid) {
// same behaviour as true
constraints.video = { mandatory: {}, optional: [] };
}
function setResolutionConstraints(constraints, resolution) {
var isAndroid = RTCBrowserType.isAndroid();
if(Resolutions[resolution]) {
if (Resolutions[resolution]) {
constraints.video.mandatory.minWidth = Resolutions[resolution].width;
constraints.video.mandatory.minHeight = Resolutions[resolution].height;
}
else {
if (isAndroid) {
constraints.video.mandatory.minWidth = 320;
constraints.video.mandatory.minHeight = 240;
constraints.video.mandatory.maxFrameRate = 15;
}
else if (isAndroid) {
// FIXME can't remember if the purpose of this was to always request
// low resolution on Android ? if yes it should be moved up front
constraints.video.mandatory.minWidth = 320;
constraints.video.mandatory.minHeight = 240;
constraints.video.mandatory.maxFrameRate = 15;
}
if (constraints.video.mandatory.minWidth)
@@ -48,17 +45,34 @@ function setResolutionConstraints(constraints, resolution, isAndroid) {
constraints.video.mandatory.minHeight;
}
function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid)
{
function getConstraints(um, resolution, bandwidth, fps, desktopStream) {
var constraints = {audio: false, video: false};
if (um.indexOf('video') >= 0) {
// same behaviour as true
constraints.video = { mandatory: {}, optional: [] };
constraints.video.optional.push({ googLeakyBucket: true });
setResolutionConstraints(constraints, resolution);
}
if (um.indexOf('audio') >= 0) {
// same behaviour as true
constraints.audio = { mandatory: {}, optional: []};
if (!RTCBrowserType.isFirefox()) {
// same behaviour as true
constraints.audio = { mandatory: {}, optional: []};
// if it is good enough for hangouts...
constraints.audio.optional.push(
{googEchoCancellation: true},
{googAutoGainControl: true},
{googNoiseSupression: true},
{googHighpassFilter: true},
{googNoisesuppression2: true},
{googEchoCancellation2: true},
{googAutoGainControl2: true}
);
} else {
constraints.audio = true;
}
}
if (um.indexOf('screen') >= 0) {
if (RTCBrowserType.isChrome()) {
@@ -100,33 +114,6 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid
};
}
if (constraints.audio) {
// if it is good enough for hangouts...
constraints.audio.optional.push(
{googEchoCancellation: true},
{googAutoGainControl: true},
{googNoiseSupression: true},
{googHighpassFilter: true},
{googNoisesuppression2: true},
{googEchoCancellation2: true},
{googAutoGainControl2: true}
);
}
if (constraints.video) {
constraints.video.optional.push(
{googNoiseReduction: false} // chrome 37 workaround for issue 3807, reenable in M38
);
if (um.indexOf('video') >= 0) {
constraints.video.optional.push(
{googLeakyBucket: true}
);
}
}
if (um.indexOf('video') >= 0) {
setResolutionConstraints(constraints, resolution, isAndroid);
}
if (bandwidth) {
if (!constraints.video) {
//same behaviour as true
@@ -144,6 +131,15 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid
constraints.video.mandatory.minFrameRate = fps;
}
// we turn audio for both audio and video tracks, the fake audio & video seems to work
// only when enabled in one getUserMedia call, we cannot get fake audio separate by fake video
// this later can be a problem with some of the tests
if(RTCBrowserType.isFirefox() && config.firefox_fake_device)
{
constraints.audio = true;
constraints.fake = true;
}
return constraints;
}
@@ -154,7 +150,7 @@ function RTCUtils(RTCService, onTemasysPluginReady)
this.service = RTCService;
if (RTCBrowserType.isFirefox()) {
var FFversion = RTCBrowserType.getFirefoxVersion();
if (FFversion >= 40 && config.useBundle && config.useRtcpMux) {
if (FFversion >= 40) {
this.peerconnection = mozRTCPeerConnection;
this.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
this.pc_constraints = {};
@@ -195,9 +191,7 @@ function RTCUtils(RTCService, onTemasysPluginReady)
RTCIceCandidate = mozRTCIceCandidate;
} else {
console.error(
"Firefox requirements not met, ver: " + FFversion +
", bundle: " + config.useBundle +
", rtcp-mux: " + config.useRtcpMux);
"Firefox version too old: " + FFversion + ". Required >= 40.");
window.location.href = 'unsupported_browser.html';
return;
}
@@ -224,7 +218,7 @@ function RTCUtils(RTCService, onTemasysPluginReady)
};
// DTLS should now be enabled by default but..
this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]};
if (navigator.userAgent.indexOf('Android') != -1) {
if (RTCBrowserType.isAndroid()) {
this.pc_constraints = {}; // disable DTLS on Android
}
if (!webkitMediaStream.prototype.getVideoTracks) {
@@ -301,11 +295,9 @@ RTCUtils.prototype.getUserMediaWithConstraints = function(
um, success_callback, failure_callback, resolution,bandwidth, fps,
desktopStream) {
currentResolution = resolution;
// Check if we are running on Android device
var isAndroid = navigator.userAgent.indexOf('Android') != -1;
var constraints = getConstraints(
um, resolution, bandwidth, fps, desktopStream, isAndroid);
um, resolution, bandwidth, fps, desktopStream);
console.info("Get media constraints", constraints);
@@ -527,7 +519,7 @@ RTCUtils.prototype.handleLocalStream = function(stream, usageOptions) {
this.service.createLocalStream(audioStream, "audio", null, null,
audioMuted, audioGUM);
this.service.createLocalStream(videoStream, "video", null, null,
this.service.createLocalStream(videoStream, "video", null, 'camera',
videoMuted, videoGUM);
};

View File

@@ -1,4 +1,4 @@
/*! adapterjs - v0.11.0 - 2015-06-08 */
/*! adapterjs - custom version from - 2015-08-19 */
// Adapter's interface.
var AdapterJS = AdapterJS || {};
@@ -17,7 +17,7 @@ AdapterJS.options = AdapterJS.options || {};
// AdapterJS.options.hidePluginInstallPrompt = true;
// AdapterJS version
AdapterJS.VERSION = '0.11.0';
AdapterJS.VERSION = '0.12.0';
// This function will be called when the WebRTC API is ready to be used
// Whether it is the native implementation (Chrome, Firefox, Opera) or
@@ -72,6 +72,12 @@ else if(!!navigator.platform.match(/^Win/i)) {
AdapterJS.WebRTCPlugin.pluginInfo.downloadLink = 'http://bit.ly/1kkS4FN';
}
AdapterJS.WebRTCPlugin.TAGS = {
NONE : 'none',
AUDIO : 'audio',
VIDEO : 'video'
};
// Unique identifier of each opened page
AdapterJS.WebRTCPlugin.pageId = Math.random().toString(36).slice(2);
@@ -340,9 +346,24 @@ AdapterJS.renderNotificationBar = function (text, buttonText, buttonLink, openNe
try {
event.cancelBubble = true;
} catch(error) { }
});
}
else {
var pluginInstallInterval = setInterval(function(){
if(! isIE) {
navigator.plugins.refresh(false);
}
AdapterJS.WebRTCPlugin.isPluginInstalled(
AdapterJS.WebRTCPlugin.pluginInfo.prefix,
AdapterJS.WebRTCPlugin.pluginInfo.plugName,
function() { // plugin now installed
clearInterval(pluginInstallInterval);
AdapterJS.WebRTCPlugin.defineWebRTCInterface();
},
function() {
// still no plugin detected, nothing to do
});
} , 500);
});
} else {
c.document.close();
}
AdapterJS.addEvent(c.document, 'click', function() {
@@ -711,6 +732,28 @@ if (navigator.mozGetUserMedia) {
return to;
};
AdapterJS.maybeThroughWebRTCReady();
} else if (navigator.mediaDevices && navigator.userAgent.match(
/Edge\/(\d+).(\d+)$/)) {
webrtcDetectedBrowser = 'edge';
webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10);
// the minimum version still supported by adapter.
webrtcMinimumVersion = 12;
getUserMedia = navigator.getUserMedia;
attachMediaStream = function(element, stream) {
element.srcObject = stream;
return element;
};
reattachMediaStream = function(to, from) {
to.srcObject = from.srcObject;
return to;
};
AdapterJS.maybeThroughWebRTCReady();
} else { // TRY TO USE PLUGIN
// IE 9 is not offering an implementation of console.log until you open a console
@@ -794,8 +837,8 @@ if (navigator.mozGetUserMedia) {
AdapterJS.WebRTCPlugin.pluginInfo.pluginId + '" /> ' +
'<param name="windowless" value="false" /> ' +
'<param name="pageId" value="' + AdapterJS.WebRTCPlugin.pageId + '" /> ' +
'<param name="onload" value="' + AdapterJS.WebRTCPlugin.pluginInfo.onload +
'" />' +
'<param name="onload" value="' + AdapterJS.WebRTCPlugin.pluginInfo.onload + '" />' +
'<param name="tag" value="' + AdapterJS.WebRTCPlugin.TAGS.NONE + '" />' +
// uncomment to be able to use virtual cams
(AdapterJS.options.getAllCams ? '<param name="forceGetAllCams" value="True" />':'') +
@@ -829,7 +872,8 @@ if (navigator.mozGetUserMedia) {
AdapterJS.WebRTCPlugin.pluginInfo.pluginId + '">' +
'<param name="windowless" value="false" /> ' +
(AdapterJS.options.getAllCams ? '<param name="forceGetAllCams" value="True" />':'') +
'<param name="pageId" value="' + AdapterJS.WebRTCPlugin.pageId + '">';
'<param name="pageId" value="' + AdapterJS.WebRTCPlugin.pageId + '">' +
'<param name="tag" value="' + AdapterJS.WebRTCPlugin.TAGS.NONE + '" />';
document.body.appendChild(AdapterJS.WebRTCPlugin.plugin);
}
@@ -860,6 +904,12 @@ if (navigator.mozGetUserMedia) {
};
AdapterJS.WebRTCPlugin.defineWebRTCInterface = function () {
if (AdapterJS.WebRTCPlugin.pluginState ===
AdapterJS.WebRTCPlugin.PLUGIN_STATES.READY) {
console.error("AdapterJS - WebRTC interface has already been defined");
return;
}
AdapterJS.WebRTCPlugin.pluginState = AdapterJS.WebRTCPlugin.PLUGIN_STATES.INITIALIZING;
AdapterJS.isDefined = function (variable) {
@@ -951,78 +1001,89 @@ if (navigator.mozGetUserMedia) {
streamId = '';
}
else {
stream.enableSoundTracks(true);
stream.enableSoundTracks(true); // TODO: remove on 0.12.0
streamId = stream.id;
}
if (element.nodeName.toLowerCase() !== 'audio') {
var elementId = element.id.length === 0 ? Math.random().toString(36).slice(2) : element.id;
if (!element.isWebRTCPlugin || !element.isWebRTCPlugin()) {
var frag = document.createDocumentFragment();
var temp = document.createElement('div');
var classHTML = '';
if (element.className) {
classHTML = 'class="' + element.className + '" ';
} else if (element.attributes && element.attributes['class']) {
classHTML = 'class="' + element.attributes['class'].value + '" ';
var elementId = element.id.length === 0 ? Math.random().toString(36).slice(2) : element.id;
var nodeName = element.nodeName.toLowerCase();
if (nodeName !== 'object') { // not a plugin <object> tag yet
var tag;
switch(nodeName) {
case 'audio':
tag = AdapterJS.WebRTCPlugin.TAGS.AUDIO;
break;
case 'video':
tag = AdapterJS.WebRTCPlugin.TAGS.VIDEO;
break;
default:
tag = AdapterJS.WebRTCPlugin.TAGS.NONE;
}
temp.innerHTML = '<object id="' + elementId + '" ' + classHTML +
'type="' + AdapterJS.WebRTCPlugin.pluginInfo.type + '">' +
'<param name="pluginId" value="' + elementId + '" /> ' +
'<param name="pageId" value="' + AdapterJS.WebRTCPlugin.pageId + '" /> ' +
'<param name="windowless" value="true" /> ' +
'<param name="streamId" value="' + streamId + '" /> ' +
'</object>';
while (temp.firstChild) {
frag.appendChild(temp.firstChild);
}
var frag = document.createDocumentFragment();
var temp = document.createElement('div');
var classHTML = '';
if (element.className) {
classHTML = 'class="' + element.className + '" ';
} else if (element.attributes && element.attributes['class']) {
classHTML = 'class="' + element.attributes['class'].value + '" ';
}
var height = '';
var width = '';
if (element.getBoundingClientRect) {
var rectObject = element.getBoundingClientRect();
width = rectObject.width + 'px';
height = rectObject.height + 'px';
}
else if (element.width) {
width = element.width;
height = element.height;
} else {
// TODO: What scenario could bring us here?
}
temp.innerHTML = '<object id="' + elementId + '" ' + classHTML +
'type="' + AdapterJS.WebRTCPlugin.pluginInfo.type + '">' +
'<param name="pluginId" value="' + elementId + '" /> ' +
'<param name="pageId" value="' + AdapterJS.WebRTCPlugin.pageId + '" /> ' +
'<param name="windowless" value="true" /> ' +
'<param name="streamId" value="' + streamId + '" /> ' +
'<param name="tag" value="' + tag + '" /> ' +
'</object>';
while (temp.firstChild) {
frag.appendChild(temp.firstChild);
}
element.parentNode.insertBefore(frag, element);
frag = document.getElementById(elementId);
frag.width = width;
frag.height = height;
element.parentNode.removeChild(element);
var height = '';
var width = '';
if (element.getBoundingClientRect) {
var rectObject = element.getBoundingClientRect();
width = rectObject.width + 'px';
height = rectObject.height + 'px';
}
else if (element.width) {
width = element.width;
height = element.height;
} else {
var children = element.children;
for (var i = 0; i !== children.length; ++i) {
if (children[i].name === 'streamId') {
children[i].value = streamId;
break;
}
// TODO: What scenario could bring us here?
}
element.parentNode.insertBefore(frag, element);
frag = document.getElementById(elementId);
frag.width = width;
frag.height = height;
element.parentNode.removeChild(element);
} else { // already an <object> tag, just change the stream id
var children = element.children;
for (var i = 0; i !== children.length; ++i) {
if (children[i].name === 'streamId') {
children[i].value = streamId;
break;
}
element.setStreamId(streamId);
}
var newElement = document.getElementById(elementId);
newElement.onplaying = (element.onplaying) ? element.onplaying : function (arg) {};
if (isIE) { // on IE the event needs to be plugged manually
newElement.attachEvent('onplaying', newElement.onplaying);
newElement.onclick = (element.onclick) ? element.onclick : function (arg) {};
newElement._TemOnClick = function (id) {
var arg = {
srcElement : document.getElementById(id)
};
newElement.onclick(arg);
};
}
return newElement;
} else {
return element;
element.setStreamId(streamId);
}
var newElement = document.getElementById(elementId);
newElement.onplaying = (element.onplaying) ? element.onplaying : function (arg) {};
newElement.onclick = (element.onclick) ? element.onclick : function (arg) {};
if (isIE) { // on IE the event needs to be plugged manually
newElement.attachEvent('onplaying', newElement.onplaying);
newElement._TemOnClick = function (id) {
var arg = {
srcElement : document.getElementById(id)
};
newElement.onclick(arg);
};
}
return newElement;
};
reattachMediaStream = function (to, from) {
@@ -1100,211 +1161,3 @@ if (navigator.mozGetUserMedia) {
AdapterJS.WebRTCPlugin.defineWebRTCInterface,
AdapterJS.WebRTCPlugin.pluginNeededButNotInstalledCb);
}
(function () {
'use strict';
var baseGetUserMedia = null;
AdapterJS.TEXT.EXTENSION = {
REQUIRE_INSTALLATION_FF: 'To enable screensharing you need to install the Skylink WebRTC tools Firefox Add-on.',
REQUIRE_INSTALLATION_CHROME: 'To enable screensharing you need to install the Skylink WebRTC tools Chrome Extension.',
REQUIRE_REFRESH: 'Please refresh this page after the Skylink WebRTC tools extension has been installed.',
BUTTON_FF: 'Install Now',
BUTTON_CHROME: 'Go to Chrome Web Store'
};
var clone = function(obj) {
if (null == obj || "object" != typeof obj) return obj;
var copy = obj.constructor();
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
}
return copy;
};
if (window.navigator.mozGetUserMedia) {
baseGetUserMedia = window.navigator.getUserMedia;
navigator.getUserMedia = function (constraints, successCb, failureCb) {
if (constraints && constraints.video && !!constraints.video.mediaSource) {
// intercepting screensharing requests
if (constraints.video.mediaSource !== 'screen' && constraints.video.mediaSource !== 'window') {
throw new Error('Only "screen" and "window" option is available as mediaSource');
}
var updatedConstraints = clone(constraints);
//constraints.video.mediaSource = constraints.video.mediaSource;
updatedConstraints.video.mozMediaSource = updatedConstraints.video.mediaSource;
// so generally, it requires for document.readyState to be completed before the getUserMedia could be invoked.
// strange but this works anyway
var checkIfReady = setInterval(function () {
if (document.readyState === 'complete') {
clearInterval(checkIfReady);
baseGetUserMedia(updatedConstraints, successCb, function (error) {
if (error.name === 'PermissionDeniedError' && window.parent.location.protocol === 'https:') {
AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_FF,
AdapterJS.TEXT.EXTENSION.BUTTON_FF,
'http://skylink.io/screensharing/ff_addon.php?domain=' + window.location.hostname, false, true);
//window.location.href = 'http://skylink.io/screensharing/ff_addon.php?domain=' + window.location.hostname;
} else {
failureCb(error);
}
});
}
}, 1);
} else { // regular GetUserMediaRequest
baseGetUserMedia(constraints, successCb, failureCb);
}
};
getUserMedia = navigator.getUserMedia;
} else if (window.navigator.webkitGetUserMedia) {
baseGetUserMedia = window.navigator.getUserMedia;
navigator.getUserMedia = function (constraints, successCb, failureCb) {
if (constraints && constraints.video && !!constraints.video.mediaSource) {
if (window.webrtcDetectedBrowser !== 'chrome') {
throw new Error('Current browser does not support screensharing');
}
// would be fine since no methods
var updatedConstraints = clone(constraints);
var chromeCallback = function(error, sourceId) {
if(!error) {
updatedConstraints.video.mandatory = updatedConstraints.video.mandatory || {};
updatedConstraints.video.mandatory.chromeMediaSource = 'desktop';
updatedConstraints.video.mandatory.maxWidth = window.screen.width > 1920 ? window.screen.width : 1920;
updatedConstraints.video.mandatory.maxHeight = window.screen.height > 1080 ? window.screen.height : 1080;
if (sourceId) {
updatedConstraints.video.mandatory.chromeMediaSourceId = sourceId;
}
delete updatedConstraints.video.mediaSource;
baseGetUserMedia(updatedConstraints, successCb, failureCb);
} else {
if (error === 'permission-denied') {
throw new Error('Permission denied for screen retrieval');
} else {
throw new Error('Failed retrieving selected screen');
}
}
};
var onIFrameCallback = function (event) {
if (!event.data) {
return;
}
if (event.data.chromeMediaSourceId) {
if (event.data.chromeMediaSourceId === 'PermissionDeniedError') {
chromeCallback('permission-denied');
} else {
chromeCallback(null, event.data.chromeMediaSourceId);
}
}
if (event.data.chromeExtensionStatus) {
if (event.data.chromeExtensionStatus === 'not-installed') {
AdapterJS.renderNotificationBar(AdapterJS.TEXT.EXTENSION.REQUIRE_INSTALLATION_CHROME,
AdapterJS.TEXT.EXTENSION.BUTTON_CHROME,
event.data.data, true, true);
} else {
chromeCallback(event.data.chromeExtensionStatus, null);
}
}
// this event listener is no more needed
window.removeEventListener('message', onIFrameCallback);
};
window.addEventListener('message', onIFrameCallback);
postFrameMessage({
captureSourceId: true
});
} else {
baseGetUserMedia(constraints, successCb, failureCb);
}
};
getUserMedia = navigator.getUserMedia;
} else {
baseGetUserMedia = window.navigator.getUserMedia;
navigator.getUserMedia = function (constraints, successCb, failureCb) {
if (constraints && constraints.video && !!constraints.video.mediaSource) {
// would be fine since no methods
var updatedConstraints = clone(constraints);
// wait for plugin to be ready
AdapterJS.WebRTCPlugin.callWhenPluginReady(function() {
// check if screensharing feature is available
if (!!AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature &&
!!AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable) {
// set the constraints
updatedConstraints.video.optional = updatedConstraints.video.optional || [];
updatedConstraints.video.optional.push({
sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey || 'Screensharing'
});
delete updatedConstraints.video.mediaSource;
} else {
throw new Error('Your WebRTC plugin does not support screensharing');
}
baseGetUserMedia(updatedConstraints, successCb, failureCb);
});
} else {
baseGetUserMedia(constraints, successCb, failureCb);
}
};
getUserMedia = window.navigator.getUserMedia;
}
if (window.webrtcDetectedBrowser === 'chrome') {
var iframe = document.createElement('iframe');
iframe.onload = function() {
iframe.isLoaded = true;
};
iframe.src = 'https://cdn.temasys.com.sg/skylink/extensions/detectRTC.html';
//'https://temasys-cdn.s3.amazonaws.com/skylink/extensions/detection-script-dev/detectRTC.html';
iframe.style.display = 'none';
(document.body || document.documentElement).appendChild(iframe);
var postFrameMessage = function (object) {
object = object || {};
if (!iframe.isLoaded) {
setTimeout(function () {
iframe.contentWindow.postMessage(object, '*');
}, 100);
return;
}
iframe.contentWindow.postMessage(object, '*');
};
}
})();

View File

@@ -21,6 +21,7 @@ var messageHandler = UI.messageHandler;
var Authentication = require("./authentication/Authentication");
var UIUtil = require("./util/UIUtil");
var NicknameHandler = require("./util/NicknameHandler");
var JitsiPopover = require("./util/JitsiPopover");
var CQEvents = require("../../service/connectionquality/CQEvents");
var DesktopSharingEventTypes
= require("../../service/desktopsharing/DesktopSharingEventTypes");
@@ -32,6 +33,7 @@ var UIEvents = require("../../service/UI/UIEvents");
var MemberEvents = require("../../service/members/Events");
var eventEmitter = new EventEmitter();
var roomNode = null;
var roomName = null;
@@ -154,9 +156,6 @@ function registerListeners() {
APP.RTC.addStreamListener(function (stream) {
VideoLayout.onRemoteStreamAdded(stream);
}, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED);
APP.RTC.addStreamListener(function (jid) {
VideoLayout.onVideoTypeChanged(jid);
}, StreamEventTypes.EVENT_TYPE_REMOTE_CHANGED);
APP.RTC.addListener(RTCEvents.LASTN_CHANGED, onLastNChanged);
APP.RTC.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED,
function (resourceJid) {
@@ -172,13 +171,13 @@ function registerListeners() {
VideoLayout.setDeviceAvailabilityIcons(null, devices);
});
APP.RTC.addListener(RTCEvents.VIDEO_MUTE, UI.setVideoMuteButtonsState);
APP.RTC.addListener(RTCEvents.DATA_CHANNEL_OPEN, function() {
APP.RTC.addListener(RTCEvents.DATA_CHANNEL_OPEN, function () {
// when the data channel becomes available, tell the bridge about video
// selections so that it can do adaptive simulcast,
// we want the notification to trigger even if userJid is undefined,
// or null.
var userJid = APP.UI.getLargeVideoJid();
eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, userJid);
var userResource = APP.UI.getLargeVideoResource();
eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, userResource);
});
APP.statistics.addAudioLevelListener(function(jid, audioLevel) {
var resourceJid;
@@ -192,7 +191,7 @@ function registerListeners() {
}
AudioLevels.updateAudioLevel(resourceJid, audioLevel,
UI.getLargeVideoJid());
UI.getLargeVideoResource());
});
APP.desktopsharing.addListener(function () {
ToolbarToggler.showDesktopSharingButton();
@@ -257,25 +256,27 @@ function registerListeners() {
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_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.PARTICIPANT_VIDEO_TYPE_CHANGED,
onPeerVideoTypeChanged);
APP.xmpp.addListener(XMPPEvents.DEVICE_AVAILABLE,
function (resource, devices) {
VideoLayout.setDeviceAvailabilityIcons(resource, devices);
});
APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED, VideoLayout.onAudioMute);
APP.xmpp.addListener(XMPPEvents.VIDEO_MUTED, VideoLayout.onVideoMute);
APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS, function(doMuteAudio) {
APP.xmpp.addListener(XMPPEvents.PARTICIPANT_AUDIO_MUTED,
VideoLayout.onAudioMute);
APP.xmpp.addListener(XMPPEvents.PARTICIPANT_VIDEO_MUTED,
VideoLayout.onVideoMute);
APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS, function (doMuteAudio) {
UI.setAudioMuted(doMuteAudio);
});
APP.members.addListener(MemberEvents.DTMF_SUPPORT_CHANGED,
onDtmfSupportChanged);
onDtmfSupportChanged);
APP.xmpp.addListener(XMPPEvents.START_MUTED_SETTING_CHANGED, function (audio, video) {
SettingsMenu.setStartMuted(audio, video);
});
@@ -288,43 +289,43 @@ function registerListeners() {
"dialog.internalError");
});
APP.xmpp.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR, function() {
APP.xmpp.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR, function () {
messageHandler.showError("dialog.error",
"dialog.SLDFailure");
"dialog.SLDFailure");
});
APP.xmpp.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR, function() {
APP.xmpp.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR, function () {
messageHandler.showError("dialog.error",
"dialog.SRDFailure");
});
APP.xmpp.addListener(XMPPEvents.CREATE_ANSWER_ERROR, function() {
APP.xmpp.addListener(XMPPEvents.CREATE_ANSWER_ERROR, function () {
messageHandler.showError();
});
APP.xmpp.addListener(XMPPEvents.PROMPT_FOR_LOGIN, function() {
APP.xmpp.addListener(XMPPEvents.PROMPT_FOR_LOGIN, function () {
// FIXME: re-use LoginDialog which supports retries
UI.showLoginPopup(connect);
});
APP.xmpp.addListener(XMPPEvents.FOCUS_DISCONNECTED, function(focusComponent, retrySec) {
APP.xmpp.addListener(XMPPEvents.FOCUS_DISCONNECTED, function (focusComponent, retrySec) {
UI.messageHandler.notify(
null, "notify.focus",
'disconnected', "notify.focusFail",
{component: focusComponent, ms: retrySec});
});
APP.xmpp.addListener(XMPPEvents.ROOM_JOIN_ERROR, function(pres) {
APP.xmpp.addListener(XMPPEvents.ROOM_JOIN_ERROR, function (pres) {
UI.messageHandler.openReportDialog(null,
"dialog.joinError", pres);
});
APP.xmpp.addListener(XMPPEvents.ROOM_CONNECT_ERROR, function(pres) {
APP.xmpp.addListener(XMPPEvents.ROOM_CONNECT_ERROR, function (pres) {
UI.messageHandler.openReportDialog(null,
"dialog.connectError", pres);
});
APP.xmpp.addListener(XMPPEvents.READY_TO_JOIN, function() {
APP.xmpp.addListener(XMPPEvents.READY_TO_JOIN, function () {
var roomName = UI.generateRoomName();
APP.xmpp.allocateConferenceFocus(roomName, UI.checkForNicknameAndJoin);
});
//NicknameHandler emits this event
UI.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) {
APP.xmpp.addToPresence("displayName", nickname);
@@ -333,6 +334,15 @@ function registerListeners() {
UI.addListener(UIEvents.LARGEVIDEO_INIT, function () {
AudioLevels.init();
});
if (!interfaceConfig.filmStripOnly) {
APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation);
APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError);
// Listens for video interruption events.
APP.xmpp.addListener(XMPPEvents.CONNECTION_INTERRUPTED, VideoLayout.onVideoInterrupted);
// Listens for video restores events.
APP.xmpp.addListener(XMPPEvents.CONNECTION_RESTORED, VideoLayout.onVideoRestored);
}
}
@@ -382,9 +392,6 @@ UI.start = function (init) {
$("#welcome_page").hide();
$("#videospace").mousemove(function () {
return ToolbarToggler.showToolbar();
});
// Set the defaults for prompt dialogs.
$.prompt.setDefaults({persistent: false});
@@ -396,34 +403,39 @@ UI.start = function (init) {
bindEvents();
setupPrezi();
setupToolbars();
setupChat();
if (!interfaceConfig.filmStripOnly) {
$("#videospace").mousemove(function () {
return ToolbarToggler.showToolbar();
});
setupToolbars();
setupChat();
// Display notice message at the top of the toolbar
if (config.noticeMessage) {
$('#noticeText').text(config.noticeMessage);
$('#notice').css({display: 'block'});
}
$("#downloadlog").click(function (event) {
dump(event.target);
});
}
else
{
$("#header").css("display", "none");
$("#bottomToolbar").css("display", "none");
$("#downloadlog").css("display", "none");
$("#remoteVideos").css("padding", "0px 0px 18px 0px");
$("#remoteVideos").css("right", "0px");
messageHandler.disableNotifications();
$('body').popover("disable");
// $("[data-toggle=popover]").popover("disable");
JitsiPopover.enabled = false;
}
document.title = interfaceConfig.APP_NAME;
$("#downloadlog").click(function (event) {
dump(event.target);
});
if(config.enableWelcomePage && window.location.pathname == "/" &&
(!window.localStorage.welcomePageDisabled ||
window.localStorage.welcomePageDisabled == "false")) {
$("#videoconference_page").hide();
if (!setupWelcomePage)
setupWelcomePage = require("./welcome_page/WelcomePage");
setupWelcomePage();
return;
}
$("#welcome_page").hide();
// Display notice message at the top of the toolbar
if (config.noticeMessage) {
$('#noticeText').text(config.noticeMessage);
$('#notice').css({display: 'block'});
}
if(config.requireDisplayName) {
var currentSettings = Settings.getSettings();
@@ -434,30 +446,33 @@ UI.start = function (init) {
init();
toastr.options = {
"closeButton": true,
"debug": false,
"positionClass": "notification-bottom-right",
"onclick": null,
"showDuration": "300",
"hideDuration": "1000",
"timeOut": "2000",
"extendedTimeOut": "1000",
"showEasing": "swing",
"hideEasing": "linear",
"showMethod": "fadeIn",
"hideMethod": "fadeOut",
"reposition": function() {
if(PanelToggler.isVisible()) {
$("#toast-container").addClass("notification-bottom-right-center");
} else {
$("#toast-container").removeClass("notification-bottom-right-center");
}
},
"newestOnTop": false
};
if (!interfaceConfig.filmStripOnly) {
toastr.options = {
"closeButton": true,
"debug": false,
"positionClass": "notification-bottom-right",
"onclick": null,
"showDuration": "300",
"hideDuration": "1000",
"timeOut": "2000",
"extendedTimeOut": "1000",
"showEasing": "swing",
"hideEasing": "linear",
"showMethod": "fadeIn",
"hideMethod": "fadeOut",
"reposition": function () {
if (PanelToggler.isVisible()) {
$("#toast-container").addClass("notification-bottom-right-center");
} else {
$("#toast-container").removeClass("notification-bottom-right-center");
}
},
"newestOnTop": false
};
SettingsMenu.init();
SettingsMenu.init();
}
};
@@ -530,6 +545,8 @@ function onLocalRoleChanged(jid, info, pres, isModerator) {
Authentication.closeAuthenticationWindow();
messageHandler.notify(null, "notify.me",
'connected', "notify.moderator");
Toolbar.checkAutoRecord();
}
}
@@ -602,6 +619,10 @@ function onMucPresenceStatus(jid, info) {
VideoLayout.setPresenceStatus(Strophe.getResourceFromJid(jid), info.status);
}
function onPeerVideoTypeChanged(resourceJid, newVideoType) {
VideoLayout.onVideoTypeChanged(resourceJid, newVideoType);
}
function onMucRoleChanged(role, displayName) {
VideoLayout.showModeratorIndicator();
@@ -660,21 +681,20 @@ UI.inputDisplayNameHandler = function (value) {
VideoLayout.inputDisplayNameHandler(value);
};
UI.getLargeVideoJid = function() {
return VideoLayout.getLargeVideoJid();
UI.getLargeVideoResource = function () {
return VideoLayout.getLargeVideoResource();
};
UI.generateRoomName = function() {
if(roomName)
return roomName;
var roomnode = null;
UI.getRoomNode = function () {
if (roomNode)
return roomNode;
var path = window.location.pathname;
// determinde the room node from the url
// TODO: just the roomnode or the whole bare jid?
if (config.getroomnode && typeof config.getroomnode === 'function') {
// custom function might be responsible for doing the pushstate
roomnode = config.getroomnode(path);
roomNode = config.getroomnode(path);
} else {
/* fall back to default strategy
* this is making assumptions about how the URL->room mapping happens.
@@ -685,17 +705,22 @@ UI.generateRoomName = function() {
}
*/
if (path.length > 1) {
roomnode = path.substr(1).toLowerCase();
roomNode = path.substr(1).toLowerCase();
} else {
var word = RoomNameGenerator.generateRoomWithoutSeparator();
roomnode = word.toLowerCase();
roomNode = word.toLowerCase();
window.history.pushState('VideoChat',
'Room: ' + word, window.location.pathname + word);
'Room: ' + word, window.location.pathname + word);
}
}
return roomNode;
};
roomName = roomnode + '@' + config.hosts.muc;
UI.generateRoomName = function () {
if (roomName)
return roomName;
var roomNode = UI.getRoomNode();
roomName = roomNode + '@' + config.hosts.muc;
return roomName;
};
@@ -804,10 +829,10 @@ UI.setAudioMuted = function (mute, earlyMute) {
if (!audioMute(mute, function () {
VideoLayout.showLocalAudioIndicator(mute);
UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
UIUtil.buttonClick("#toolbar_button_mute", "icon-microphone icon-mic-disabled");
})) {
// We still click the button.
UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
UIUtil.buttonClick("#toolbar_button_mute", "icon-microphone icon-mic-disabled");
return;
}
};
@@ -834,7 +859,7 @@ UI.dockToolbar = function (isDock) {
};
UI.setVideoMuteButtonsState = function (mute) {
var video = $('#video');
var video = $('#toolbar_button_camera');
var communicativeClass = "icon-camera";
var muteClass = "icon-camera icon-camera-disabled";

View File

@@ -30,8 +30,8 @@ function resize() {
* Creates the Etherpad button and adds it to the toolbar.
*/
function enableEtherpadButton() {
if (!$('#etherpadButton').is(":visible"))
$('#etherpadButton').css({display: 'inline-block'});
if (!$('#toolbar_button_etherpad').is(":visible"))
$('#toolbar_button_etherpad').css({display: 'inline-block'});
}
/**

View File

@@ -17,7 +17,7 @@ var PanelToggler = (function(my) {
var buttons = {
'#chatspace': '#chatBottomButton',
'#contactlist': '#contactListButton',
'#settingsmenu': '#settingsButton'
'#settingsmenu': '#toolbar_button_settings'
};
/**

View File

@@ -19,7 +19,7 @@ function setVisualNotification(show) {
var unreadMsgBottomElement
= document.getElementById('bottomUnreadMessages');
var glower = $('#chatButton');
var glower = $('#toolbar_button_chat');
var bottomGlower = $('#chatBottomButton');
if (unreadMessages) {
@@ -29,7 +29,7 @@ function setVisualNotification(show) {
ToolbarToggler.dockToolbar(true);
var chatButtonElement
= document.getElementById('chatButton').parentNode;
= document.getElementById('toolbar_button_chat');
var leftIndent = (UIUtil.getTextWidth(chatButtonElement) -
UIUtil.getTextWidth(unreadMsgElement)) / 2;
var topIndent = (UIUtil.getTextHeight(chatButtonElement) -

View File

@@ -13,6 +13,7 @@ var AuthenticationEvents
var roomUrl = null;
var sharedKey = '';
var UI = null;
var recordingToaster = null;
var buttonHandlers = {
"toolbar_button_mute": function () {
@@ -46,7 +47,7 @@ var buttonHandlers = {
return APP.desktopsharing.toggleScreenSharing();
},
"toolbar_button_fullScreen": function() {
UIUtil.buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");
UIUtil.buttonClick("#toolbar_button_fullScreen", "icon-full-screen icon-exit-full-screen");
return Toolbar.toggleFullScreen();
},
"toolbar_button_sip": function () {
@@ -122,8 +123,13 @@ function hangup() {
* Starts or stops the recording for the conference.
*/
function toggleRecording() {
function toggleRecording(predefinedToken) {
APP.xmpp.toggleRecording(function (callback) {
if (predefinedToken) {
callback(UIUtil.escapeHtml(predefinedToken));
return;
}
var msg = APP.translation.generateTranslationHTML(
"dialog.recordingToken");
var token = APP.translation.translateString("dialog.token");
@@ -147,7 +153,7 @@ function toggleRecording() {
function () { },
':input:first'
);
}, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState);
}, Toolbar.setRecordingButtonState);
}
/**
@@ -334,7 +340,7 @@ var Toolbar = (function (my) {
*/
my.setupButtonsFromConfig = function () {
if (config.disablePrezi) {
$("#prezi_button").css({display: "none"});
$("#toolbar_button_prezi").css({display: "none"});
}
};
@@ -509,15 +515,15 @@ var Toolbar = (function (my) {
* Unlocks the lock button state.
*/
my.unlockLockButton = function () {
if ($("#lockIcon").hasClass("icon-security-locked"))
UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
if ($("#toolbar_button_security").hasClass("icon-security-locked"))
UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked");
};
/**
* Updates the lock button state to locked.
*/
my.lockLockButton = function () {
if ($("#lockIcon").hasClass("icon-security"))
UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
if ($("#toolbar_button_security").hasClass("icon-security"))
UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked");
};
/**
@@ -540,40 +546,77 @@ var Toolbar = (function (my) {
}
if (show) {
$('#recording').css({display: "inline"});
$('#toolbar_button_record').css({display: "inline-block"});
}
else {
$('#recording').css({display: "none"});
$('#toolbar_button_record').css({display: "none"});
}
};
// Sets the state of the recording button
my.setRecordingButtonState = function (isRecording) {
var selector = $('#recordButton');
if (isRecording) {
my.setRecordingButtonState = function (recordingState) {
var selector = $('#toolbar_button_record');
if (recordingState === 'on') {
selector.removeClass("icon-recEnable");
selector.addClass("icon-recEnable active");
} else {
$("#largeVideo").toggleClass("videoMessageFilter", true);
var recordOnKey = "recording.on";
$('#videoConnectionMessage').attr("data-i18n", recordOnKey);
$('#videoConnectionMessage').text(APP.translation.translateString(recordOnKey));
setTimeout(function(){
$("#largeVideo").toggleClass("videoMessageFilter", false);
$('#videoConnectionMessage').css({display: "none"});
}, 1500);
recordingToaster = messageHandler.notify(null, "recording.toaster", null,
null, null, {timeOut: 0, closeButton: null, tapToDismiss: false});
} else if (recordingState === 'off') {
selector.removeClass("icon-recEnable active");
selector.addClass("icon-recEnable");
$("#largeVideo").toggleClass("videoMessageFilter", false);
$('#videoConnectionMessage').css({display: "none"});
if (recordingToaster)
messageHandler.remove(recordingToaster);
} else if (recordingState === 'pending') {
selector.removeClass("icon-recEnable active");
selector.addClass("icon-recEnable");
$("#largeVideo").toggleClass("videoMessageFilter", true);
var recordPendingKey = "recording.pending";
$('#videoConnectionMessage').attr("data-i18n", recordPendingKey);
$('#videoConnectionMessage').text(APP.translation.translateString(recordPendingKey));
$('#videoConnectionMessage').css({display: "block"});
}
};
// checks whether recording is enabled and whether we have params to start automatically recording
my.checkAutoRecord = function () {
if (config.enableRecording && config.autoRecord) {
toggleRecording(config.autoRecordToken);
}
}
// Shows or hides SIP calls button
my.showSipCallButton = function (show) {
if (APP.xmpp.isSipGatewayEnabled() && show) {
$('#sipCallButton').css({display: "inline-block"});
$('#toolbar_button_sip').css({display: "inline-block"});
} else {
$('#sipCallButton').css({display: "none"});
$('#toolbar_button_sip').css({display: "none"});
}
};
// Shows or hides the dialpad button
my.showDialPadButton = function (show) {
if (show) {
$('#dialPadButton').css({display: "inline-block"});
$('#toolbar_button_dialpad').css({display: "inline-block"});
} else {
$('#dialPadButton').css({display: "none"});
$('#toolbar_button_dialpad').css({display: "none"});
}
};
@@ -621,7 +664,7 @@ var Toolbar = (function (my) {
* @param active the state of the desktop streaming.
*/
my.changeDesktopSharingButtonState = function (active) {
var button = $("#desktopsharing > a");
var button = $("#toolbar_button_desktopsharing");
if (active) {
button.addClass("glow");
} else {

View File

@@ -6,9 +6,9 @@ var toolbarTimeoutObject,
function showDesktopSharingButton() {
if (APP.desktopsharing.isDesktopSharingEnabled()) {
$('#desktopsharing').css({display: "inline"});
$('#toolbar_button_desktopsharing').css({display: "inline-block"});
} else {
$('#desktopsharing').css({display: "none"});
$('#toolbar_button_desktopsharing').css({display: "none"});
}
}
@@ -53,6 +53,8 @@ var ToolbarToggler = {
* Shows the main toolbar.
*/
showToolbar: function () {
if (interfaceConfig.filmStripOnly)
return;
var header = $("#header"),
bottomToolbar = $("#bottomToolbar");
if (!header.is(':visible') || !bottomToolbar.is(":visible")) {
@@ -88,6 +90,9 @@ var ToolbarToggler = {
* @param isDock indicates what operation to perform
*/
dockToolbar: function (isDock) {
if (interfaceConfig.filmStripOnly)
return;
if (isDock) {
// First make sure the toolbar is shown.
if (!$('#header').is(':visible')) {

View File

@@ -46,6 +46,8 @@ var JitsiPopover = (function () {
* Shows the popover
*/
JitsiPopover.prototype.show = function () {
if(!JitsiPopover.enabled)
return;
this.createPopover();
this.popoverShown = true;
};
@@ -118,6 +120,8 @@ var JitsiPopover = (function () {
this.createPopover();
};
JitsiPopover.enabled = true;
return JitsiPopover;
})();

View File

@@ -1,4 +1,11 @@
/* global $, APP, jQuery, toastr */
/**
* Flag for enable/disable of the notifications.
* @type {boolean}
*/
var notificationsEnabled = true;
var messageHandler = (function(my) {
/**
@@ -172,8 +179,19 @@ var messageHandler = (function(my) {
messageHandler.openMessageDialog(titleKey, msgKey);
};
/**
* Displayes notification.
* @param displayName display name of the participant that is associated with the notification.
* @param displayNameKey the key from the language file for the display name.
* @param cls css class for the notification
* @param messageKey the key from the language file for the text of the message.
* @param messageArguments object with the arguments for the message.
* @param options object with language options.
*/
my.notify = function(displayName, displayNameKey,
cls, messageKey, messageArguments, options) {
if(!notificationsEnabled)
return;
var displayNameSpan = '<span class="nickname" ';
if (displayName) {
displayNameSpan += ">" + displayName;
@@ -182,7 +200,7 @@ var messageHandler = (function(my) {
"'>" + APP.translation.translateString(displayNameKey);
}
displayNameSpan += "</span>";
toastr.info(
return toastr.info(
displayNameSpan + '<br>' +
'<span class=' + cls + ' data-i18n="' + messageKey + '"' +
(messageArguments?
@@ -193,6 +211,28 @@ var messageHandler = (function(my) {
'</span>', null, options);
};
/**
* Removes the toaster.
* @param toasterElement
*/
my.remove = function(toasterElement) {
toasterElement.remove();
};
/**
* Disables notifications.
*/
my.disableNotifications = function () {
notificationsEnabled = false;
};
/**
* Enables notifications.
*/
my.enableNotifications = function () {
notificationsEnabled = true;
};
return my;
}(messageHandler || {}));

View File

@@ -78,5 +78,19 @@ module.exports = {
element.setAttribute("data-placement", position);
element.setAttribute("data-html", true);
element.setAttribute("data-container", "body");
},
/**
* Inserts given child element as the first one into the container.
* @param container the container to which new child element will be added
* @param newChild the new element that will be inserted into the container
*/
prependChild: function (container, newChild) {
var firstChild = container.childNodes[0];
if (firstChild) {
container.insertBefore(newChild, firstChild);
} else {
container.appendChild(newChild);
}
}
};

View File

@@ -42,7 +42,7 @@ function getContainerByState(state)
switch (state)
{
case "video":
selector = "#largeVideo";
selector = "#largeVideoWrapper";
break;
case "etherpad":
selector = "#etherpad>iframe";
@@ -255,7 +255,7 @@ function changeVideo(isVisible) {
"none";
}
var isDesktop = APP.RTC.isVideoSrcDesktop(currentSmallVideo.peerJid);
var isDesktop = currentSmallVideo.getVideoType() === 'screen';
// Change the way we'll be measuring and positioning large video
getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize;
@@ -267,7 +267,7 @@ function changeVideo(isVisible) {
if (isVisible) {
LargeVideo.VideoLayout.largeVideoUpdated(currentSmallVideo);
$('#largeVideo').fadeIn(300);
$('#largeVideoWrapper').fadeTo(300, 1);
}
}
@@ -288,7 +288,10 @@ function createLargeVideoHTML()
'<img id="activeSpeakerAvatar" src=""/>' +
'<canvas id="activeSpeakerAudioLevel"></canvas>' +
'</div>' +
'<video id="largeVideo" autoplay oncontextmenu="return false;"></video>';
'<div id="largeVideoWrapper">' +
'<video id="largeVideo" autoplay oncontextmenu="return false;"></video>' +
'</div id="largeVideoWrapper">' +
'<span id="videoConnectionMessage"></span>';
html += '</div>';
$(html).prependTo("#videospace");
@@ -350,7 +353,7 @@ var LargeVideo = {
* @return <tt>true</tt> if visible, <tt>false</tt> - otherwise
*/
isLargeVideoVisible: function() {
return $('#largeVideo').is(':visible');
return $('#largeVideoWrapper').is(':visible');
},
/**
* Returns <tt>true</tt> if the user is currently displayed on large video.
@@ -368,6 +371,11 @@ var LargeVideo = {
var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid);
console.log('hover in ' + resourceJid + ', video: ', newSmallVideo);
if (!newSmallVideo) {
console.error("Small video not found for: " + resourceJid);
return;
}
if (!LargeVideo.isCurrentlyOnLarge(resourceJid) || forceUpdate) {
$('#activeSpeaker').css('visibility', 'hidden');
@@ -385,13 +393,17 @@ var LargeVideo = {
// or null.
this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, resourceJid);
}
if (RTCBrowserType.isSafari()) {
// FIXME In Safari fadeOut works only for the first time
changeVideo(this.isLargeVideoVisible());
} else {
$('#largeVideo').fadeOut(300,
changeVideo.bind($('#largeVideo'), this.isLargeVideoVisible()));
}
// We are doing fadeOut/fadeIn animations on parent div which wraps
// largeVideo, because when Temasys plugin is in use it replaces
// <video> elements with plugin <object> tag. In Safari jQuery is
// unable to store values on this plugin object which breaks all
// animation effects performed on it directly.
//
// If for any reason large video was hidden before calling fadeOut
// changeVideo will never be called, so we call show() in chain just
// to be sure
$('#largeVideoWrapper').show().fadeTo(300, 0,
changeVideo.bind($('#largeVideo'), this.isLargeVideoVisible()));
} else {
if (currentSmallVideo) {
currentSmallVideo.showAvatar();
@@ -407,30 +419,29 @@ var LargeVideo = {
if(!isEnabled)
return;
if (isVisible) {
$('#largeVideo').css({visibility: 'visible'});
$('#largeVideoWrapper').css({visibility: 'visible'});
$('.watermark').css({visibility: 'visible'});
if(currentSmallVideo)
currentSmallVideo.enableDominantSpeaker(true);
}
else {
$('#largeVideo').css({visibility: 'hidden'});
$('#largeVideoWrapper').css({visibility: 'hidden'});
$('#activeSpeaker').css('visibility', 'hidden');
$('.watermark').css({visibility: 'hidden'});
if(currentSmallVideo)
currentSmallVideo.enableDominantSpeaker(false);
}
},
onVideoTypeChanged: function (jid) {
if(!isEnabled)
onVideoTypeChanged: function (resourceJid, newVideoType) {
if (!isEnabled)
return;
var resourceJid = Strophe.getResourceFromJid(jid);
if (LargeVideo.isCurrentlyOnLarge(resourceJid))
{
var isDesktop = APP.RTC.isVideoSrcDesktop(jid);
var isDesktop = newVideoType === 'screen';
getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize;
getVideoPosition = isDesktop ? getDesktopVideoPosition
: getCameraVideoPosition;
this.position(null, null);
this.position(null, null, null, null, true);
}
},
/**
@@ -464,11 +475,19 @@ var LargeVideo = {
var horizontalIndent = videoPosition[0];
var verticalIndent = videoPosition[1];
positionVideo($('#largeVideo'),
positionVideo($('#largeVideoWrapper'),
largeVideoWidth,
largeVideoHeight,
horizontalIndent, verticalIndent, animate);
},
/**
* Resizes the large html elements.
* @param animate boolean property that indicates whether the resize should be animated or not.
* @param isChatVisible boolean property that indicates whether the chat area is displayed or not.
* If that parameter is null the method will check the chat pannel visibility.
* @param completeFunction a function to be called when the video space is resized
* @returns {*[]} array with the current width and height values of the largeVideo html element.
*/
resize: function (animate, isVisible, completeFunction) {
if(!isEnabled)
return;
@@ -481,18 +500,8 @@ var LargeVideo = {
var top = availableHeight / 2 - avatarSize / 4 * 3;
$('#activeSpeaker').css('top', top);
this.VideoLayout.resizeVideoSpace(animate, isVisible, completeFunction);
if(animate) {
$('#videospace').animate({
right: window.innerWidth - availableWidth,
width: availableWidth,
height: availableHeight
},
{
queue: false,
duration: 500,
complete: completeFunction
});
$('#largeVideoContainer').animate({
width: availableWidth,
height: availableHeight
@@ -502,8 +511,6 @@ var LargeVideo = {
duration: 500
});
} else {
$('#videospace').width(availableWidth);
$('#videospace').height(availableHeight);
$('#largeVideoContainer').width(availableWidth);
$('#largeVideoContainer').height(availableHeight);
}
@@ -526,10 +533,11 @@ var LargeVideo = {
}
},
showAvatar: function (resourceJid, show) {
if(!isEnabled)
if (!isEnabled)
return;
if(this.getResourceJid() === resourceJid && state === "video") {
$("#largeVideo").css("visibility", show ? "hidden" : "visible");
if (this.getResourceJid() === resourceJid && state === "video") {
$("#largeVideoWrapper")
.css("visibility", show ? "hidden" : "visible");
$('#activeSpeaker').css("visibility", show ? "visible" : "hidden");
return true;
}
@@ -636,7 +644,7 @@ var LargeVideo = {
document.body.style.background = 'black';
ToolbarToggler.dockToolbar(false);//fix that
});
$('#largeVideo').fadeIn(300, function () {
$('#largeVideoWrapper').fadeIn(300, function () {
self.setLargeVideoVisible(true);
});
break;
@@ -654,14 +662,23 @@ var LargeVideo = {
},
/**
* Sets hover handlers for the large video container div.
*
* @param inHandler
* @param outHandler
*/
setHover: function(inHandler, outHandler)
{
$('#largeVideoContainer').hover(inHandler, outHandler);
},
/**
* Enables/disables the filter indicating a video problem to the user.
*
* @param enable <tt>true</tt> to enable, <tt>false</tt> to disable
*/
enableVideoProblemFilter: function (enable) {
$("#largeVideo").toggleClass("videoProblemFilter", enable);
}
};
module.exports = LargeVideo;

View File

@@ -9,6 +9,7 @@ var RTCBrowserType = require("../../RTC/RTCBrowserType");
function LocalVideo(VideoLayout) {
this.videoSpanId = "localVideoContainer";
this.container = $("#localVideoContainer").get(0);
this.bindHoverHandler();
this.VideoLayout = VideoLayout;
this.flipX = true;
this.isLocal = true;
@@ -165,7 +166,7 @@ LocalVideo.prototype.changeVideo = function (stream, isMuted) {
event.stopPropagation();
}
self.VideoLayout.handleVideoThumbClicked(
false,
true,
APP.xmpp.myResource());
}
@@ -173,19 +174,6 @@ LocalVideo.prototype.changeVideo = function (stream, isMuted) {
localVideoContainerSelector.off('click');
localVideoContainerSelector.on('click', localVideoClick);
// Add hover handler
localVideoContainerSelector.hover(
function() {
self.showDisplayName(true);
},
function() {
if (!LargeVideo.isLargeVideoVisible() ||
!LargeVideo.isCurrentlyOnLarge(self.getResourceJid())) {
self.showDisplayName(false);
}
}
);
if(isMuted) {
APP.UI.setVideoMute(true);
return;
@@ -201,7 +189,8 @@ LocalVideo.prototype.changeVideo = function (stream, isMuted) {
localVideo.oncontextmenu = function () { return false; };
var localVideoContainer = document.getElementById('localVideoWrapper');
localVideoContainer.appendChild(localVideo);
// Put the new video always in front
UIUtil.prependChild(localVideoContainer, localVideo);
var localVideoSelector = $('#' + localVideo.id);

View File

@@ -5,6 +5,7 @@ var AudioLevels = require("../audio_levels/AudioLevels");
var LargeVideo = require("./LargeVideo");
var Avatar = require("../avatar/Avatar");
var RTCBrowserType = require("../../RTC/RTCBrowserType");
var UIUtils = require("../util/UIUtil");
function RemoteVideo(peerJid, VideoLayout) {
this.peerJid = peerJid;
@@ -19,6 +20,7 @@ function RemoteVideo(peerJid, VideoLayout) {
nickfield.className = "nick";
nickfield.appendChild(document.createTextNode(this.resourceJid));
this.container.appendChild(nickfield);
this.bindHoverHandler();
this.flipX = false;
this.isLocal = false;
}
@@ -42,85 +44,96 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
* @param jid the jid indicating the video for which we're adding a menu.
* @param parentElement the parent element where this menu will be added
*/
RemoteVideo.prototype.addRemoteVideoMenu = function () {
var spanElement = document.createElement('span');
spanElement.className = 'remotevideomenu';
this.container.appendChild(spanElement);
if (!interfaceConfig.filmStripOnly) {
RemoteVideo.prototype.addRemoteVideoMenu = function () {
var spanElement = document.createElement('span');
spanElement.className = 'remotevideomenu';
var menuElement = document.createElement('i');
menuElement.className = 'fa fa-angle-down';
menuElement.title = 'Remote user controls';
spanElement.appendChild(menuElement);
this.container.appendChild(spanElement);
var menuElement = document.createElement('i');
menuElement.className = 'fa fa-angle-down';
menuElement.title = 'Remote user controls';
spanElement.appendChild(menuElement);
var popupmenuElement = document.createElement('ul');
popupmenuElement.className = 'popupmenu';
popupmenuElement.id = 'remote_popupmenu_' + this.getResourceJid();
spanElement.appendChild(popupmenuElement);
var popupmenuElement = document.createElement('ul');
popupmenuElement.className = 'popupmenu';
popupmenuElement.id = 'remote_popupmenu_' + this.getResourceJid();
spanElement.appendChild(popupmenuElement);
var muteMenuItem = document.createElement('li');
var muteLinkItem = document.createElement('a');
var muteMenuItem = document.createElement('li');
var muteLinkItem = document.createElement('a');
var mutedIndicator = "<i style='float:left;' class='icon-mic-disabled'></i>";
var mutedIndicator = "<i style='float:left;' " +
"class='icon-mic-disabled'></i>";
if (!this.isMuted) {
muteLinkItem.innerHTML = mutedIndicator +
" <div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.domute'></div>";
muteLinkItem.className = 'mutelink';
}
else {
muteLinkItem.innerHTML = mutedIndicator +
" <div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.muted'></div>";
muteLinkItem.className = 'mutelink disabled';
}
var self = this;
muteLinkItem.onclick = function(){
if ($(this).attr('disabled') != undefined) {
event.preventDefault();
}
var isMute = self.isMuted == true;
APP.xmpp.setMute(self.peerJid, !isMute);
popupmenuElement.setAttribute('style', 'display:none;');
if (isMute) {
this.innerHTML = mutedIndicator +
" <div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.muted'></div>";
this.className = 'mutelink disabled';
if (!this.isMuted) {
muteLinkItem.innerHTML = mutedIndicator +
" <div style='width: 90px;margin-left: 20px;' " +
"data-i18n='videothumbnail.domute'></div>";
muteLinkItem.className = 'mutelink';
}
else {
this.innerHTML = mutedIndicator +
" <div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.domute'></div>";
this.className = 'mutelink';
muteLinkItem.innerHTML = mutedIndicator +
" <div style='width: 90px;margin-left: 20px;' " +
"data-i18n='videothumbnail.muted'></div>";
muteLinkItem.className = 'mutelink disabled';
}
var self = this;
muteLinkItem.onclick = function(){
if ($(this).attr('disabled') != undefined) {
event.preventDefault();
}
var isMute = self.isMuted == true;
APP.xmpp.setMute(self.peerJid, !isMute);
popupmenuElement.setAttribute('style', 'display:none;');
if (isMute) {
this.innerHTML = mutedIndicator +
" <div style='width: 90px;margin-left: 20px;' " +
"data-i18n='videothumbnail.muted'></div>";
this.className = 'mutelink disabled';
}
else {
this.innerHTML = mutedIndicator +
" <div style='width: 90px;margin-left: 20px;' " +
"data-i18n='videothumbnail.domute'></div>";
this.className = 'mutelink';
}
};
muteMenuItem.appendChild(muteLinkItem);
popupmenuElement.appendChild(muteMenuItem);
var ejectIndicator = "<i style='float:left;' class='fa fa-eject'></i>";
var ejectMenuItem = document.createElement('li');
var ejectLinkItem = document.createElement('a');
var ejectText = "<div style='width: 90px;margin-left: 20px;' " +
"data-i18n='videothumbnail.kick'>&nbsp;</div>";
ejectLinkItem.innerHTML = ejectIndicator + ' ' + ejectText;
ejectLinkItem.onclick = function(){
APP.xmpp.eject(self.peerJid);
popupmenuElement.setAttribute('style', 'display:none;');
};
ejectMenuItem.appendChild(ejectLinkItem);
popupmenuElement.appendChild(ejectMenuItem);
var paddingSpan = document.createElement('span');
paddingSpan.className = 'popupmenuPadding';
popupmenuElement.appendChild(paddingSpan);
APP.translation.translateElement(
$("#" + popupmenuElement.id + " > li > a > div"));
};
muteMenuItem.appendChild(muteLinkItem);
popupmenuElement.appendChild(muteMenuItem);
var ejectIndicator = "<i style='float:left;' class='fa fa-eject'></i>";
var ejectMenuItem = document.createElement('li');
var ejectLinkItem = document.createElement('a');
var ejectText = "<div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.kick'>&nbsp;</div>";
ejectLinkItem.innerHTML = ejectIndicator + ' ' + ejectText;
ejectLinkItem.onclick = function(){
APP.xmpp.eject(self.peerJid);
popupmenuElement.setAttribute('style', 'display:none;');
};
ejectMenuItem.appendChild(ejectLinkItem);
popupmenuElement.appendChild(ejectMenuItem);
var paddingSpan = document.createElement('span');
paddingSpan.className = 'popupmenuPadding';
popupmenuElement.appendChild(paddingSpan);
APP.translation.translateElement(
$("#" + popupmenuElement.id + " > li > a > div"));
};
} else {
RemoteVideo.prototype.addRemoteVideoMenu = function() {}
}
/**
* Removes the remote stream element corresponding to the given stream and
@@ -179,7 +192,7 @@ RemoteVideo.prototype.waitForPlayback = function (sel, stream) {
APP.RTC.attachMediaStream(sel, stream);
}
if (RTCBrowserType.isTemasysPluginUsed()) {
sel = self.VideoLayout.getPeerVideoSel(resourceJid);
sel = self.selectVideoElement();
}
self.VideoLayout.videoactive(sel, resourceJid);
sel[0].onplaying = null;
@@ -201,7 +214,8 @@ RemoteVideo.prototype.addRemoteStreamElement = function (sid, stream, thessrc) {
var streamElement = SmallVideo.createStreamElement(sid, stream);
var newElementId = streamElement.id;
this.container.appendChild(streamElement);
// Put new stream element always in front
UIUtils.prependChild(this.container, streamElement);
var sel = $('#' + newElementId);
sel.hide();
@@ -220,9 +234,6 @@ RemoteVideo.prototype.addRemoteStreamElement = function (sid, stream, thessrc) {
};
// Name of video element name is different for IE/Safari
var videoElem = APP.RTC.getVideoElementName();
// Add click handler.
var onClickHandler = function (event) {
@@ -242,21 +253,6 @@ RemoteVideo.prototype.addRemoteStreamElement = function (sid, stream, thessrc) {
if (RTCBrowserType.isTemasysPluginUsed())
sel = $('#' + newElementId);
sel[0].onclick = onClickHandler;
//FIXME
// Add hover handler
$(this.container).hover(
function() {
self.showDisplayName(true);
},
function() {
// If the video has been "pinned" by the user we want to
// keep the display name on place.
if (!LargeVideo.isLargeVideoVisible() ||
!LargeVideo.isCurrentlyOnLarge(self.getResourceJid()))
self.showDisplayName(false);
}
);
},
/**

View File

@@ -53,6 +53,22 @@ SmallVideo.prototype.setDeviceAvailabilityIcons = function (devices) {
}
};
/**
* Sets the type of the video displayed by this instance.
* @param videoType 'camera' or 'screen'
*/
SmallVideo.prototype.setVideoType = function (videoType) {
this.videoType = videoType;
};
/**
* Returns the type of the video displayed by this instance.
* @returns {String} 'camera', 'screen' or undefined.
*/
SmallVideo.prototype.getVideoType = function () {
return this.videoType;
};
/**
* Shows the presence status message for the given video.
*/
@@ -104,6 +120,26 @@ SmallVideo.createStreamElement = function (sid, stream) {
return element;
};
/**
* Configures hoverIn/hoverOut handlers.
*/
SmallVideo.prototype.bindHoverHandler = function () {
// Add hover handler
var self = this;
$(this.container).hover(
function () {
self.showDisplayName(true);
},
function () {
// If the video has been "pinned" by the user we want to
// keep the display name on place.
if (!LargeVideo.isLargeVideoVisible() ||
!LargeVideo.isCurrentlyOnLarge(self.getResourceJid()))
self.showDisplayName(false);
}
);
};
/**
* Updates the data for the indicator
* @param id the id of the indicator
@@ -268,10 +304,20 @@ SmallVideo.prototype.createModeratorIndicatorElement = function () {
APP.translation.translateElement($('#' + this.videoSpanId + ' .focusindicator'));
};
SmallVideo.prototype.selectVideoElement = function () {
var videoElem = APP.RTC.getVideoElementName();
if (!RTCBrowserType.isTemasysPluginUsed()) {
return $('#' + this.videoSpanId).find(videoElem);
} else {
return $('#' + this.videoSpanId +
(this.isLocal ? '>>' : '>') +
videoElem + '>param[value="video"]').parent();
}
};
SmallVideo.prototype.getSrc = function () {
var videoElement = APP.RTC.getVideoElementName();
return APP.RTC.getVideoSrc(
$('#' + this.videoSpanId).find(videoElement).get(0));
var videoElement = this.selectVideoElement().get(0);
return APP.RTC.getVideoSrc(videoElement);
};
SmallVideo.prototype.focus = function(isFocused) {
@@ -283,8 +329,7 @@ SmallVideo.prototype.focus = function(isFocused) {
};
SmallVideo.prototype.hasVideo = function () {
return $("#" + this.videoSpanId).find(
APP.RTC.getVideoElementName()).length !== 0;
return this.selectVideoElement().length !== 0;
};
/**
@@ -304,8 +349,8 @@ SmallVideo.prototype.showAvatar = function (show) {
}
var resourceJid = this.getResourceJid();
var videoElem = APP.RTC.getVideoElementName();
var video = $('#' + this.videoSpanId).find(videoElem);
var video = this.selectVideoElement();
var avatar = $('#avatar_' + resourceJid);
if (show === undefined || show === null) {

View File

@@ -3,6 +3,7 @@ var AudioLevels = require("../audio_levels/AudioLevels");
var ContactList = require("../side_pannels/contactlist/ContactList");
var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
var UIEvents = require("../../../service/UI/UIEvents");
var UIUtil = require("../util/UIUtil");
var RTC = require("../../RTC/RTC");
var RTCBrowserType = require('../../RTC/RTCBrowserType');
@@ -11,7 +12,9 @@ var RemoteVideo = require("./RemoteVideo");
var LargeVideo = require("./LargeVideo");
var LocalVideo = require("./LocalVideo");
var remoteVideos = {};
var remoteVideoTypes = {};
var localVideoThumbnail = null;
var currentDominantSpeaker = null;
@@ -33,8 +36,12 @@ var VideoLayout = (function (my) {
my.init = function (emitter) {
eventEmitter = emitter;
localVideoThumbnail = new LocalVideo(VideoLayout);
if (interfaceConfig.filmStripOnly) {
LargeVideo.disable();
} else {
LargeVideo.init(VideoLayout, emitter);
}
LargeVideo.init(VideoLayout, emitter);
VideoLayout.resizeLargeVideoContainer();
};
@@ -61,6 +68,16 @@ var VideoLayout = (function (my) {
localAudio.autoplay = true;
localAudio.volume = 0;
}
// Now when Temasys plugin is converting also <audio> elements to
// plugin's <object>s, in current layout it will capture click events
// before it reaches the local video object. We hide it here in order
// to prevent that.
if (RTCBrowserType.isIExplorer()) {
// The issue is not present on Safari. Also if we hide it in Safari
// then the local audio track will have 'enabled' flag set to false
// which will result in audio mute issues
$('#localAudio').hide();
}
};
my.changeLocalVideo = function(stream, isMuted) {
@@ -68,6 +85,8 @@ var VideoLayout = (function (my) {
localVideoThumbnail.setDisplayName();
localVideoThumbnail.createConnectionIndicator();
this.onVideoTypeChanged(APP.xmpp.myResource(), stream.videoType);
AudioLevels.updateAudioLevelCanvas(null, VideoLayout);
localVideoThumbnail.changeVideo(stream, isMuted);
@@ -126,21 +145,21 @@ var VideoLayout = (function (my) {
}
};
my.electLastVisibleVideo = function() {
my.electLastVisibleVideo = function () {
// pick the last visible video in the row
// if nobody else is left, this picks the local video
var jid;
var videoElem = RTC.getVideoElementName();
var pick = $('#remoteVideos>span[id!="mixedstream"]:visible:last>' + videoElem);
if (pick.length && APP.RTC.getVideoSrc(pick[0])) {
jid = VideoLayout.getPeerContainerResourceJid(pick[0].parentNode);
var pick = $('#remoteVideos>span[id!="mixedstream"]:visible:last');
if (pick.length) {
jid = VideoLayout.getPeerContainerResourceJid(pick[0]);
} else {
console.info("Last visible video no longer exists");
pick = $('#remoteVideos>span[id!="mixedstream"]>' + videoElem);
if (pick.length && APP.RTC.getVideoSrc(pick[0])) {
jid = VideoLayout.getPeerContainerResourceJid(pick[0].parentNode);
} else {
// Try local video
pick = $('#remoteVideos>span[id!="mixedstream"]');
if (pick.length) {
jid = VideoLayout.getPeerContainerResourceJid(pick[0]);
}
if (!jid) {
// Go with local video
console.info("Fallback to local video...");
jid = APP.xmpp.myResource();
}
@@ -161,7 +180,7 @@ var VideoLayout = (function (my) {
}
};
my.getLargeVideoJid = function () {
my.getLargeVideoResource = function () {
return LargeVideo.getResourceJid();
};
@@ -189,7 +208,7 @@ var VideoLayout = (function (my) {
resourceJid) {
if(focusedVideoResourceJid) {
var oldSmallVideo = VideoLayout.getSmallVideo(focusedVideoResourceJid);
if(oldSmallVideo)
if (oldSmallVideo && !interfaceConfig.filmStripOnly)
oldSmallVideo.focus(false);
}
@@ -216,7 +235,7 @@ var VideoLayout = (function (my) {
// Update focused/pinned interface.
if (resourceJid) {
if(smallVideo)
if (smallVideo && !interfaceConfig.filmStripOnly)
smallVideo.focus(true);
if (!noPinnedEndpointChangedEvent) {
@@ -252,14 +271,21 @@ var VideoLayout = (function (my) {
var resourceJid = Strophe.getResourceFromJid(peerJid);
if(!remoteVideos[resourceJid]) {
remoteVideos[resourceJid] = new RemoteVideo(peerJid, VideoLayout);
if (!remoteVideos[resourceJid]) {
var remoteVideo = new RemoteVideo(peerJid, VideoLayout);
remoteVideos[resourceJid] = remoteVideo;
var videoType = remoteVideoTypes[resourceJid];
if (videoType) {
remoteVideo.setVideoType(videoType);
}
// In case this is not currently in the last n we don't show it.
if (localLastNCount &&
localLastNCount > 0 &&
$('#remoteVideos>span').length >= localLastNCount + 2) {
remoteVideos[resourceJid].showPeerContainer('hide');
remoteVideo.showPeerContainer('hide');
}
else
VideoLayout.resizeThumbnails();
@@ -344,7 +370,11 @@ var VideoLayout = (function (my) {
* Resizes the large video container.
*/
my.resizeLargeVideoContainer = function () {
LargeVideo.resize();
if(LargeVideo.isEnabled()) {
LargeVideo.resize();
} else {
VideoLayout.resizeVideoSpace();
}
VideoLayout.resizeThumbnails();
LargeVideo.position();
};
@@ -363,7 +393,7 @@ var VideoLayout = (function (my) {
if(animate) {
$('#remoteVideos').animate({
height: height
height: height + 2 // adds 2 px because of small video 1px border
},
{
queue: false,
@@ -388,7 +418,7 @@ var VideoLayout = (function (my) {
} else {
// size videos so that while keeping AR and max height, we have a
// nice fit
$('#remoteVideos').height(height);
$('#remoteVideos').height(height + 2);// adds 2 px because of small video 1px border
$('#remoteVideos>span').width(width);
$('#remoteVideos>span').height(height);
@@ -421,7 +451,7 @@ var VideoLayout = (function (my) {
var availableWidth = availableWinWidth / numvids;
var aspectRatio = 16.0 / 9.0;
var maxHeight = Math.min(160, availableHeight);
availableHeight = Math.min(maxHeight, availableWidth / aspectRatio);
availableHeight = Math.min(maxHeight, availableWidth / aspectRatio, window.innerHeight - 18);
if (availableHeight < availableWidth / aspectRatio) {
availableWidth = Math.floor(availableHeight * aspectRatio);
}
@@ -437,15 +467,14 @@ var VideoLayout = (function (my) {
* DOM element
*/
my.getPeerContainerResourceJid = function (containerElement) {
if (localVideoThumbnail.container === containerElement) {
return localVideoThumbnail.getResourceJid();
}
var i = containerElement.id.indexOf('participant_');
if (i >= 0)
return containerElement.id.substring(i + 12);
};
my.getPeerVideoSel = function (peerResourceJid) {
return $('#participant_' + peerResourceJid +
'>' + APP.RTC.getVideoElementName());
return containerElement.id.substring(i + 12);
};
/**
@@ -462,9 +491,9 @@ var VideoLayout = (function (my) {
}
var resource = Strophe.getResourceFromJid(jid);
var videoSel = VideoLayout.getPeerVideoSel(resource);
if (videoSel.length > 0) {
var videoThumb = videoSel[0];
var remoteVideo = remoteVideos[resource];
if (remoteVideo && remoteVideo.selectVideoElement().length) {
var videoThumb = remoteVideo.selectVideoElement()[0];
// It is not always the case that a videoThumb exists (if there is
// no actual video).
if (RTC.getVideoSrc(videoThumb)) {
@@ -523,9 +552,10 @@ var VideoLayout = (function (my) {
var resource = Strophe.getResourceFromJid(jid);
VideoLayout.ensurePeerContainerExists(jid);
remoteVideos[resource].showVideoIndicator(value);
var remoteVideo = remoteVideos[resource];
remoteVideo.showVideoIndicator(value);
var el = VideoLayout.getPeerVideoSel(resource);
var el = remoteVideo.selectVideoElement();
if (!value)
el.show();
else
@@ -557,18 +587,19 @@ var VideoLayout = (function (my) {
if (resourceJid === APP.xmpp.myResource())
return;
var remoteVideo = remoteVideos[resourceJid];
var members = APP.xmpp.getMembers();
// Update the current dominant speaker.
if (resourceJid !== currentDominantSpeaker) {
var currentJID = APP.xmpp.findJidFromResource(currentDominantSpeaker);
var newJID = APP.xmpp.findJidFromResource(resourceJid);
if(currentDominantSpeaker && (!members || !members[currentJID] ||
!members[currentJID].displayName) && remoteVideos[resourceJid]) {
remoteVideos[resourceJid].setDisplayName(null);
if (currentDominantSpeaker && (!members || !members[currentJID] ||
!members[currentJID].displayName) && remoteVideo) {
remoteVideo.setDisplayName(null);
}
if(resourceJid && (!members || !members[newJID] ||
!members[newJID].displayName) && remoteVideos[resourceJid]) {
remoteVideos[resourceJid].setDisplayName(null,
if (resourceJid && (!members || !members[newJID] ||
!members[newJID].displayName) && remoteVideo) {
remoteVideo.setDisplayName(null,
interfaceConfig.DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME);
}
currentDominantSpeaker = resourceJid;
@@ -576,8 +607,11 @@ var VideoLayout = (function (my) {
return;
}
if (!remoteVideo)
return;
// Obtain container for new dominant speaker.
var videoSel = VideoLayout.getPeerVideoSel(resourceJid);
var videoSel = remoteVideo.selectVideoElement();
// Local video will not have container found, but that's ok
// since we don't want to switch to local video.
@@ -647,13 +681,19 @@ var VideoLayout = (function (my) {
lastNEndpoints.indexOf(resourceJid) < 0 &&
localLastNSet.indexOf(resourceJid) < 0) {
console.log("Remove from last N", resourceJid);
remoteVideos[resourceJid].showPeerContainer('hide');
if (remoteVideos[resourceJid])
remoteVideos[resourceJid].showPeerContainer('hide');
else if (APP.xmpp.myResource() !== resourceJid)
console.error("No remote video for: " + resourceJid);
isReceived = false;
} else if (resourceJid &&
$('#participant_' + resourceJid).is(':visible') &&
lastNEndpoints.indexOf(resourceJid) < 0 &&
localLastNSet.indexOf(resourceJid) >= 0) {
remoteVideos[resourceJid].showPeerContainer('avatar');
if (remoteVideos[resourceJid])
remoteVideos[resourceJid].showPeerContainer('avatar');
else if (APP.xmpp.myResource() !== resourceJid)
console.error("No remote video for: " + resourceJid);
isReceived = false;
}
@@ -676,14 +716,15 @@ var VideoLayout = (function (my) {
endpointsEnteringLastN.forEach(function (resourceJid) {
var isVisible = $('#participant_' + resourceJid).is(':visible');
remoteVideos[resourceJid].showPeerContainer('show');
var remoteVideo = remoteVideos[resourceJid];
remoteVideo.showPeerContainer('show');
if (!isVisible) {
console.log("Add to last N", resourceJid);
var jid = APP.xmpp.findJidFromResource(resourceJid);
var mediaStream =
APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
var sel = VideoLayout.getPeerVideoSel(resourceJid);
var sel = remoteVideo.selectVideoElement();
APP.RTC.attachMediaStream(sel, mediaStream.stream);
if (lastNPickupJid == mediaStream.peerjid) {
@@ -809,8 +850,30 @@ var VideoLayout = (function (my) {
VideoLayout.resizeThumbnails();
};
my.onVideoTypeChanged = function (jid) {
LargeVideo.onVideoTypeChanged(jid);
my.onVideoTypeChanged = function (resourceJid, newVideoType) {
if (remoteVideoTypes[resourceJid] === newVideoType) {
return;
}
console.info("Peer video type changed: ", resourceJid, newVideoType);
remoteVideoTypes[resourceJid] = newVideoType;
var smallVideo;
if (resourceJid === APP.xmpp.myResource()) {
if (!localVideoThumbnail) {
console.warn("Local video not ready yet");
return;
}
smallVideo = localVideoThumbnail;
} else if (remoteVideos[resourceJid]) {
smallVideo = remoteVideos[resourceJid];
} else {
return;
}
smallVideo.setVideoType(newVideoType);
LargeVideo.onVideoTypeChanged(resourceJid, newVideoType);
};
my.showMore = function (jid) {
@@ -854,6 +917,37 @@ var VideoLayout = (function (my) {
VideoLayout.resizeThumbnails(true);
};
/**
* Resizes the #videospace html element
* @param animate boolean property that indicates whether the resize should be animated or not.
* @param isChatVisible boolean property that indicates whether the chat area is displayed or not.
* If that parameter is null the method will check the chat pannel visibility.
* @param completeFunction a function to be called when the video space is resized
*/
my.resizeVideoSpace = function (animate, isChatVisible, completeFunction) {
var availableHeight = window.innerHeight;
var availableWidth = UIUtil.getAvailableVideoWidth(isChatVisible);
if (availableWidth < 0 || availableHeight < 0) return;
if(animate) {
$('#videospace').animate({
right: window.innerWidth - availableWidth,
width: availableWidth,
height: availableHeight
},
{
queue: false,
duration: 500,
complete: completeFunction
});
} else {
$('#videospace').width(availableWidth);
$('#videospace').height(availableHeight);
}
};
my.getSmallVideo = function (resourceJid) {
if(resourceJid == APP.xmpp.myResource()) {
return localVideoThumbnail;
@@ -891,6 +985,25 @@ var VideoLayout = (function (my) {
LargeVideo.setHover(inHandler, outHandler);
};
/**
* Indicates that the video has been interrupted.
*/
my.onVideoInterrupted = function () {
LargeVideo.enableVideoProblemFilter(true);
var reconnectingKey = "connection.RECONNECTING";
$('#videoConnectionMessage').attr("data-i18n", reconnectingKey);
$('#videoConnectionMessage').text(APP.translation.translateString(reconnectingKey));
$('#videoConnectionMessage').css({display: "block"});
};
/**
* Indicates that the video has been restored.
*/
my.onVideoRestored = function () {
LargeVideo.enableVideoProblemFilter(false);
$('#videoConnectionMessage').css({display: "none"});
};
return my;
}(VideoLayout || {}));

View File

@@ -1,41 +0,0 @@
/* global $, $iq, config */
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

@@ -0,0 +1,55 @@
/* global $, $iq, config, interfaceConfig */
var configUtil = require('./Util');
var HttpConfig = {
/**
* Sends HTTP POST request to specified <tt>endpoint</tt>. In request
* the name of the room is included in JSON format:
* {
* "rooomName": "someroom12345"
* }
* @param endpoint the name of HTTP endpoint to which HTTP POST request will
* be sent.
* @param roomName the name of the conference room for which config will be
* requested.
* @param complete
*/
obtainConfig: function (endpoint, roomName, complete) {
console.info(
"Send config request to " + endpoint + " for room: " + roomName);
var request = new XMLHttpRequest();
var error = null;
request.onreadystatechange = function (aEvt) {
if (request.readyState == 4) {
var status = request.status;
if (status === 200) {
try {
var data = JSON.parse(request.responseText);
configUtil.overrideConfigJSON(
config, interfaceConfig, data);
complete(true);
return;
} catch (exception) {
console.error("Parse config error: ", exception);
error = exception;
}
} else {
console.error("Get config error: ", request, status);
error = "Get config response status: " + status;
}
complete(false, error);
}
};
request.open("POST", endpoint, true);
request.setRequestHeader(
"Content-Type", "application/json;charset=UTF-8");
request.send({ "roomName": roomName });
}
};
module.exports = HttpConfig;

View File

@@ -0,0 +1,65 @@
/* global $, $iq, config, interfaceConfig */
var configUtils = require('./Util');
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 () {
// Convert 'params' to JSON object
// We have:
// {
// "config.disableAudioLevels": false,
// "config.channelLastN": -1,
// "interfaceConfig.APP_NAME": "Jitsi Meet"
// }
// We want to have:
// {
// "config": {
// "disableAudioLevels": false,
// "channelLastN": -1
// },
// interfaceConfig: {
// APP_NAME: "Jitsi Meet"
// }
// }
var configJSON = {
config: {},
interfaceConfig: {}
};
for (var key in params) {
if (typeof key !== "string") {
console.warn("Invalid config key: ", key);
continue;
}
var confObj = null, confKey;
if (key.indexOf("config.") === 0) {
confObj = configJSON.config;
confKey = key.substr("config.".length);
} else if (key.indexOf("interfaceConfig.") === 0) {
confObj = configJSON.interfaceConfig;
confKey = key.substr("interfaceConfig.".length);
}
if (!confObj)
continue;
confObj[confKey] = params[key];
}
configUtils.overrideConfigJSON(config, interfaceConfig, configJSON);
}
};
module.exports = URLProcessor;

49
modules/config/Util.js Normal file
View File

@@ -0,0 +1,49 @@
/* global $ */
var ConfigUtil = {
/**
* Method overrides JSON properties in <tt>config</tt> and
* <tt>interfaceConfig</tt> Objects with the values from <tt>newConfig</tt>
* @param config the config object for which we'll be overriding properties
* @param interfaceConfig the interfaceConfig object for which we'll be
* overriding properties.
* @param newConfig object containing configuration properties. Destination
* object is selected based on root property name:
* {
* config: {
* // config.js properties to be
* },
* interfaceConfig: {
* // interfaceConfig.js properties here
* }
* }
*/
overrideConfigJSON: function (config, interfaceConfig, newConfig) {
for (var configRoot in newConfig) {
var confObj = null;
if (configRoot == "config") {
confObj = config;
} else if (configRoot == "interfaceConfig") {
confObj = interfaceConfig;
} else {
continue;
}
for (var key in newConfig[configRoot]) {
var value = newConfig[configRoot][key];
if (confObj[key] && typeof confObj[key] !== typeof value)
{
console.warn(
"The type of " + key +
" is wrong. That parameter won't be updated in: ",
confObj);
continue;
}
console.info("Overriding " + key + " with: " + value);
confObj[key] = value;
}
}
}
};
module.exports = ConfigUtil;

View File

@@ -1,4 +1,6 @@
/* global config, $, APP, Strophe, callstats */
var jsSHA = require('jssha');
var io = require('socket.io-client');
var callStats = null;
function initCallback (err, msg) {
@@ -11,7 +13,7 @@ var CallStats = {
if(!config.callStatsID || !config.callStatsSecret || callStats !== null)
return;
callStats = new callstats($,io,jsSHA);
callStats = new callstats($, io, jsSHA);
this.session = jingleSession;
this.peerconnection = jingleSession.peerconnection.peerconnection;
@@ -28,9 +30,7 @@ var CallStats = {
this.userID,
initCallback);
var usage = callStats.fabricUsage.unbundled;
if(config.useBundle)
usage = callStats.fabricUsage.multiplex;
var usage = callStats.fabricUsage.multiplex;
callStats.addNewFabric(this.peerconnection,
Strophe.getResourceFromJid(jingleSession.peerjid),

View File

@@ -4,7 +4,7 @@ var RTCBrowserType = require("../RTC/RTCBrowserType");
/* Whether we support the browser we are running into for logging statistics */
var browserSupported = RTCBrowserType.isChrome() ||
RTCBrowserType.isChrome();
RTCBrowserType.isOpera() || RTCBrowserType.isFirefox();
/**
* Calculates packet lost percent using the number of lost packets and the
* number of all packet.
@@ -296,7 +296,8 @@ StatsCollector.prototype.start = function ()
);
}
if (config.logStats && browserSupported) {
// Logging statistics does not support firefox
if (config.logStats && (browserSupported && !RTCBrowserType.isFirefox())) {
this.gatherStatsIntervalId = setInterval(
function () {
self.peerconnection.getStats(

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,268 @@
/* global $ */
/*
Here we do modifications of local video SSRCs. There are 2 situations we have
to handle:
1. We generate SSRC for local recvonly video stream. This is the case when we
have no local camera and it is not generated automatically, but SSRC=1 is
used implicitly. If that happens RTCP packets will be dropped by the JVB
and we won't be able to request video key frames correctly.
2. A hack to re-use SSRC of the first video stream for any new stream created
in future. It turned out that Chrome may keep on using the SSRC of removed
video stream in RTCP even though a new one has been created. So we just
want to avoid that by re-using it. Jingle 'source-remove'/'source-add'
notifications are blocked once first video SSRC is advertised to the focus.
What this hack does:
1. Stores the SSRC of the first video stream created by
a) scanning Jingle session-accept/session-invite for existing video SSRC
b) watching for 'source-add' for new video stream if it has not been
created in step a)
2. Exposes method 'mungeLocalVideoSSRC' which replaces any new video SSRC with
the stored one. It is called by 'TracablePeerConnection' before local SDP is
returned to the other parts of the application.
3. Scans 'source-remove'/'source-add' notifications for stored video SSRC and
blocks those notifications. This makes Jicofo and all participants think
that it exists all the time even if the video stream has been removed or
replaced locally. Thanks to that there is no additional signaling activity
on video mute or when switching to the desktop stream.
*/
var SDP = require('./SDP');
var RTCBrowserType = require('../RTC/RTCBrowserType');
/**
* The hack is enabled on all browsers except FF by default
* FIXME finish the hack once removeStream method is implemented in FF
* @type {boolean}
*/
var isEnabled = !RTCBrowserType.isFirefox();
/**
* Stored SSRC of local video stream.
*/
var localVideoSSRC;
/**
* SSRC used for recvonly video stream when we have no local camera.
* This is in order to tell Chrome what SSRC should be used in RTCP requests
* instead of 1.
*/
var localRecvOnlySSRC;
/**
* cname for <tt>localRecvOnlySSRC</tt>
*/
var localRecvOnlyCName;
/**
* Method removes <source> element which describes <tt>localVideoSSRC</tt>
* from given Jingle IQ.
* @param modifyIq 'source-add' or 'source-remove' Jingle IQ.
* @param actionName display name of the action which will be printed in log
* messages.
* @returns {*} modified Jingle IQ, so that it does not contain <source> element
* corresponding to <tt>localVideoSSRC</tt> or <tt>null</tt> if no
* other SSRCs left to be signaled after removing it.
*/
var filterOutSource = function (modifyIq, actionName) {
var modifyIqTree = $(modifyIq.tree());
if (!localVideoSSRC)
return modifyIqTree[0];
var videoSSRC = modifyIqTree.find(
'>jingle>content[name="video"]' +
'>description>source[ssrc="' + localVideoSSRC + '"]');
if (!videoSSRC.length) {
return modifyIqTree[0];
}
console.info(
'Blocking ' + actionName + ' for local video SSRC: ' + localVideoSSRC);
videoSSRC.remove();
// Check if any sources still left to be added/removed
if (modifyIqTree.find('>jingle>content>description>source').length) {
return modifyIqTree[0];
} else {
return null;
}
};
/**
* Scans given Jingle IQ for video SSRC and stores it.
* @param jingleIq the Jingle IQ to be scanned for video SSRC.
*/
var storeLocalVideoSSRC = function (jingleIq) {
var videoSSRCs =
$(jingleIq.tree())
.find('>jingle>content[name="video"]>description>source');
videoSSRCs.each(function (idx, ssrcElem) {
if (localVideoSSRC)
return;
// We consider SSRC real only if it has msid attribute
// recvonly streams in FF do not have it as well as local SSRCs
// we generate for recvonly streams in Chrome
var ssrSel = $(ssrcElem);
var msid = ssrSel.find('>parameter[name="msid"]');
if (msid.length) {
var ssrcVal = ssrSel.attr('ssrc');
if (ssrcVal) {
localVideoSSRC = ssrcVal;
console.info('Stored local video SSRC' +
' for future re-use: ' + localVideoSSRC);
}
}
});
};
/**
* Generates new SSRC for local video recvonly stream.
* FIXME what about eventual SSRC collision ?
*/
function generateRecvonlySSRC() {
//
localRecvOnlySSRC =
Math.random().toString(10).substring(2, 11);
localRecvOnlyCName =
Math.random().toString(36).substring(2);
console.info(
"Generated local recvonly SSRC: " + localRecvOnlySSRC +
", cname: " + localRecvOnlyCName);
}
var LocalSSRCReplacement = {
/**
* Method must be called before 'session-initiate' or 'session-invite' is
* sent. Scans the IQ for local video SSRC and stores it if detected.
*
* @param sessionInit our 'session-initiate' or 'session-accept' Jingle IQ
* which will be scanned for local video SSRC.
*/
processSessionInit: function (sessionInit) {
if (!isEnabled)
return;
if (localVideoSSRC) {
console.error("Local SSRC stored already: " + localVideoSSRC);
return;
}
storeLocalVideoSSRC(sessionInit);
},
/**
* If we have local video SSRC stored searched given
* <tt>localDescription</tt> for video SSRC and makes sure it is replaced
* with the stored one.
* @param localDescription local description object that will have local
* video SSRC replaced with the stored one
* @returns modified <tt>localDescription</tt> object.
*/
mungeLocalVideoSSRC: function (localDescription) {
if (!isEnabled)
return localDescription;
if (!localDescription) {
console.warn("localDescription is null or undefined");
return localDescription;
}
// IF we have local video SSRC stored make sure it is replaced
// with old SSRC
if (localVideoSSRC) {
var newSdp = new SDP(localDescription.sdp);
if (newSdp.media[1].indexOf("a=ssrc:") !== -1 &&
!newSdp.containsSSRC(localVideoSSRC)) {
// Get new video SSRC
var map = newSdp.getMediaSsrcMap();
var videoPart = map[1];
var videoSSRCs = videoPart.ssrcs;
var newSSRC = Object.keys(videoSSRCs)[0];
console.info(
"Replacing new video SSRC: " + newSSRC +
" with " + localVideoSSRC);
localDescription.sdp =
newSdp.raw.replace(
new RegExp('a=ssrc:' + newSSRC, 'g'),
'a=ssrc:' + localVideoSSRC);
}
} else {
// Make sure we have any SSRC for recvonly video stream
var sdp = new SDP(localDescription.sdp);
if (sdp.media[1] && sdp.media[1].indexOf('a=ssrc:') === -1 &&
sdp.media[1].indexOf('a=recvonly') !== -1) {
if (!localRecvOnlySSRC) {
generateRecvonlySSRC();
}
console.info('No SSRC in video recvonly stream' +
' - adding SSRC: ' + localRecvOnlySSRC);
sdp.media[1] += 'a=ssrc:' + localRecvOnlySSRC +
' cname:' + localRecvOnlyCName + '\r\n';
localDescription.sdp = sdp.session + sdp.media.join('');
}
}
return localDescription;
},
/**
* Method must be called before 'source-add' notification is sent. In case
* we have local video SSRC advertised already it will be removed from the
* notification. If no other SSRCs are described by given IQ null will be
* returned which means that there is no point in sending the notification.
* @param sourceAdd 'source-add' Jingle IQ to be processed
* @returns modified 'source-add' IQ which can be sent to the focus or
* <tt>null</tt> if no notification shall be sent. It is no longer
* a Strophe IQ Builder instance, but DOM element tree.
*/
processSourceAdd: function (sourceAdd) {
if (!isEnabled)
return sourceAdd;
if (!localVideoSSRC) {
// Store local SSRC if available
storeLocalVideoSSRC(sourceAdd);
return sourceAdd;
} else {
return filterOutSource(sourceAdd, 'source-add');
}
},
/**
* Method must be called before 'source-remove' notification is sent.
* Removes local video SSRC from the notification. If there are no other
* SSRCs described in the given IQ <tt>null</tt> will be returned which
* means that there is no point in sending the notification.
* @param sourceRemove 'source-remove' Jingle IQ to be processed
* @returns modified 'source-remove' IQ which can be sent to the focus or
* <tt>null</tt> if no notification shall be sent. It is no longer
* a Strophe IQ Builder instance, but DOM element tree.
*/
processSourceRemove: function (sourceRemove) {
if (!isEnabled)
return sourceRemove;
return filterOutSource(sourceRemove, 'source-remove');
},
/**
* Turns the hack on or off
* @param enabled <tt>true</tt> to enable the hack or <tt>false</tt>
* to disable it
*/
setEnabled: function (enabled) {
isEnabled = enabled;
}
};
module.exports = LocalSSRCReplacement;

View File

@@ -125,7 +125,7 @@ SDP.prototype.removeMediaLines = function(mediaindex, prefix) {
}
// add content's to a jingle element
SDP.prototype.toJingle = function (elem, thecreator, ssrcs, videoType) {
SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
// console.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]);
var i, j, k, mline, ssrc, rtpmap, tmp, line, lines;
var self = this;
@@ -258,14 +258,6 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs, videoType) {
elem.up();
}
}
// Video type
if (videoType && mline.media == "video") {
elem.c('ssrc-info',
{
xmlns: 'http://jitsi.org/jitmeet',
'video-type': videoType
}).up();
}
elem.up();
// XEP-0339 handle ssrc-group attributes

View File

@@ -106,7 +106,7 @@ SDPDiffer.prototype.getNewMedia = function() {
* @param toJid destination Jid
* @param isAdd indicates if this is remove or add operation.
*/
SDPDiffer.prototype.toJingle = function(modify, videoType) {
SDPDiffer.prototype.toJingle = function(modify) {
var sdpMediaSsrcs = this.getNewMedia();
var self = this;
@@ -141,15 +141,6 @@ SDPDiffer.prototype.toJingle = function(modify, videoType) {
}
modify.up(); // end of parameter
});
// indicate video type
if (videoType && media.mid == 'video') {
modify.c('ssrc-info',
{
xmlns: 'http://jitsi.org/jitmeet',
'video-type': videoType
})
.up();
}
modify.up(); // end of source
});

View File

@@ -1,6 +1,7 @@
var RTC = require('../RTC/RTC');
var RTCBrowserType = require("../RTC/RTCBrowserType.js");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var SSRCReplacement = require("./LocalSSRCReplacement");
function TraceablePeerConnection(ice_config, constraints, session) {
var self = this;
@@ -211,6 +212,9 @@ if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
'localDescription',
function() {
var desc = this.peerconnection.localDescription;
desc = SSRCReplacement.mungeLocalVideoSSRC(desc);
this.trace('getLocalDescription::preTransform', dumpSDP(desc));
// if we're running on FF, transform to Plan B first.
@@ -359,14 +363,17 @@ TraceablePeerConnection.prototype.createOffer
this.peerconnection.createOffer(
function (offer) {
self.trace('createOfferOnSuccess::preTransform', dumpSDP(offer));
// if we're running on FF, transform to Plan B first.
// NOTE this is not tested because in meet the focus generates the
// offer.
// if we're running on FF, transform to Plan B first.
if (RTCBrowserType.usesUnifiedPlan()) {
offer = self.interop.toPlanB(offer);
self.trace('createOfferOnSuccess::postTransform (Plan B)', dumpSDP(offer));
}
offer = SSRCReplacement.mungeLocalVideoSSRC(offer);
if (config.enableSimulcast && self.simulcast.isSupported()) {
offer = self.simulcast.mungeLocalDescription(offer);
self.trace('createOfferOnSuccess::postTransform (simulcast)', dumpSDP(offer));
@@ -393,6 +400,10 @@ TraceablePeerConnection.prototype.createAnswer
answer = self.interop.toPlanB(answer);
self.trace('createAnswerOnSuccess::postTransform (Plan B)', dumpSDP(answer));
}
// munge local video SSRC
answer = SSRCReplacement.mungeLocalVideoSSRC(answer);
if (config.enableSimulcast && self.simulcast.isSupported()) {
answer = self.simulcast.mungeLocalDescription(answer);
self.trace('createAnswerOnSuccess::postTransform (simulcast)', dumpSDP(answer));

View File

@@ -35,7 +35,7 @@ var externalAuthEnabled = false;
// Sip gateway can be enabled by configuring Jigasi host in config.js or
// it will be enabled automatically if focus detects the component through
// service discovery.
var sipGatewayEnabled = config.hosts.call_control !== undefined;
var sipGatewayEnabled;
var eventEmitter = null;
@@ -65,6 +65,9 @@ var Moderator = {
this.xmppService = xmpp;
eventEmitter = emitter;
sipGatewayEnabled =
config.hosts && config.hosts.call_control !== undefined;
// Message listener that talks to POPUP window
function listener(event) {
if (event.data && event.data.sessionId) {

View File

@@ -10,7 +10,7 @@ var recordingEnabled;
* Whether to use a jirecon component for recording, or use the videobridge
* through COLIBRI.
*/
var useJirecon = (typeof config.hosts.jirecon != "undefined");
var useJirecon;
/**
* The ID of the jirecon recording session. Jirecon generates it when we
@@ -19,6 +19,12 @@ var useJirecon = (typeof config.hosts.jirecon != "undefined");
*/
var jireconRid = null;
/**
* The callback to update the recording button. Currently used from colibri
* after receiving a pending status.
*/
var recordingStateChangeCallback = null;
function setRecordingToken(token) {
recordingToken = token;
}
@@ -30,9 +36,9 @@ function setRecordingJirecon(state, token, callback, connection) {
var iq = $iq({to: config.hosts.jirecon, type: 'set'})
.c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
action: state ? 'start' : 'stop',
action: (state === 'on') ? 'start' : 'stop',
mucjid: connection.emuc.roomjid});
if (!state){
if (state === 'off'){
iq.attrs({rid: jireconRid});
}
@@ -44,10 +50,10 @@ function setRecordingJirecon(state, token, callback, connection) {
// TODO wait for an IQ with the real status, since this is
// provisional?
jireconRid = $(result).find('recording').attr('rid');
console.log('Recording ' + (state ? 'started' : 'stopped') +
console.log('Recording ' + ((state === 'on') ? 'started' : 'stopped') +
'(jirecon)' + result);
recordingEnabled = state;
if (!state){
if (state === 'off'){
jireconRid = null;
}
@@ -73,10 +79,19 @@ function setRecordingColibri(state, token, callback, connection) {
function (result) {
console.log('Set recording "', state, '". Result:', result);
var recordingElem = $(result).find('>conference>recording');
var newState = ('true' === recordingElem.attr('state'));
var newState = recordingElem.attr('state');
recordingEnabled = newState;
callback(newState);
if (newState === 'pending' && recordingStateChangeCallback == null) {
recordingStateChangeCallback = callback;
connection.addHandler(function(iq){
var state = $(iq).find('recording').attr('state');
if (state)
recordingStateChangeCallback(state);
}, 'http://jitsi.org/protocol/colibri', 'iq', null, null, null);
}
},
function (error) {
console.warn(error);
@@ -94,8 +109,11 @@ function setRecording(state, token, callback, connection) {
}
var Recording = {
toggleRecording: function (tokenEmptyCallback,
startingCallback, startedCallback, connection) {
init: function () {
useJirecon = config.hosts &&
(typeof config.hosts.jirecon != "undefined");
},
toggleRecording: function (tokenEmptyCallback, recordingStateChangeCallback, connection) {
if (!Moderator.isModerator()) {
console.log(
'non-focus, or conference not yet organized:' +
@@ -108,16 +126,16 @@ var Recording = {
if (!recordingToken && !useJirecon) {
tokenEmptyCallback(function (value) {
setRecordingToken(value);
self.toggleRecording(tokenEmptyCallback,
startingCallback, startedCallback, connection);
self.toggleRecording(tokenEmptyCallback, recordingStateChangeCallback, connection);
});
return;
}
var oldState = recordingEnabled;
startingCallback(!oldState);
setRecording(!oldState,
var newState = (oldState === 'off' || !oldState) ? 'on' : 'off';
setRecording(newState,
recordingToken,
function (state) {
console.log("New recording state: ", state);
@@ -143,7 +161,7 @@ var Recording = {
// have been wrong
setRecordingToken(null);
}
startedCallback(state);
recordingStateChangeCallback(state);
},
connection

View File

@@ -111,10 +111,11 @@ module.exports = function(XMPP, eventEmitter) {
}
}
var url;
// Parse prezi tag.
var presentation = $(pres).find('>prezi');
if (presentation.length) {
var url = presentation.attr('url');
url = presentation.attr('url');
var current = presentation.find('>current').text();
console.log('presentation info received from', from, url);
@@ -129,7 +130,7 @@ module.exports = function(XMPP, eventEmitter) {
}
}
else if (this.preziMap[from] != null) {
var url = this.preziMap[from];
url = this.preziMap[from];
delete this.preziMap[from];
$(document).trigger('presentationremoved.muc', [from, url]);
}
@@ -137,22 +138,22 @@ module.exports = function(XMPP, eventEmitter) {
// Parse audio info tag.
var audioMuted = $(pres).find('>audiomuted');
if (audioMuted.length) {
eventEmitter.emit(XMPPEvents.AUDIO_MUTED,
eventEmitter.emit(XMPPEvents.PARTICIPANT_AUDIO_MUTED,
from, (audioMuted.text() === "true"));
}
// Parse video info tag.
var videoMuted = $(pres).find('>videomuted');
if (videoMuted.length) {
eventEmitter.emit(XMPPEvents.VIDEO_MUTED,
eventEmitter.emit(XMPPEvents.PARTICIPANT_VIDEO_MUTED,
from, (videoMuted.text() === "true"));
}
var startMuted = $(pres).find('>startmuted');
if (startMuted.length)
{
if (startMuted.length && Moderator.isPeerModerator(from)) {
eventEmitter.emit(XMPPEvents.START_MUTED_SETTING_CHANGED,
startMuted.attr("audio") === "true", startMuted.attr("video") === "true");
startMuted.attr("audio") === "true",
startMuted.attr("video") === "true");
}
var devices = $(pres).find('>devices');
@@ -174,6 +175,16 @@ module.exports = function(XMPP, eventEmitter) {
Strophe.getResourceFromJid(from), devicesValues);
}
var videoType = $(pres).find('>videoType');
if (videoType.length)
{
if (videoType.text().length)
{
eventEmitter.emit(XMPPEvents.PARTICIPANT_VIDEO_TYPE_CHANGED,
Strophe.getResourceFromJid(from), videoType.text());
}
}
var stats = $(pres).find('>stats');
if (stats.length) {
var statsObj = {};
@@ -206,7 +217,7 @@ module.exports = function(XMPP, eventEmitter) {
}
var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
member.displayName = (nicktag.length > 0 ? nicktag.html() : null);
member.displayName = (nicktag.length > 0 ? nicktag.text() : null);
if (from == this.myroomjid) {
if (member.affiliation == 'owner') this.isOwner = true;
@@ -482,6 +493,11 @@ module.exports = function(XMPP, eventEmitter) {
.t(this.presMap['videomuted']).up();
}
if (this.presMap['videoTypeNs']) {
pres.c('videoType', { xmlns: this.presMap['videoTypeNs'] })
.t(this.presMap['videoType']).up();
}
if (this.presMap['statsns']) {
var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
for (var stat in this.presMap["stats"])
@@ -514,6 +530,14 @@ module.exports = function(XMPP, eventEmitter) {
addDevicesToPresence: function (devices) {
this.presMap['devices'] = devices;
},
/**
* Adds the info about the type of our video stream.
* @param videoType 'camera' or 'screen'
*/
addVideoTypeToPresence: function (videoType) {
this.presMap['videoTypeNs'] = 'http://jitsi.org/jitmeet/video';
this.presMap['videoType'] = videoType;
},
addPreziToPresence: function (url, currentSlide) {
this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
this.presMap['preziurl'] = url;

View File

@@ -1,27 +1,11 @@
/* jshint -W117 */
var JingleSession = require("./JingleSession");
var JingleSession = require("./JingleSessionPC");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var RTCBrowserType = require("../RTC/RTCBrowserType");
module.exports = function(XMPP, eventEmitter) {
function CallIncomingJingle(sid, connection) {
var sess = connection.jingle.sessions[sid];
// TODO: do we check activecall == null?
connection.jingle.activecall = sess;
eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
// TODO: check affiliation and/or role
console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
sess.usedrip = true; // not-so-naive trickle ice
sess.sendAnswer();
sess.accept();
}
Strophe.addConnectionPlugin('jingle', {
connection: null,
sessions: {},
@@ -54,15 +38,13 @@ module.exports = function(XMPP, eventEmitter) {
this.connection.disco.addFeature('urn:ietf:rfc:4588');
}
// this is dealt with by SDP O/A so we don't need to annouce this
// this is dealt with by SDP O/A so we don't need to announce this
//this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
//this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
if (config.useRtcpMux) {
this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
}
if (config.useBundle) {
this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
}
this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
//this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
}
this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
@@ -87,9 +69,8 @@ module.exports = function(XMPP, eventEmitter) {
this.connection.send(ack);
return true;
}
// compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
// local jid is not checked
if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
if (fromJid != sess.peerjid) {
console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
ack.type = 'error';
ack.c('error', {type: 'cancel'})
@@ -129,20 +110,30 @@ module.exports = function(XMPP, eventEmitter) {
sess.pc_constraints = this.pc_constraints;
sess.ice_config = this.ice_config;
sess.initiate(fromJid, false);
sess.initialize(fromJid, false);
// FIXME: setRemoteDescription should only be done when this call is to be accepted
sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
sess.setOffer($(iq).find('>jingle'));
this.sessions[sess.sid] = sess;
this.jid2session[sess.peerjid] = sess;
// the callback should either
// .sendAnswer and .accept
// or .sendTerminate -- not necessarily synchronus
CallIncomingJingle(sess.sid, this.connection);
// or .sendTerminate -- not necessarily synchronous
// TODO: do we check activecall == null?
this.connection.jingle.activecall = sess;
eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
// TODO: check affiliation and/or role
console.log('emuc data for', sess.peerjid,
this.connection.emuc.members[sess.peerjid]);
sess.sendAnswer();
sess.accept();
break;
case 'session-accept':
sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
sess.setAnswer($(iq).find('>jingle'));
sess.accept();
$(document).trigger('callaccepted.jingle', [sess.sid]);
break;
@@ -209,7 +200,7 @@ module.exports = function(XMPP, eventEmitter) {
sess.pc_constraints = this.pc_constraints;
sess.ice_config = this.ice_config;
sess.initiate(peerjid, true);
sess.initialize(peerjid, true);
this.sessions[sess.sid] = sess;
this.jid2session[sess.peerjid] = sess;
sess.sendOffer();

View File

@@ -171,9 +171,28 @@ function initStrophePlugins()
require("./strophe.logger")();
}
/**
* If given <tt>localStream</tt> is video one this method will advertise it's
* video type in MUC presence.
* @param localStream new or modified <tt>LocalStream</tt>.
*/
function broadcastLocalVideoType(localStream) {
if (localStream.videoType)
XMPP.addToPresence('videoType', localStream.videoType);
}
function registerListeners() {
APP.RTC.addStreamListener(maybeDoJoin,
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
APP.RTC.addStreamListener(
function (localStream) {
maybeDoJoin();
broadcastLocalVideoType(localStream);
},
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED
);
APP.RTC.addStreamListener(
broadcastLocalVideoType,
StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED
);
APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) {
XMPP.addToPresence("devices", devices);
});
@@ -251,6 +270,7 @@ var XMPP = {
initStrophePlugins();
registerListeners();
Moderator.init(this, eventEmitter);
Recording.init();
var configDomain = config.hosts.anonymousdomain || config.hosts.domain;
// Force authenticated domain if room is appended with '?login=true'
if (config.hosts.anonymousdomain &&
@@ -415,9 +435,9 @@ var XMPP = {
return true;
},
toggleRecording: function (tokenEmptyCallback,
startingCallback, startedCallback) {
recordingStateChangeCallback) {
Recording.toggleRecording(tokenEmptyCallback,
startingCallback, startedCallback, connection);
recordingStateChangeCallback, connection);
},
addToPresence: function (name, value, dontSend) {
switch (name) {
@@ -439,6 +459,9 @@ var XMPP = {
case "devices":
connection.emuc.addDevicesToPresence(value);
break;
case "videoType":
connection.emuc.addVideoTypeToPresence(value);
break;
case "startMuted":
if(!Moderator.isModerator())
return;

View File

@@ -14,15 +14,18 @@
],
"author": "",
"readmeFilename": "README.md",
"//": "Callstats.io does not work with recent versions of jsSHA (2.0.1 in particular)",
"dependencies": {
"events": "*",
"pako": "*",
"i18next-client": "1.7.7",
"sdp-interop": "0.1.4",
"sdp-transform": "1.4.0",
"sdp-transform": "1.4.1",
"sdp-simulcast": "0.1.0",
"async": "0.9.0",
"retry": "0.6.1"
"retry": "0.6.1",
"jssha": "1.5.0",
"socket.io-client": "1.3.6"
},
"devDependencies": {
},

View File

@@ -7,9 +7,7 @@ var StreamEventTypes = {
EVENT_TYPE_REMOTE_CREATED: "stream.remote_created",
EVENT_TYPE_REMOTE_ENDED: "stream.remote_ended",
EVENT_TYPE_REMOTE_CHANGED: "stream.changed"
EVENT_TYPE_REMOTE_ENDED: "stream.remote_ended"
};
module.exports = StreamEventTypes;

View File

@@ -1,42 +1,85 @@
var XMPPEvents = {
// Designates an event indicating that the connection to the XMPP server
// failed.
CONNECTION_FAILED: "xmpp.connection.failed",
CONFERENCE_CREATED: "xmpp.conferenceCreated.jingle",
// Designates an event indicating that the media (ICE) connection was
// interrupted. This should go to the RTC module.
CONNECTION_INTERRUPTED: "xmpp.connection.interrupted",
// Designates an event indicating that the media (ICE) connection was
// restored. This should go to the RTC module.
CONNECTION_RESTORED: "xmpp.connection.restored",
// Designates an event indicating that an offer (e.g. Jingle
// session-initiate) was received.
CALL_INCOMING: "xmpp.callincoming.jingle",
DISPOSE_CONFERENCE: "xmpp.dispose_conference",
GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown",
// Designates an event indicating that we were kicked from the XMPP MUC.
KICKED: "xmpp.kicked",
BRIDGE_DOWN: "xmpp.bridge_down",
// Designates an event indicating that the userID for a specific JID has
// changed.
USER_ID_CHANGED: "xmpp.user_id_changed",
// We joined the MUC
// Designates an event indicating that we have joined the XMPP MUC.
MUC_JOINED: "xmpp.muc_joined",
// A member joined the MUC
// Designates an event indicating that a participant joined the XMPP MUC.
MUC_MEMBER_JOINED: "xmpp.muc_member_joined",
// A member left the MUC
// Designates an event indicating that a participant left the XMPP MUC.
MUC_MEMBER_LEFT: "xmpp.muc_member_left",
// Designates an event indicating that the MUC role of a participant has
// changed.
MUC_ROLE_CHANGED: "xmpp.muc_role_changed",
// Designates an event indicating that the XMPP MUC was destroyed.
MUC_DESTROYED: "xmpp.muc_destroyed",
// Designates an event indicating that the display name of a participant
// has changed.
DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
// Designates an event indicating that we received statistics from a
// participant in the MUC.
REMOTE_STATS: "xmpp.remote_stats",
// Designates an event indicating that our role in the XMPP MUC has changed.
LOCAL_ROLE_CHANGED: "xmpp.localrole_changed",
PRESENCE_STATUS: "xmpp.presence_status",
RESERVATION_ERROR: "xmpp.room_reservation_error",
// Designates an event indicating that the subject of the XMPP MUC has
// changed.
SUBJECT_CHANGED: "xmpp.subject_changed",
// Designates an event indicating that an XMPP message in the MUC was
// received.
MESSAGE_RECEIVED: "xmpp.message_received",
// Designates an event indicating that we sent an XMPP message to the MUC.
SENDING_CHAT_MESSAGE: "xmpp.sending_chat_message",
// Designates an event indicating that the video type (e.g. 'camera' or
// 'screen') for a participant has changed.
PARTICIPANT_VIDEO_TYPE_CHANGED: "xmpp.video_type",
// Designates an event indicating that a participant in the XMPP MUC has
// advertised that they have audio muted (or unmuted).
PARTICIPANT_AUDIO_MUTED: "xmpp.audio_muted",
// Designates an event indicating that a participant in the XMPP MUC has
// advertised that they have video muted (or unmuted).
PARTICIPANT_VIDEO_MUTED: "xmpp.video_muted",
// Designates an event indicating that the focus has asked us to mute our
// audio.
AUDIO_MUTED_BY_FOCUS: "xmpp.audio_muted_by_focus",
// Designates an event indicating that a moderator in the room changed the
// "start muted" settings for the conference.
START_MUTED_SETTING_CHANGED: "xmpp.start_muted_setting_changed",
// Designates an event indicating that we should join the conference with
// audio and/or video muted.
START_MUTED_FROM_FOCUS: "xmpp.start_muted_from_focus",
PEERCONNECTION_READY: "xmpp.peerconnection_ready",
CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed",
PASSWORD_REQUIRED: "xmpp.password_required",
AUTHENTICATION_REQUIRED: "xmpp.authentication_required",
CHAT_ERROR_RECEIVED: "xmpp.chat_error_received",
ETHERPAD: "xmpp.etherpad",
DEVICE_AVAILABLE: "xmpp.device_available",
PEERCONNECTION_READY: "xmpp.peerconnection_ready",
CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed",
AUDIO_MUTED: "xmpp.audio_muted",
VIDEO_MUTED: "xmpp.video_muted",
AUDIO_MUTED_BY_FOCUS: "xmpp.audio_muted_by_focus",
START_MUTED_SETTING_CHANGED: "xmpp.start_muted_setting_changed",
START_MUTED_FROM_FOCUS: "xmpp.start_muted_from_focus",
BRIDGE_DOWN: "xmpp.bridge_down",
PRESENCE_STATUS: "xmpp.presence_status",
RESERVATION_ERROR: "xmpp.room_reservation_error",
DISPOSE_CONFERENCE: "xmpp.dispose_conference",
GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown",
// TODO: only used in a hack, should probably be removed.
SET_LOCAL_DESCRIPTION_ERROR: 'xmpp.set_local_description_error',
// TODO: only used in a hack, should probably be removed.
SET_REMOTE_DESCRIPTION_ERROR: 'xmpp.set_remote_description_error',
// TODO: only used in a hack, should probably be removed.
CREATE_ANSWER_ERROR: 'xmpp.create_answer_error',
JINGLE_FATAL_ERROR: 'xmpp.jingle_fatal_error',
PROMPT_FOR_LOGIN: 'xmpp.prompt_for_login',