mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-09 00:00:19 +00:00
Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2b43843b7 | ||
|
|
2f03a0a7fe | ||
|
|
4c2f0d3600 | ||
|
|
a8a0945d73 | ||
|
|
a7048fba06 | ||
|
|
7b35dd89bb | ||
|
|
3561204bb5 | ||
|
|
ee50d07dc3 | ||
|
|
9ec4bc91fc | ||
|
|
88071e5258 | ||
|
|
e79d476d89 | ||
|
|
0fe4999beb | ||
|
|
ae96b9f365 | ||
|
|
922d0bd512 | ||
|
|
9a7bc4ebab | ||
|
|
2081757ba1 | ||
|
|
e9c9fc5e69 | ||
|
|
562761196d | ||
|
|
420514b921 | ||
|
|
eb63b24a9a | ||
|
|
c8bbded994 | ||
|
|
2a2702c13a | ||
|
|
5fc868ee96 | ||
|
|
502eab7278 | ||
|
|
332aafbe20 | ||
|
|
d5258e6197 | ||
|
|
9cc9e6132c | ||
|
|
f60c1d9751 | ||
|
|
5d32318d93 | ||
|
|
fee8482bae | ||
|
|
f2b5cdbfb8 | ||
|
|
60afe2d202 | ||
|
|
18f03e296b | ||
|
|
5cd9db1b6a | ||
|
|
f83404a99e | ||
|
|
7c1ba9242b | ||
|
|
bfcc587047 | ||
|
|
e90d8f5531 | ||
|
|
59033aab28 | ||
|
|
7f1eb617c3 | ||
|
|
fd7e8c9162 | ||
|
|
51e886142b | ||
|
|
dcc206b2b4 | ||
|
|
da75e17ff5 | ||
|
|
8fea9b76ee | ||
|
|
cb024be2d6 | ||
|
|
4c4e99c51a | ||
|
|
4b8bc398dd | ||
|
|
466e7dcc91 | ||
|
|
de30ce0f5c | ||
|
|
fc6f5717cb | ||
|
|
b680ecd2ff | ||
|
|
2bea2eec74 | ||
|
|
f52b1380ee | ||
|
|
baf720c553 | ||
|
|
deaff6af5b | ||
|
|
6ca1e131af | ||
|
|
57b9aeb38c | ||
|
|
cc20a4d776 | ||
|
|
fd404b8465 | ||
|
|
cc29df6376 | ||
|
|
44136e8a55 | ||
|
|
fb875423a9 | ||
|
|
ab4c29eddc | ||
|
|
95e964a089 | ||
|
|
c288aa6e84 | ||
|
|
e5d03d1d11 | ||
|
|
59147f059d | ||
|
|
7793d65a99 | ||
|
|
b77791f4b2 | ||
|
|
4092d67853 | ||
|
|
2ea6be9b2c | ||
|
|
74e7507a73 | ||
|
|
9a31fa3d63 | ||
|
|
fd44cfa7a0 | ||
|
|
ab570d63fa | ||
|
|
b4983b2566 | ||
|
|
fdb470d22f | ||
|
|
c163a22415 | ||
|
|
1dea41d3d4 | ||
|
|
9d321df49e | ||
|
|
d92d8e8299 | ||
|
|
4cac7ac97f | ||
|
|
46a17948d0 | ||
|
|
79ac1e800f | ||
|
|
b0c81985d4 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ node_modules
|
||||
.idea/
|
||||
*.iml
|
||||
.*.tmp
|
||||
deploy-local.sh
|
||||
|
||||
7
Makefile
7
Makefile
@@ -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
33
app.js
@@ -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);
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
36
css/main.css
36
css/main.css
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
19
doc/api.md
19
doc/api.md
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
100
index.html
100
index.html
@@ -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>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
9
lang/languages-sl.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"en": "Angleščina",
|
||||
"bg": "Bolgarščina",
|
||||
"de": "Nemščina",
|
||||
"tr": "Turščina",
|
||||
"it": "Italjanščina",
|
||||
"fr": "Francoščina",
|
||||
"sl": ""
|
||||
}
|
||||
@@ -4,5 +4,6 @@
|
||||
"de": "German",
|
||||
"tr": "Turkish",
|
||||
"it": "Italian",
|
||||
"fr": "French"
|
||||
"fr": "French",
|
||||
"sl": "Slovenian"
|
||||
}
|
||||
|
||||
@@ -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
249
lang/main-sl.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
46300
libs/app.bundle.js
46300
libs/app.bundle.js
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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, '*');
|
||||
};
|
||||
}
|
||||
})();
|
||||
201
modules/UI/UI.js
201
modules/UI/UI.js
@@ -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";
|
||||
|
||||
|
||||
@@ -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'});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,7 @@ var PanelToggler = (function(my) {
|
||||
var buttons = {
|
||||
'#chatspace': '#chatBottomButton',
|
||||
'#contactlist': '#contactListButton',
|
||||
'#settingsmenu': '#settingsButton'
|
||||
'#settingsmenu': '#toolbar_button_settings'
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) -
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
@@ -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;
|
||||
})();
|
||||
|
||||
|
||||
@@ -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 || {}));
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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'> </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'> </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);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 || {}));
|
||||
|
||||
|
||||
@@ -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;
|
||||
55
modules/config/HttpConfigFetch.js
Normal file
55
modules/config/HttpConfigFetch.js
Normal 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;
|
||||
65
modules/config/URLProcessor.js
Normal file
65
modules/config/URLProcessor.js
Normal 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
49
modules/config/Util.js
Normal 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;
|
||||
@@ -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),
|
||||
|
||||
@@ -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
1447
modules/xmpp/JingleSessionPC.js
Normal file
1447
modules/xmpp/JingleSessionPC.js
Normal file
File diff suppressed because it is too large
Load Diff
268
modules/xmpp/LocalSSRCReplacement.js
Normal file
268
modules/xmpp/LocalSSRCReplacement.js
Normal 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;
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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": {
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user