Merge remote-tracking branch 'upstream/master'
Conflicts (resolved manually): css/main.css css/videolayout_default.css index.html
This commit is contained in:
commit
bb04d4574f
|
@ -6,5 +6,6 @@ node_modules
|
|||
deploy-local.sh
|
||||
libs/app.bundle.*
|
||||
libs/lib-jitsi-meet*
|
||||
libs/external_connect.js
|
||||
all.css
|
||||
.remote-sync.json
|
||||
|
|
5
Makefile
5
Makefile
|
@ -31,8 +31,9 @@ deploy-appbundle:
|
|||
|
||||
deploy-lib-jitsi-meet:
|
||||
cp $(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.js \
|
||||
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.map $(DEPLOY_DIR)
|
||||
|
||||
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.map \
|
||||
$(LIBJITSIMEET_DIR)/connection_optimization/external_connect.js \
|
||||
$(DEPLOY_DIR)
|
||||
deploy-css:
|
||||
(cd css; cat $(CSS_FILES)) | $(CLEANCSS) > css/all.css
|
||||
|
||||
|
|
61
app.js
61
app.js
|
@ -1,4 +1,4 @@
|
|||
/* global $, JitsiMeetJS, config */
|
||||
/* global $, JitsiMeetJS, config, getRoomName */
|
||||
/* application specific logic */
|
||||
|
||||
import "babel-polyfill";
|
||||
|
@ -23,43 +23,42 @@ import API from './modules/API/API';
|
|||
|
||||
import UIEvents from './service/UI/UIEvents';
|
||||
|
||||
|
||||
/**
|
||||
* Builds and returns the room name.
|
||||
*/
|
||||
function buildRoomName () {
|
||||
let path = window.location.pathname;
|
||||
let roomName;
|
||||
let roomName = getRoomName();
|
||||
|
||||
// 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
|
||||
roomName = config.getroomnode(path);
|
||||
} else {
|
||||
/* fall back to default strategy
|
||||
* this is making assumptions about how the URL->room mapping happens.
|
||||
* It currently assumes deployment at root, with a rewrite like the
|
||||
* following one (for nginx):
|
||||
location ~ ^/([a-zA-Z0-9]+)$ {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
*/
|
||||
if (path.length > 1) {
|
||||
roomName = path.substr(1).toLowerCase();
|
||||
} else {
|
||||
let word = RoomnameGenerator.generateRoomWithoutSeparator();
|
||||
roomName = word.toLowerCase();
|
||||
window.history.pushState(
|
||||
'VideoChat', `Room: ${word}`, window.location.pathname + word
|
||||
);
|
||||
}
|
||||
if(!roomName) {
|
||||
let word = RoomnameGenerator.generateRoomWithoutSeparator();
|
||||
roomName = word.toLowerCase();
|
||||
window.history.pushState(
|
||||
'VideoChat', `Room: ${word}`, window.location.pathname + word
|
||||
);
|
||||
}
|
||||
|
||||
return roomName;
|
||||
}
|
||||
|
||||
const APP = {
|
||||
// Used by do_external_connect.js if we receive the attach data after
|
||||
// connect was already executed. status property can be "initialized",
|
||||
// "ready" or "connecting". We are interested in "ready" status only which
|
||||
// means that connect was executed but we have to wait for the attach data.
|
||||
// In status "ready" handler property will be set to a function that will
|
||||
// finish the connect process when the attach data or error is received.
|
||||
connect: {
|
||||
status: "initialized",
|
||||
handler: null
|
||||
},
|
||||
// Used for automated performance tests
|
||||
performanceTimes: {
|
||||
"index.loaded": window.indexLoadedTime
|
||||
},
|
||||
UI,
|
||||
settings,
|
||||
conference,
|
||||
connection: null,
|
||||
API,
|
||||
init () {
|
||||
this.keyboardshortcut =
|
||||
|
@ -104,8 +103,9 @@ function obtainConfigAndInit() {
|
|||
// Get config result callback
|
||||
function(success, error) {
|
||||
if (success) {
|
||||
console.log("(TIME) configuration fetched:\t",
|
||||
window.performance.now());
|
||||
var now = APP.performanceTimes["configuration.fetched"] =
|
||||
window.performance.now();
|
||||
console.log("(TIME) configuration fetched:\t", now);
|
||||
init();
|
||||
} else {
|
||||
// Show obtain config error,
|
||||
|
@ -124,7 +124,8 @@ function obtainConfigAndInit() {
|
|||
|
||||
|
||||
$(document).ready(function () {
|
||||
console.log("(TIME) document ready:\t", window.performance.now());
|
||||
var now = APP.performanceTimes["document.ready"] = window.performance.now();
|
||||
console.log("(TIME) document ready:\t", now);
|
||||
|
||||
URLProcessor.setConfigParametersFromUrl();
|
||||
APP.init();
|
||||
|
|
|
@ -328,7 +328,7 @@ export default {
|
|||
]);
|
||||
}).then(([tracks, con]) => {
|
||||
console.log('initialized with %s local tracks', tracks.length);
|
||||
connection = con;
|
||||
APP.connection = connection = con;
|
||||
this._createRoom(tracks);
|
||||
this.isDesktopSharingEnabled =
|
||||
JitsiMeetJS.isDesktopSharingEnabled();
|
||||
|
@ -567,7 +567,7 @@ export default {
|
|||
|
||||
_getConferenceOptions() {
|
||||
let options = config;
|
||||
if(config.enableRecording) {
|
||||
if(config.enableRecording && !config.recordingType) {
|
||||
options.recordingType = (config.hosts &&
|
||||
(typeof config.hosts.jirecon != "undefined"))?
|
||||
"jirecon" : "colibri";
|
||||
|
@ -848,7 +848,8 @@ export default {
|
|||
APP.UI.changeDisplayName(id, displayName);
|
||||
});
|
||||
|
||||
room.on(ConferenceEvents.RECORDING_STATE_CHANGED, (status, error) => {
|
||||
room.on(ConferenceEvents.RECORDER_STATE_CHANGED, (status, error) => {
|
||||
console.log("Received recorder status change: ", status, error);
|
||||
if(status == "error") {
|
||||
console.error(error);
|
||||
return;
|
||||
|
@ -1008,15 +1009,8 @@ export default {
|
|||
|
||||
|
||||
// Starts or stops the recording for the conference.
|
||||
APP.UI.addListener(UIEvents.RECORDING_TOGGLE, (predefinedToken) => {
|
||||
if (predefinedToken) {
|
||||
room.toggleRecording({token: predefinedToken});
|
||||
return;
|
||||
}
|
||||
APP.UI.requestRecordingToken().then((token) => {
|
||||
room.toggleRecording({token: token});
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.RECORDING_TOGGLED, (options) => {
|
||||
room.toggleRecording(options);
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.SUBJECT_CHANGED, (topic) => {
|
||||
|
@ -1116,7 +1110,7 @@ export default {
|
|||
Commands.SHARED_VIDEO, ({value, attributes}, id) => {
|
||||
|
||||
if (attributes.state === 'stop') {
|
||||
APP.UI.stopSharedVideo(id);
|
||||
APP.UI.stopSharedVideo(id, attributes);
|
||||
} else if (attributes.state === 'start') {
|
||||
APP.UI.showSharedVideo(id, value, attributes);
|
||||
} else if (attributes.state === 'playing'
|
||||
|
|
|
@ -5,6 +5,42 @@ import LoginDialog from './modules/UI/authentication/LoginDialog';
|
|||
const ConnectionEvents = JitsiMeetJS.events.connection;
|
||||
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
|
||||
/**
|
||||
* Checks if we have data to use attach instead of connect. If we have the data
|
||||
* executes attach otherwise check if we have to wait for the data. If we have
|
||||
* to wait for the attach data we are setting handler to APP.connect.handler
|
||||
* which is going to be called when the attach data is received otherwise
|
||||
* executes connect.
|
||||
*
|
||||
* @param {string} [id] user id
|
||||
* @param {string} [password] password
|
||||
* @param {string} [roomName] the name of the conference.
|
||||
*/
|
||||
function checkForAttachParametersAndConnect(id, password, connection) {
|
||||
if(window.XMPPAttachInfo){
|
||||
APP.connect.status = "connecting";
|
||||
// When connection optimization is not deployed or enabled the default
|
||||
// value will be window.XMPPAttachInfo.status = "error"
|
||||
// If the connection optimization is deployed and enabled and there is
|
||||
// a failure the value will be window.XMPPAttachInfo.status = "error"
|
||||
if(window.XMPPAttachInfo.status === "error") {
|
||||
connection.connect({id, password});
|
||||
return;
|
||||
}
|
||||
|
||||
var attachOptions = window.XMPPAttachInfo.data;
|
||||
if(attachOptions) {
|
||||
connection.attach(attachOptions);
|
||||
} else {
|
||||
connection.connect({id, password});
|
||||
}
|
||||
} else {
|
||||
APP.connect.status = "ready";
|
||||
APP.connect.handler = checkForAttachParametersAndConnect.bind(null,
|
||||
id, password, connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to open connection using provided credentials.
|
||||
* @param {string} [id]
|
||||
|
@ -18,7 +54,8 @@ function connect(id, password, roomName) {
|
|||
let connectionConfig = config;
|
||||
|
||||
connectionConfig.bosh += '?room=' + roomName;
|
||||
let connection = new JitsiMeetJS.JitsiConnection(null, null, config);
|
||||
let connection
|
||||
= new JitsiMeetJS.JitsiConnection(null, config.token, config);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
connection.addEventListener(
|
||||
|
@ -50,7 +87,7 @@ function connect(id, password, roomName) {
|
|||
reject(err);
|
||||
}
|
||||
|
||||
connection.connect({id, password});
|
||||
checkForAttachParametersAndConnect(id, password, connection);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/* global config, getRoomName, getConfigParamsFromUrl */
|
||||
/* global createConnectionExternally */
|
||||
/**
|
||||
* Implements extrnal connect using createConnectionExtenally function defined
|
||||
* in external_connect.js for Jitsi Meet. Parses the room name and token from
|
||||
* the url and executes createConnectionExtenally.
|
||||
*
|
||||
* NOTE: If you are using lib-jitsi-meet without Jitsi Meet you should use this
|
||||
* file as reference only because the implementation is Jitsi Meet specific.
|
||||
*
|
||||
* NOTE: For optimal results this file should be included right after
|
||||
* exrnal_connect.js.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the token from the URL.
|
||||
*/
|
||||
function buildToken(){
|
||||
var params = getConfigParamsFromUrl();
|
||||
return params["config.token"] || config.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes createConnectionExternally function.
|
||||
*/
|
||||
(function () {
|
||||
// FIXME: Add implementation for changing that config from the url for
|
||||
// consistency
|
||||
var url = config.externalConnectUrl;
|
||||
|
||||
/**
|
||||
* Check if connect from connection.js was executed and executes the handler
|
||||
* that is going to finish the connect work.
|
||||
*/
|
||||
function checkForConnectHandlerAndConnect() {
|
||||
|
||||
if(window.APP && window.APP.connect.status === "ready") {
|
||||
window.APP.connect.handler();
|
||||
}
|
||||
}
|
||||
|
||||
function error_callback(error){
|
||||
if(error) //error=undefined if external connect is disabled.
|
||||
console.warn(error);
|
||||
// Sets that global variable to be used later by connect method in
|
||||
// connection.js
|
||||
window.XMPPAttachInfo = {
|
||||
status: "error"
|
||||
};
|
||||
checkForConnectHandlerAndConnect();
|
||||
}
|
||||
|
||||
if(!url || !window.createConnectionExternally) {
|
||||
error_callback();
|
||||
return;
|
||||
}
|
||||
var room_name = getRoomName();
|
||||
if(!room_name) {
|
||||
error_callback();
|
||||
return;
|
||||
}
|
||||
|
||||
url += "?room=" + room_name;
|
||||
|
||||
var token = buildToken();
|
||||
if(token)
|
||||
url += "&token=" + token;
|
||||
|
||||
createConnectionExternally(url, function(connectionInfo) {
|
||||
// Sets that global variable to be used later by connect method in
|
||||
// connection.js
|
||||
window.XMPPAttachInfo = {
|
||||
status: "success",
|
||||
data: connectionInfo
|
||||
};
|
||||
checkForConnectHandlerAndConnect();
|
||||
}, error_callback);
|
||||
})();
|
|
@ -1,4 +1,5 @@
|
|||
#chatspace {
|
||||
display: none;
|
||||
background-color: black;
|
||||
border-left: 1px solid #424242;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#contactlist {
|
||||
display: none;
|
||||
background-color: black;
|
||||
cursor: default;
|
||||
}
|
||||
|
|
39
css/main.css
39
css/main.css
|
@ -129,21 +129,6 @@ html, body{
|
|||
-moz-transition: all .5s ease-in-out;
|
||||
transition: all .5s ease-in-out;
|
||||
}
|
||||
/*#ffde00*/
|
||||
#toolbar_button_record.active {
|
||||
-webkit-text-shadow: -1px 0 10px #00ccff,
|
||||
0 1px 10px #00ccff,
|
||||
1px 0 10px #00ccff,
|
||||
0 -1px 10px #00ccff;
|
||||
-moz-text-shadow: 1px 0 10px #00ccff,
|
||||
0 1px 10px #00ccff,
|
||||
1px 0 10px #00ccff,
|
||||
0 -1px 10px #00ccff;
|
||||
text-shadow: -1px 0 10px #00ccff,
|
||||
0 1px 10px #00ccff,
|
||||
1px 0 10px #00ccff,
|
||||
0 -1px 10px #00ccff;
|
||||
}
|
||||
|
||||
a.button:hover,
|
||||
a.bottomToolbarButton:hover {
|
||||
|
@ -157,15 +142,6 @@ a.bottomToolbarButton:hover {
|
|||
color: #636363;
|
||||
}
|
||||
|
||||
.header_button_separator {
|
||||
display: inline-block;
|
||||
position:relative;
|
||||
top: 5px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background: #373737;
|
||||
}
|
||||
|
||||
.bottom_button_separator {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
@ -248,19 +224,24 @@ form {
|
|||
position: absolute;
|
||||
}
|
||||
|
||||
div.feedbackButton {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
#feedbackButtonDiv {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: rgba(0,0,0,.50);
|
||||
border-radius: 50%;
|
||||
bottom: 0;
|
||||
font-size: 1em;
|
||||
height: 3em;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
transition: all 0.2s ease-in-out 0s;
|
||||
width: 3em;
|
||||
z-index: 100;
|
||||
transition: all 2s ease-in-out;
|
||||
}
|
||||
|
||||
#feedbackButtonDiv.hidden {
|
||||
bottom: -246px;
|
||||
}
|
||||
|
||||
div.feedbackButton:hover {
|
||||
|
@ -299,7 +280,7 @@ div.feedbackButton:hover {
|
|||
}
|
||||
|
||||
.active {
|
||||
color: #00ccff;
|
||||
background-color: #00ccff;
|
||||
}
|
||||
|
||||
.bottomToolbar_span>span {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#settingsmenu {
|
||||
display: none;
|
||||
background: black;
|
||||
color: #00ccff;
|
||||
overflow-y: auto;
|
||||
|
|
|
@ -504,3 +504,33 @@
|
|||
color: rgba(255,255,255,.5);
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.centeredVideoLabel {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 45%;
|
||||
top: auto;
|
||||
right: auto;
|
||||
left: auto;
|
||||
line-height: 28px;
|
||||
height: 28px;
|
||||
width: auto;
|
||||
padding: 5px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
background: rgba(0,0,0,.5);
|
||||
color: #FFF;
|
||||
z-index: 10000;
|
||||
border-radius: 2px;
|
||||
-webkit-transition: all 2s 2s linear;
|
||||
transition: all 2s 2s linear;
|
||||
}
|
||||
|
||||
.moveToCorner {
|
||||
top: 5px;
|
||||
right: 50px; /*leave free space for the HD label*/
|
||||
margin-right: 0px;
|
||||
margin-left: auto;
|
||||
background: rgba(0,0,0,.3);
|
||||
color: rgba(255,255,255,.5);
|
||||
}
|
||||
|
|
|
@ -9,3 +9,4 @@ sounds /usr/share/jitsi-meet/
|
|||
fonts /usr/share/jitsi-meet/
|
||||
images /usr/share/jitsi-meet/
|
||||
lang /usr/share/jitsi-meet/
|
||||
connection_optimization /usr/share/jitsi-meet/
|
||||
|
|
23
index.html
23
index.html
|
@ -4,6 +4,17 @@
|
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||
<!--#include virtual="title.html" -->
|
||||
<script>
|
||||
window.indexLoadedTime = window.performance.now();
|
||||
console.log("(TIME) index.html loaded:\t", indexLoadedTime);
|
||||
</script>
|
||||
<script src="config.js?v=15"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script src="utils.js?v=1"></script>
|
||||
<!--#include virtual="connection_optimization/connection_optimization.html" -->
|
||||
<script src="connection_optimization/do_external_connect.js?v=1"></script>
|
||||
<script src="interface_config.js?v=6"></script>
|
||||
<script src="libs/lib-jitsi-meet.min.js?v=139"></script>
|
||||
<script src="libs/app.bundle.min.js?v=139"></script>
|
||||
<link rel="icon" type="image/png" href="/images/favicon.ico?v=1"/>
|
||||
<meta property="og:title" content="Jitsi Meet"/>
|
||||
<meta property="og:image" content="/images/jitsilogo.png?v=1"/>
|
||||
|
@ -14,11 +25,6 @@
|
|||
<meta itemprop="image" content="/images/jitsilogo.png?v=1"/>
|
||||
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="css/all.css"/>
|
||||
<script>console.log("(TIME) index.html loaded:\t", window.performance.now());</script>
|
||||
<script src="config.js?v=15"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script src="interface_config.js?v=6"></script>
|
||||
<script src="libs/lib-jitsi-meet.min.js?v=139"></script>
|
||||
<script src="libs/app.bundle.min.js?v=139"></script>
|
||||
<!--#include virtual="plugin.head.html" -->
|
||||
</head>
|
||||
<body>
|
||||
|
@ -138,14 +144,14 @@
|
|||
</span>
|
||||
<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" id="toolbar_button_record" data-container="body" data-toggle="popover" data-placement="bottom" 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>
|
||||
<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 fa fa-share-alt-square" id="toolbar_button_sharedvideo" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared Video" data-i18n="[content]toolbar.sharedvideo" style="display: none"></a>
|
||||
<a class="button fa fa-share-alt-square" id="toolbar_button_sharedvideo" data-container="body" data-toggle="popover" data-placement="bottom" content="Share a YouTube video" data-i18n="[content]toolbar.sharedvideo" style="display: none"></a>
|
||||
<a class="button icon-share-desktop" id="toolbar_button_desktopsharing" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleDesktopSharingPopover" 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>
|
||||
|
@ -176,6 +182,7 @@
|
|||
</div>
|
||||
<span id="videoConnectionMessage"></span>
|
||||
<span id="videoResolutionLabel">HD</span>
|
||||
<span id="recordingLabel" class="centeredVideoLabel"></span>
|
||||
</div>
|
||||
|
||||
<div id="remoteVideos">
|
||||
|
@ -271,7 +278,7 @@
|
|||
</div>
|
||||
<a id="downloadlog" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[data-content]downloadlogs" ><i class="fa fa-cloud-download"></i></a>
|
||||
</div>
|
||||
<div class="feedbackButton">
|
||||
<div id="feedbackButtonDiv">
|
||||
<a id="feedbackButton" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[data-content]feedback"><i class="fa fa-heart"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -51,12 +51,11 @@
|
|||
"mute": "Mute / Unmute",
|
||||
"videomute": "Start / stop camera",
|
||||
"authenticate": "Authenticate",
|
||||
"record": "Record",
|
||||
"lock": "Lock / unlock room",
|
||||
"invite": "Invite others",
|
||||
"chat": "Open / close chat",
|
||||
"etherpad": "Shared document",
|
||||
"sharedvideo": "Shared video",
|
||||
"sharedvideo": "Share a YouTube video",
|
||||
"sharescreen": "Share screen",
|
||||
"fullscreen": "Enter / Exit Full Screen",
|
||||
"sip": "Call SIP number",
|
||||
|
@ -83,10 +82,10 @@
|
|||
"title": "SETTINGS",
|
||||
"update": "Update",
|
||||
"name": "Name",
|
||||
"startAudioMuted": "start without audio",
|
||||
"startVideoMuted": "start without video",
|
||||
"selectCamera": "select camera",
|
||||
"selectMic": "select microphone",
|
||||
"startAudioMuted": "Start without audio",
|
||||
"startVideoMuted": "Start without video",
|
||||
"selectCamera": "Select camera",
|
||||
"selectMic": "Select microphone",
|
||||
"followMe": "Enable follow me"
|
||||
},
|
||||
"videothumbnail":
|
||||
|
@ -179,6 +178,7 @@
|
|||
"joinAgain": "Join again",
|
||||
"Share": "Share",
|
||||
"Save": "Save",
|
||||
"recording": "Recording",
|
||||
"recordingToken": "Enter recording token",
|
||||
"Dial": "Dial",
|
||||
"sipMsg": "Enter SIP number",
|
||||
|
@ -206,7 +206,14 @@
|
|||
"firefoxExtensionPrompt": "You need to install a Firefox extension in order to use screen sharing. Please try again after you <a href='__url__'>get it from here</a>!",
|
||||
"feedbackQuestion": "How was your call?",
|
||||
"thankYou": "Thank you for using __appName__!",
|
||||
"sorryFeedback": "We're sorry to hear that. Would you like to tell us more?"
|
||||
"sorryFeedback": "We're sorry to hear that. Would you like to tell us more?",
|
||||
"liveStreaming": "Live Streaming",
|
||||
"streamKey": "Stream name/key",
|
||||
"startLiveStreaming": "Start live streaming",
|
||||
"stopStreamingWarning": "Are you sure you would like to stop the live streaming?",
|
||||
"stopRecordingWarning": "Are you sure you would like to stop the recording?",
|
||||
"stopLiveStreaming": "Stop live streaming",
|
||||
"stopRecording": "Stop recording"
|
||||
},
|
||||
"email":
|
||||
{
|
||||
|
@ -254,8 +261,20 @@
|
|||
},
|
||||
"recording":
|
||||
{
|
||||
"toaster": "Currently recording!",
|
||||
"pending": "Your recording will start as soon as another participant joins",
|
||||
"on": "Recording has been started"
|
||||
"pending": "Recording waiting for a participant to join...",
|
||||
"on": "Recording",
|
||||
"off": "Recording stopped",
|
||||
"failedToStart": "Recording failed to start",
|
||||
"buttonTooltip": "Start / stop recording"
|
||||
},
|
||||
"liveStreaming":
|
||||
{
|
||||
"pending": "Starting Live Stream...",
|
||||
"on": "Live Streaming",
|
||||
"off": "Live Streaming Stopped",
|
||||
"unavailable": "The live streaming service is currently unavailable. Please try again later.",
|
||||
"failedToStart": "Live streaming failed to start",
|
||||
"buttonTooltip": "Start / stop live stream",
|
||||
"streamIdRequired": "Please fill in the stream id in order to launch the live streaming."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
import UIEvents from '../service/UI/UIEvents';
|
||||
import VideoLayout from './UI/videolayout/VideoLayout';
|
||||
import FilmStrip from './UI/videolayout/FilmStrip';
|
||||
|
||||
/**
|
||||
* The (name of the) command which transports the state (represented by
|
||||
|
@ -25,6 +24,15 @@ import FilmStrip from './UI/videolayout/FilmStrip';
|
|||
*/
|
||||
const _COMMAND = "follow-me";
|
||||
|
||||
/**
|
||||
* The timeout after which a follow-me command that has been received will be
|
||||
* ignored if not consumed.
|
||||
*
|
||||
* @type {number} in seconds
|
||||
* @private
|
||||
*/
|
||||
const _FOLLOW_ME_RECEIVED_TIMEOUT = 30;
|
||||
|
||||
/**
|
||||
* Represents the set of {FollowMe}-related states (properties and their
|
||||
* respective values) which are to be followed by a participant. {FollowMe}
|
||||
|
@ -112,6 +120,7 @@ class FollowMe {
|
|||
constructor (conference, UI) {
|
||||
this._conference = conference;
|
||||
this._UI = UI;
|
||||
this.nextOnStageTimer = 0;
|
||||
|
||||
// The states of the local participant which are to be followed (by the
|
||||
// remote participants when the local participant is in her right to
|
||||
|
@ -127,6 +136,29 @@ class FollowMe {
|
|||
this._onFollowMeCommand.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current state of all follow-me properties, which will fire a
|
||||
* localPropertyChangeEvent and trigger a send of the follow-me command.
|
||||
* @private
|
||||
*/
|
||||
_setFollowMeInitialState() {
|
||||
this._filmStripToggled.bind(this, this._UI.isFilmStripVisible());
|
||||
|
||||
var pinnedId = VideoLayout.getPinnedId();
|
||||
var isPinned = false;
|
||||
var smallVideo;
|
||||
if (pinnedId) {
|
||||
isPinned = true;
|
||||
smallVideo = VideoLayout.getSmallVideo(pinnedId);
|
||||
}
|
||||
|
||||
this._nextOnStage(smallVideo, isPinned);
|
||||
|
||||
this._sharedDocumentToggled
|
||||
.bind(this, this._UI.getSharedDocumentManager().isVisible());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds listeners for the UI states of the local participant which are
|
||||
* to be followed (by the remote participants). A non-moderator (very
|
||||
|
@ -171,9 +203,10 @@ class FollowMe {
|
|||
* to disable it
|
||||
*/
|
||||
enableFollowMe (enable) {
|
||||
this.isEnabled = enable;
|
||||
if (this.isEnabled)
|
||||
if (enable) {
|
||||
this._setFollowMeInitialState();
|
||||
this._addFollowMeListeners();
|
||||
}
|
||||
else
|
||||
this._removeFollowMeListeners();
|
||||
}
|
||||
|
@ -201,7 +234,7 @@ class FollowMe {
|
|||
}
|
||||
|
||||
/**
|
||||
* Changes the nextOnPage property value.
|
||||
* Changes the nextOnStage property value.
|
||||
*
|
||||
* @param smallVideo the {SmallVideo} that was pinned or unpinned
|
||||
* @param isPinned indicates if the given {SmallVideo} was pinned or
|
||||
|
@ -265,6 +298,7 @@ class FollowMe {
|
|||
// issued by a defined commander.
|
||||
if (typeof id === 'undefined')
|
||||
return;
|
||||
|
||||
// The Command(s) API will send us our own commands and we don't want
|
||||
// to act upon them.
|
||||
if (this._conference.isLocalId(id))
|
||||
|
@ -284,6 +318,13 @@ class FollowMe {
|
|||
this._onSharedDocumentVisible(attributes.sharedDocumentVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a film strip open / close event received from FOLLOW-ME
|
||||
* command.
|
||||
* @param filmStripVisible indicates if the film strip has been shown or
|
||||
* hidden
|
||||
* @private
|
||||
*/
|
||||
_onFilmStripVisible(filmStripVisible) {
|
||||
if (typeof filmStripVisible !== 'undefined') {
|
||||
// XXX The Command(s) API doesn't preserve the types (of
|
||||
|
@ -296,25 +337,41 @@ class FollowMe {
|
|||
// eventEmitter as a public field. I'm not sure at the time of this
|
||||
// writing whether calling UI.toggleFilmStrip() is acceptable (from
|
||||
// a design standpoint) either.
|
||||
if (filmStripVisible !== FilmStrip.isFilmStripVisible())
|
||||
this._UI.eventEmitter.emit(
|
||||
UIEvents.TOGGLE_FILM_STRIP,
|
||||
filmStripVisible);
|
||||
if (filmStripVisible !== this._UI.isFilmStripVisible())
|
||||
this._UI.eventEmitter.emit(UIEvents.TOGGLE_FILM_STRIP);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the id received from a FOLLOW-ME command.
|
||||
* @param id the identifier of the next participant to show on stage or
|
||||
* undefined if we're clearing the stage (we're unpining all pined and we
|
||||
* rely on dominant speaker events)
|
||||
* @private
|
||||
*/
|
||||
_onNextOnStage(id) {
|
||||
|
||||
var clickId = null;
|
||||
if(typeof id !== 'undefined' && !VideoLayout.isPinned(id))
|
||||
var pin;
|
||||
if(typeof id !== 'undefined' && !VideoLayout.isPinned(id)) {
|
||||
clickId = id;
|
||||
else if (typeof id == 'undefined' && VideoLayout.getPinnedId())
|
||||
pin = true;
|
||||
}
|
||||
else if (typeof id == 'undefined' && VideoLayout.getPinnedId()) {
|
||||
clickId = VideoLayout.getPinnedId();
|
||||
pin = false;
|
||||
}
|
||||
|
||||
if (clickId)
|
||||
VideoLayout.handleVideoThumbClicked(clickId);
|
||||
this._pinVideoThumbnailById(clickId, pin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a shared document open / close event received from FOLLOW-ME
|
||||
* command.
|
||||
* @param sharedDocumentVisible indicates if the shared document has been
|
||||
* opened or closed
|
||||
* @private
|
||||
*/
|
||||
_onSharedDocumentVisible(sharedDocumentVisible) {
|
||||
if (typeof sharedDocumentVisible !== 'undefined') {
|
||||
// XXX The Command(s) API doesn't preserve the types (of
|
||||
|
@ -328,6 +385,41 @@ class FollowMe {
|
|||
this._UI.getSharedDocumentManager().toggleEtherpad();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pins / unpins the video thumbnail given by clickId.
|
||||
*
|
||||
* @param clickId the identifier of the video thumbnail to pin or unpin
|
||||
* @param pin {true} to pin, {false} to unpin
|
||||
* @private
|
||||
*/
|
||||
_pinVideoThumbnailById(clickId, pin) {
|
||||
var self = this;
|
||||
var smallVideo = VideoLayout.getSmallVideo(clickId);
|
||||
|
||||
// If the SmallVideo for the given clickId exists we proceed with the
|
||||
// pin/unpin.
|
||||
if (smallVideo) {
|
||||
this.nextOnStageTimer = 0;
|
||||
clearTimeout(this.nextOnStageTimout);
|
||||
if (pin && !VideoLayout.isPinned(clickId)
|
||||
|| !pin && VideoLayout.isPinned(clickId))
|
||||
VideoLayout.handleVideoThumbClicked(clickId);
|
||||
}
|
||||
// If there's no SmallVideo object for the given id, lets wait and see
|
||||
// if it's going to be created in the next 30sec.
|
||||
else {
|
||||
this.nextOnStageTimout = setTimeout(function () {
|
||||
if (self.nextOnStageTimer > _FOLLOW_ME_RECEIVED_TIMEOUT) {
|
||||
self.nextOnStageTimer = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
this.nextOnStageTimer++;
|
||||
self._pinVideoThumbnailById(clickId, pin);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FollowMe;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/* global $, APP, config, interfaceConfig */
|
||||
import UIEvents from "../../service/UI/UIEvents";
|
||||
|
||||
/*
|
||||
* Created by Yana Stamcheva on 2/10/15.
|
||||
|
@ -60,6 +61,14 @@ var constructDetailedFeedbackHtml = function() {
|
|||
*/
|
||||
var feedbackWindowCallback = null;
|
||||
|
||||
/**
|
||||
* Shows / hides the feedback button.
|
||||
* @private
|
||||
*/
|
||||
function _toggleFeedbackIcon() {
|
||||
$('#feedbackButtonDiv').toggleClass("hidden");
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines all methods in connection to the Feedback window.
|
||||
*
|
||||
|
@ -73,17 +82,23 @@ var Feedback = {
|
|||
feedbackScore: -1,
|
||||
/**
|
||||
* Initialise the Feedback functionality.
|
||||
* @param emitter the EventEmitter to associate with the Feedback.
|
||||
*/
|
||||
init: function () {
|
||||
init: function (emitter) {
|
||||
// CallStats is the way we send feedback, so we don't have to initialise
|
||||
// if callstats isn't enabled.
|
||||
if (!APP.conference.isCallstatsEnabled())
|
||||
return;
|
||||
|
||||
$("div.feedbackButton").css("display", "block");
|
||||
$("#feedbackButtonDiv").css("display", "block");
|
||||
$("#feedbackButton").click(function (event) {
|
||||
Feedback.openFeedbackWindow();
|
||||
});
|
||||
|
||||
// Show / hide the feedback button whenever the film strip is
|
||||
// shown / hidden.
|
||||
emitter.addListener(UIEvents.TOGGLE_FILM_STRIP, function () {
|
||||
_toggleFeedbackIcon();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Indicates if the feedback functionality is enabled.
|
||||
|
|
|
@ -14,6 +14,7 @@ import UIEvents from "../../service/UI/UIEvents";
|
|||
import CQEvents from '../../service/connectionquality/CQEvents';
|
||||
import EtherpadManager from './etherpad/Etherpad';
|
||||
import SharedVideoManager from './shared_video/SharedVideo';
|
||||
import Recording from "./recording/Recording";
|
||||
|
||||
import VideoLayout from "./videolayout/VideoLayout";
|
||||
import FilmStrip from "./videolayout/FilmStrip";
|
||||
|
@ -251,7 +252,7 @@ UI.initConference = function () {
|
|||
|
||||
Toolbar.checkAutoEnableDesktopSharing();
|
||||
if(!interfaceConfig.filmStripOnly) {
|
||||
Feedback.init();
|
||||
Feedback.init(eventEmitter);
|
||||
}
|
||||
|
||||
// FollowMe attempts to copy certain aspects of the moderator's UI into the
|
||||
|
@ -363,12 +364,16 @@ UI.start = function () {
|
|||
bindEvents();
|
||||
sharedVideoManager = new SharedVideoManager(eventEmitter);
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
|
||||
$("#videospace").mousemove(function () {
|
||||
return ToolbarToggler.showToolbar();
|
||||
});
|
||||
setupToolbars();
|
||||
setupChat();
|
||||
|
||||
// Initialise the recording module.
|
||||
if (config.enableRecording)
|
||||
Recording.init(eventEmitter, config.recordingType);
|
||||
|
||||
// Display notice message at the top of the toolbar
|
||||
if (config.noticeMessage) {
|
||||
$('#noticeText').text(config.noticeMessage);
|
||||
|
@ -566,15 +571,15 @@ UI.updateLocalRole = function (isModerator) {
|
|||
VideoLayout.showModeratorIndicator();
|
||||
|
||||
Toolbar.showSipCallButton(isModerator);
|
||||
Toolbar.showRecordingButton(isModerator);
|
||||
Toolbar.showSharedVideoButton(isModerator);
|
||||
Recording.showRecordingButton(isModerator);
|
||||
SettingsMenu.showStartMutedOptions(isModerator);
|
||||
SettingsMenu.showFollowMeOptions(isModerator);
|
||||
|
||||
if (isModerator) {
|
||||
messageHandler.notify(null, "notify.me", 'connected', "notify.moderator");
|
||||
|
||||
Toolbar.checkAutoRecord();
|
||||
Recording.checkAutoRecord();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -622,6 +627,14 @@ UI.toggleFilmStrip = function () {
|
|||
self.toggleFilmStrip.apply(self, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates if the film strip is currently visible or not.
|
||||
* @returns {true} if the film strip is currently visible, otherwise
|
||||
*/
|
||||
UI.isFilmStripVisible = function () {
|
||||
return FilmStrip.isFilmStripVisible();
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles chat panel.
|
||||
*/
|
||||
|
@ -977,37 +990,8 @@ UI.requestFeedback = function () {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Request recording token from the user.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
UI.requestRecordingToken = function () {
|
||||
let msg = APP.translation.generateTranslationHTML("dialog.recordingToken");
|
||||
let token = APP.translation.translateString("dialog.token");
|
||||
return new Promise(function (resolve, reject) {
|
||||
messageHandler.openTwoButtonDialog(
|
||||
null, null, null,
|
||||
`<h2>${msg}</h2>
|
||||
<input name="recordingToken" type="text"
|
||||
data-i18n="[placeholder]dialog.token"
|
||||
placeholder="${token}" autofocus>`,
|
||||
false, "dialog.Save",
|
||||
function (e, v, m, f) {
|
||||
if (v && f.recordingToken) {
|
||||
resolve(UIUtil.escapeHtml(f.recordingToken));
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
},
|
||||
null,
|
||||
function () { },
|
||||
':input:first'
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
UI.updateRecordingState = function (state) {
|
||||
Toolbar.updateRecordingState(state);
|
||||
Recording.updateRecordingState(state);
|
||||
};
|
||||
|
||||
UI.notifyTokenAuthFailed = function () {
|
||||
|
@ -1139,7 +1123,7 @@ UI.updateSharedVideo = function (id, url, attributes) {
|
|||
*/
|
||||
UI.stopSharedVideo = function (id, attributes) {
|
||||
if (sharedVideoManager)
|
||||
sharedVideoManager.stopSharedVideo(id);
|
||||
sharedVideoManager.stopSharedVideo(id, attributes);
|
||||
};
|
||||
|
||||
module.exports = UI;
|
||||
|
|
|
@ -0,0 +1,357 @@
|
|||
/* global APP, $, config, interfaceConfig */
|
||||
/*
|
||||
* Copyright @ 2015 Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import UIEvents from "../../../service/UI/UIEvents";
|
||||
import UIUtil from '../util/UIUtil';
|
||||
|
||||
/**
|
||||
* Indicates if the recording button should be enabled.
|
||||
*
|
||||
* @returns {boolean} {true} if the
|
||||
* @private
|
||||
*/
|
||||
function _isRecordingButtonEnabled() {
|
||||
return interfaceConfig.TOOLBAR_BUTTONS.indexOf("recording") !== -1
|
||||
&& config.enableRecording;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request live stream token from the user.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function _requestLiveStreamId() {
|
||||
const msg = APP.translation.generateTranslationHTML("dialog.liveStreaming");
|
||||
const token = APP.translation.translateString("dialog.streamKey");
|
||||
const cancelButton
|
||||
= APP.translation.generateTranslationHTML("dialog.Cancel");
|
||||
const backButton = APP.translation.generateTranslationHTML("dialog.Back");
|
||||
const startStreamingButton
|
||||
= APP.translation.generateTranslationHTML("dialog.startLiveStreaming");
|
||||
const streamIdRequired
|
||||
= APP.translation.generateTranslationHTML(
|
||||
"liveStreaming.streamIdRequired");
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
let dialog = APP.UI.messageHandler.openDialogWithStates({
|
||||
state0: {
|
||||
html:
|
||||
`<h2>${msg}</h2>
|
||||
<input name="streamId" type="text"
|
||||
data-i18n="[placeholder]dialog.streamKey"
|
||||
placeholder="${token}" autofocus>`,
|
||||
persistent: false,
|
||||
buttons: [
|
||||
{title: cancelButton, value: false},
|
||||
{title: startStreamingButton, value: true}
|
||||
],
|
||||
focus: ':input:first',
|
||||
defaultButton: 1,
|
||||
submit: function (e, v, m, f) {
|
||||
e.preventDefault();
|
||||
|
||||
if (v) {
|
||||
if (f.streamId && f.streamId.length > 0) {
|
||||
resolve(UIUtil.escapeHtml(f.streamId));
|
||||
dialog.close();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
dialog.goToState('state1');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
reject();
|
||||
dialog.close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
state1: {
|
||||
html: `<h2>${msg}</h2> ${streamIdRequired}`,
|
||||
persistent: false,
|
||||
buttons: [
|
||||
{title: cancelButton, value: false},
|
||||
{title: backButton, value: true}
|
||||
],
|
||||
focus: ':input:first',
|
||||
defaultButton: 1,
|
||||
submit: function (e, v, m, f) {
|
||||
e.preventDefault();
|
||||
if (v === 0) {
|
||||
reject();
|
||||
dialog.close();
|
||||
} else {
|
||||
dialog.goToState('state0');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Request recording token from the user.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function _requestRecordingToken () {
|
||||
let msg = APP.translation.generateTranslationHTML("dialog.recordingToken");
|
||||
let token = APP.translation.translateString("dialog.token");
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
APP.UI.messageHandler.openTwoButtonDialog(
|
||||
null, null, null,
|
||||
`<h2>${msg}</h2>
|
||||
<input name="recordingToken" type="text"
|
||||
data-i18n="[placeholder]dialog.token"
|
||||
placeholder="${token}" autofocus>`,
|
||||
false, "dialog.Save",
|
||||
function (e, v, m, f) {
|
||||
if (v && f.recordingToken) {
|
||||
resolve(UIUtil.escapeHtml(f.recordingToken));
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
},
|
||||
null,
|
||||
function () { },
|
||||
':input:first'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function _showStopRecordingPrompt (recordingType) {
|
||||
var title;
|
||||
var message;
|
||||
var buttonKey;
|
||||
if (recordingType === "jibri") {
|
||||
title = "dialog.liveStreaming";
|
||||
message = "dialog.stopStreamingWarning";
|
||||
buttonKey = "dialog.stopLiveStreaming";
|
||||
}
|
||||
else {
|
||||
title = "dialog.recording";
|
||||
message = "dialog.stopRecordingWarning";
|
||||
buttonKey = "dialog.stopRecording";
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
APP.UI.messageHandler.openTwoButtonDialog(
|
||||
title,
|
||||
null,
|
||||
message,
|
||||
null,
|
||||
false,
|
||||
buttonKey,
|
||||
function(e,v,m,f) {
|
||||
if (v) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function moveToCorner(selector, move) {
|
||||
let moveToCornerClass = "moveToCorner";
|
||||
|
||||
if (move && !selector.hasClass(moveToCornerClass))
|
||||
selector.addClass(moveToCornerClass);
|
||||
else
|
||||
selector.removeClass(moveToCornerClass);
|
||||
}
|
||||
|
||||
var Status = {
|
||||
ON: "on",
|
||||
OFF: "off",
|
||||
AVAILABLE: "available",
|
||||
UNAVAILABLE: "unavailable",
|
||||
PENDING: "pending"
|
||||
};
|
||||
|
||||
var Recording = {
|
||||
/**
|
||||
* Initializes the recording UI.
|
||||
*/
|
||||
init (emitter, recordingType) {
|
||||
this.eventEmitter = emitter;
|
||||
// Use recorder states directly from the library.
|
||||
this.currentState = Status.UNAVAILABLE;
|
||||
|
||||
this.initRecordingButton(recordingType);
|
||||
},
|
||||
|
||||
initRecordingButton(recordingType) {
|
||||
let selector = $('#toolbar_button_record');
|
||||
|
||||
if (recordingType === 'jibri') {
|
||||
this.baseClass = "fa fa-play-circle";
|
||||
this.recordingOnKey = "liveStreaming.on";
|
||||
this.recordingOffKey = "liveStreaming.off";
|
||||
this.recordingPendingKey = "liveStreaming.pending";
|
||||
this.failedToStartKey = "liveStreaming.failedToStart";
|
||||
this.recordingButtonTooltip = "liveStreaming.buttonTooltip";
|
||||
}
|
||||
else {
|
||||
this.baseClass = "icon-recEnable";
|
||||
this.recordingOnKey = "recording.on";
|
||||
this.recordingOffKey = "recording.off";
|
||||
this.recordingPendingKey = "recording.pending";
|
||||
this.failedToStartKey = "recording.failedToStart";
|
||||
this.recordingButtonTooltip = "recording.buttonTooltip";
|
||||
}
|
||||
|
||||
selector.addClass(this.baseClass);
|
||||
selector.attr("data-i18n", "[content]" + this.recordingButtonTooltip);
|
||||
selector.attr("content",
|
||||
APP.translation.translateString(this.recordingButtonTooltip));
|
||||
|
||||
var self = this;
|
||||
selector.click(function () {
|
||||
switch (self.currentState) {
|
||||
case Status.ON:
|
||||
case Status.PENDING: {
|
||||
_showStopRecordingPrompt(recordingType).then(() =>
|
||||
self.eventEmitter.emit(UIEvents.RECORDING_TOGGLED));
|
||||
break;
|
||||
}
|
||||
case Status.AVAILABLE:
|
||||
case Status.OFF: {
|
||||
if (recordingType === 'jibri')
|
||||
_requestLiveStreamId().then((streamId) => {
|
||||
self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
|
||||
{streamId: streamId});
|
||||
});
|
||||
else {
|
||||
if (self.predefinedToken) {
|
||||
self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
|
||||
{token: self.predefinedToken});
|
||||
return;
|
||||
}
|
||||
|
||||
_requestRecordingToken().then((token) => {
|
||||
self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
|
||||
{token: token});
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
APP.UI.messageHandler.openMessageDialog(
|
||||
"dialog.liveStreaming",
|
||||
"liveStreaming.unavailable"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Shows or hides the 'recording' button.
|
||||
showRecordingButton (show) {
|
||||
if (_isRecordingButtonEnabled() && show) {
|
||||
$('#toolbar_button_record').css({display: "inline-block"});
|
||||
} else {
|
||||
$('#toolbar_button_record').css({display: "none"});
|
||||
}
|
||||
},
|
||||
|
||||
updateRecordingState(recordingState) {
|
||||
// I'm the recorder, so I don't want to see any UI related to states.
|
||||
if (config.iAmRecorder)
|
||||
return;
|
||||
|
||||
// If there's no state change, we ignore the update.
|
||||
if (this.currentState === recordingState)
|
||||
return;
|
||||
|
||||
this.setRecordingButtonState(recordingState);
|
||||
},
|
||||
|
||||
// Sets the state of the recording button
|
||||
setRecordingButtonState (recordingState) {
|
||||
let buttonSelector = $('#toolbar_button_record');
|
||||
let labelSelector = $('#recordingLabel');
|
||||
|
||||
// TODO: handle recording state=available
|
||||
if (recordingState === Status.ON) {
|
||||
|
||||
buttonSelector.removeClass(this.baseClass);
|
||||
buttonSelector.addClass(this.baseClass + " active");
|
||||
|
||||
labelSelector.attr("data-i18n", this.recordingOnKey);
|
||||
moveToCorner(labelSelector, true, 3000);
|
||||
labelSelector
|
||||
.text(APP.translation.translateString(this.recordingOnKey));
|
||||
} else if (recordingState === Status.OFF
|
||||
|| recordingState === Status.UNAVAILABLE) {
|
||||
|
||||
// We don't want to do any changes if this is
|
||||
// an availability change.
|
||||
if (this.currentState !== Status.ON
|
||||
&& this.currentState !== Status.PENDING)
|
||||
return;
|
||||
|
||||
buttonSelector.removeClass(this.baseClass + " active");
|
||||
buttonSelector.addClass(this.baseClass);
|
||||
|
||||
moveToCorner(labelSelector, false);
|
||||
let messageKey;
|
||||
if (this.currentState === Status.PENDING)
|
||||
messageKey = this.failedToStartKey;
|
||||
else
|
||||
messageKey = this.recordingOffKey;
|
||||
|
||||
labelSelector.attr("data-i18n", messageKey);
|
||||
labelSelector.text(APP.translation.translateString(messageKey));
|
||||
|
||||
setTimeout(function(){
|
||||
$('#recordingLabel').css({display: "none"});
|
||||
}, 5000);
|
||||
}
|
||||
else if (recordingState === Status.PENDING) {
|
||||
|
||||
buttonSelector.removeClass(this.baseClass + " active");
|
||||
buttonSelector.addClass(this.baseClass);
|
||||
|
||||
moveToCorner(labelSelector, false);
|
||||
labelSelector
|
||||
.attr("data-i18n", this.recordingPendingKey);
|
||||
labelSelector
|
||||
.text(APP.translation.translateString(
|
||||
this.recordingPendingKey));
|
||||
}
|
||||
|
||||
this.currentState = recordingState;
|
||||
|
||||
// We don't show the label for available state.
|
||||
if (recordingState !== Status.AVAILABLE
|
||||
&& !labelSelector.is(":visible"))
|
||||
labelSelector.css({display: "inline-block"});
|
||||
},
|
||||
// checks whether recording is enabled and whether we have params
|
||||
// to start automatically recording
|
||||
checkAutoRecord () {
|
||||
if (_isRecordingButtonEnabled && config.autoRecord) {
|
||||
this.predefinedToken = UIUtil.escapeHtml(config.autoRecordToken);
|
||||
this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED,
|
||||
this.predefinedToken);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default Recording;
|
|
@ -44,7 +44,8 @@ export default class SharedVideoManager {
|
|||
|
||||
if(APP.conference.isLocalId(this.from)) {
|
||||
showStopVideoPropmpt().then(() =>
|
||||
this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO, null, 'stop'));
|
||||
this.emitter.emit(
|
||||
UIEvents.UPDATE_SHARED_VIDEO, this.url, 'stop'));
|
||||
} else {
|
||||
messageHandler.openMessageDialog(
|
||||
"dialog.shareVideoTitle",
|
||||
|
@ -64,12 +65,18 @@ export default class SharedVideoManager {
|
|||
if (this.isSharedVideoShown)
|
||||
return;
|
||||
|
||||
this.isSharedVideoShown = true;
|
||||
|
||||
// the video url
|
||||
this.url = url;
|
||||
|
||||
// the owner of the video
|
||||
this.from = id;
|
||||
|
||||
//listen for local audio mute events
|
||||
this.localAudioMutedListener = this.localAudioMuted.bind(this);
|
||||
this.emitter.on(UIEvents.AUDIO_MUTED, this.localAudioMutedListener);
|
||||
|
||||
// This code loads the IFrame Player API code asynchronously.
|
||||
var tag = document.createElement('script');
|
||||
|
||||
|
@ -82,7 +89,7 @@ export default class SharedVideoManager {
|
|||
// we need to operate with player after start playing
|
||||
// self.player will be defined once it start playing
|
||||
// and will process any initial attributes if any
|
||||
this.initialAttributes = null;
|
||||
this.initialAttributes = attributes;
|
||||
|
||||
var self = this;
|
||||
if(self.isPlayerAPILoaded)
|
||||
|
@ -91,7 +98,7 @@ export default class SharedVideoManager {
|
|||
window.onYouTubeIframeAPIReady = function() {
|
||||
self.isPlayerAPILoaded = true;
|
||||
let showControls = APP.conference.isLocalId(self.from) ? 1 : 0;
|
||||
new YT.Player('sharedVideoIFrame', {
|
||||
let p = new YT.Player('sharedVideoIFrame', {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
videoId: self.url,
|
||||
|
@ -108,6 +115,18 @@ export default class SharedVideoManager {
|
|||
'onError': onPlayerError
|
||||
}
|
||||
});
|
||||
|
||||
// add listener for volume changes
|
||||
p.addEventListener(
|
||||
"onVolumeChange", "onVolumeChange");
|
||||
|
||||
if (APP.conference.isLocalId(self.from)){
|
||||
// adds progress listener that will be firing events
|
||||
// while we are paused and we change the progress of the
|
||||
// video (seeking forward or backward on the video)
|
||||
p.addEventListener(
|
||||
"onVideoProgress", "onVideoProgress");
|
||||
}
|
||||
};
|
||||
|
||||
window.onPlayerStateChange = function(event) {
|
||||
|
@ -130,6 +149,32 @@ export default class SharedVideoManager {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Track player progress while paused.
|
||||
* @param event
|
||||
*/
|
||||
window.onVideoProgress = function (event) {
|
||||
let state = event.target.getPlayerState();
|
||||
if (state == YT.PlayerState.PAUSED) {
|
||||
self.updateCheck(true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets notified for volume state changed.
|
||||
* @param event
|
||||
*/
|
||||
window.onVolumeChange = function (event) {
|
||||
self.updateCheck();
|
||||
|
||||
// let's check, if player is not muted lets mute locally
|
||||
if(event.data.volume > 0 && !event.data.muted
|
||||
&& !APP.conference.isLocalAudioMuted()){
|
||||
self.emitter.emit(UIEvents.AUDIO_MUTED, true);
|
||||
self.notifyUserComfortableMicMute(true);
|
||||
}
|
||||
};
|
||||
|
||||
window.onPlayerReady = function(event) {
|
||||
let player = event.target;
|
||||
// do not relay on autoplay as it is not sending all of the events
|
||||
|
@ -144,12 +189,16 @@ export default class SharedVideoManager {
|
|||
self.sharedVideo = new SharedVideoContainer(
|
||||
{url, iframe, player});
|
||||
|
||||
//prevents pausing participants not sharing the video
|
||||
// to pause the video
|
||||
if (!APP.conference.isLocalId(self.from)) {
|
||||
$("#sharedVideo").css("pointer-events","none");
|
||||
}
|
||||
|
||||
VideoLayout.addLargeVideoContainer(
|
||||
SHARED_VIDEO_CONTAINER_TYPE, self.sharedVideo);
|
||||
VideoLayout.handleVideoThumbClicked(self.url);
|
||||
|
||||
self.isSharedVideoShown = true;
|
||||
|
||||
// If we are sending the command and we are starting the player
|
||||
// we need to continuously send the player current time position
|
||||
if(APP.conference.isLocalId(self.from)) {
|
||||
|
@ -157,17 +206,12 @@ export default class SharedVideoManager {
|
|||
self.updateCheck.bind(self),
|
||||
updateInterval);
|
||||
}
|
||||
|
||||
if(self.player)
|
||||
self.processAttributes(
|
||||
self.player, attributes, self.playerPaused);
|
||||
else {
|
||||
self.initialAttributes = attributes;
|
||||
}
|
||||
};
|
||||
|
||||
window.onPlayerError = function(event) {
|
||||
console.error("Error in the player:" + event.data);
|
||||
console.error("Error in the player:", event.data);
|
||||
// store the error player, so we can remove it
|
||||
self.errorInPlayer = event.target;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -184,13 +228,15 @@ export default class SharedVideoManager {
|
|||
|
||||
if (attributes.state == 'playing') {
|
||||
|
||||
this.processTime(player, attributes);
|
||||
this.processTime(player, attributes, playerPaused);
|
||||
|
||||
// lets check the volume
|
||||
if (attributes.volume !== undefined &&
|
||||
player.getVolume() != attributes.volume) {
|
||||
player.getVolume() != attributes.volume
|
||||
&& APP.conference.isLocalAudioMuted()) {
|
||||
player.setVolume(attributes.volume);
|
||||
console.info("Player change of volume:" + attributes.volume);
|
||||
this.notifyUserComfortableVideoMute(false);
|
||||
}
|
||||
|
||||
if(playerPaused)
|
||||
|
@ -200,7 +246,9 @@ export default class SharedVideoManager {
|
|||
// if its not paused, pause it
|
||||
player.pauseVideo();
|
||||
|
||||
this.processTime(player, attributes);
|
||||
this.processTime(player, attributes, true);
|
||||
} else if (attributes.state == 'stop') {
|
||||
this.stopSharedVideo(this.from);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,9 +256,16 @@ export default class SharedVideoManager {
|
|||
* Check for time in attributes and if needed seek in current player
|
||||
* @param player the player to operate over
|
||||
* @param attributes the attributes with the player state we want
|
||||
* @param forceSeek whether seek should be forced
|
||||
*/
|
||||
processTime (player, attributes)
|
||||
processTime (player, attributes, forceSeek)
|
||||
{
|
||||
if(forceSeek) {
|
||||
console.info("Player seekTo:", attributes.time);
|
||||
player.seekTo(attributes.time);
|
||||
return;
|
||||
}
|
||||
|
||||
// check received time and current time
|
||||
let currentPosition = player.getCurrentTime();
|
||||
let diff = Math.abs(attributes.time - currentPosition);
|
||||
|
@ -230,8 +285,10 @@ export default class SharedVideoManager {
|
|||
updateCheck(sendPauseEvent)
|
||||
{
|
||||
// ignore update checks if we are not the owner of the video
|
||||
// or there is still no player defined
|
||||
if(!APP.conference.isLocalId(this.from) || !this.player)
|
||||
// or there is still no player defined or we are stopped
|
||||
// (in a process of stopping)
|
||||
if(!APP.conference.isLocalId(this.from) || !this.player
|
||||
|| !this.isSharedVideoShown)
|
||||
return;
|
||||
|
||||
let state = this.player.getPlayerState();
|
||||
|
@ -281,18 +338,31 @@ export default class SharedVideoManager {
|
|||
* left and we want to remove video if the user sharing it left).
|
||||
* @param id the id of the sender of the command
|
||||
*/
|
||||
stopSharedVideo (id) {
|
||||
stopSharedVideo (id, attributes) {
|
||||
if (!this.isSharedVideoShown)
|
||||
return;
|
||||
|
||||
if(this.from !== id)
|
||||
return;
|
||||
|
||||
if(!this.player){
|
||||
// if there is no error in the player till now,
|
||||
// store the initial attributes
|
||||
if (!this.errorInPlayer) {
|
||||
this.initialAttributes = attributes;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = null;
|
||||
}
|
||||
|
||||
this.emitter.removeListener(UIEvents.AUDIO_MUTED,
|
||||
this.localAudioMutedListener);
|
||||
this.localAudioMutedListener = null;
|
||||
|
||||
VideoLayout.removeParticipantContainer(this.url);
|
||||
|
||||
VideoLayout.showLargeVideoContainer(SHARED_VIDEO_CONTAINER_TYPE, false)
|
||||
|
@ -300,12 +370,68 @@ export default class SharedVideoManager {
|
|||
VideoLayout.removeLargeVideoContainer(
|
||||
SHARED_VIDEO_CONTAINER_TYPE);
|
||||
|
||||
this.player.destroy();
|
||||
this.player = null;
|
||||
if(this.player) {
|
||||
this.player.destroy();
|
||||
this.player = null;
|
||||
} // if there is an error in player, remove that instance
|
||||
else if (this.errorInPlayer) {
|
||||
this.errorInPlayer.destroy();
|
||||
this.errorInPlayer = null;
|
||||
}
|
||||
// revert to original behavior (prevents pausing
|
||||
// for participants not sharing the video to pause it)
|
||||
$("#sharedVideo").css("pointer-events","auto");
|
||||
});
|
||||
|
||||
this.url = null;
|
||||
this.isSharedVideoShown = false;
|
||||
this.initialAttributes = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives events for local audio mute/unmute by local user.
|
||||
* @param muted boolena whether it is muted or not.
|
||||
*/
|
||||
localAudioMuted (muted) {
|
||||
if(!this.player)
|
||||
return;
|
||||
|
||||
if(muted)
|
||||
return;
|
||||
|
||||
// if we are un-muting and player is not muted, lets muted
|
||||
// to not pollute the conference
|
||||
if(this.player.getVolume() > 0 || !this.player.isMuted()){
|
||||
this.player.setVolume(0);
|
||||
this.notifyUserComfortableVideoMute(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies user for muting its audio due to video is unmuted.
|
||||
* @param show boolean, show or hide the notification
|
||||
*/
|
||||
notifyUserComfortableMicMute (show) {
|
||||
if(show) {
|
||||
this.notifyUserComfortableVideoMute(false);
|
||||
console.log("Your audio was muted to enjoy the video");
|
||||
}
|
||||
else
|
||||
console.log("Hide notification local audio muted");
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies user for muting the video due to audio is unmuted.
|
||||
* @param show boolean, show or hide the notification
|
||||
*/
|
||||
notifyUserComfortableVideoMute (show) {
|
||||
if(show) {
|
||||
this.notifyUserComfortableMicMute(false);
|
||||
console.log(
|
||||
"Your shared video was muted in order to speak freely!");
|
||||
}
|
||||
else
|
||||
console.log("Hide notification share video muted");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -333,6 +459,7 @@ class SharedVideoContainer extends LargeContainer {
|
|||
self.bodyBackground = document.body.style.background;
|
||||
document.body.style.background = 'black';
|
||||
this.$iframe.css({opacity: 1});
|
||||
ToolbarToggler.dockToolbar(true);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
@ -340,6 +467,7 @@ class SharedVideoContainer extends LargeContainer {
|
|||
|
||||
hide () {
|
||||
let self = this;
|
||||
ToolbarToggler.dockToolbar(false);
|
||||
return new Promise(resolve => {
|
||||
this.$iframe.fadeOut(300, () => {
|
||||
document.body.style.background = self.bodyBackground;
|
||||
|
|
|
@ -6,7 +6,6 @@ import AnalyticsAdapter from '../../statistics/AnalyticsAdapter';
|
|||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
|
||||
let roomUrl = null;
|
||||
let recordingToaster = null;
|
||||
let emitter = null;
|
||||
|
||||
|
||||
|
@ -43,51 +42,6 @@ function openLinkDialog () {
|
|||
);
|
||||
}
|
||||
|
||||
// Sets the state of the recording button
|
||||
function setRecordingButtonState (recordingState) {
|
||||
let selector = $('#toolbar_button_record');
|
||||
|
||||
if (recordingState === 'on') {
|
||||
selector.removeClass("icon-recEnable");
|
||||
selector.addClass("icon-recEnable active");
|
||||
|
||||
$("#largeVideo").toggleClass("videoMessageFilter", true);
|
||||
let 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);
|
||||
let recordPendingKey = "recording.pending";
|
||||
$('#videoConnectionMessage').attr("data-i18n", recordPendingKey);
|
||||
$('#videoConnectionMessage').text(APP.translation.translateString(recordPendingKey));
|
||||
$('#videoConnectionMessage').css({display: "block"});
|
||||
}
|
||||
}
|
||||
|
||||
const buttonHandlers = {
|
||||
"toolbar_button_mute": function () {
|
||||
if (APP.conference.audioMuted) {
|
||||
|
@ -107,10 +61,6 @@ const buttonHandlers = {
|
|||
emitter.emit(UIEvents.VIDEO_MUTED, true);
|
||||
}
|
||||
},
|
||||
"toolbar_button_record": function () {
|
||||
AnalyticsAdapter.sendEvent('toolbar.recording.toggled');
|
||||
emitter.emit(UIEvents.RECORDING_TOGGLE);
|
||||
},
|
||||
"toolbar_button_security": function () {
|
||||
emitter.emit(UIEvents.ROOM_LOCK_CLICKED);
|
||||
},
|
||||
|
@ -250,7 +200,8 @@ const Toolbar = {
|
|||
*/
|
||||
unlockLockButton () {
|
||||
if ($("#toolbar_button_security").hasClass("icon-security-locked"))
|
||||
UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked");
|
||||
UIUtil.buttonClick("#toolbar_button_security",
|
||||
"icon-security icon-security-locked");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -258,7 +209,8 @@ const Toolbar = {
|
|||
*/
|
||||
lockLockButton () {
|
||||
if ($("#toolbar_button_security").hasClass("icon-security"))
|
||||
UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked");
|
||||
UIUtil.buttonClick("#toolbar_button_security",
|
||||
"icon-security icon-security-locked");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -279,15 +231,6 @@ const Toolbar = {
|
|||
}
|
||||
},
|
||||
|
||||
// Shows or hides the 'recording' button.
|
||||
showRecordingButton (show) {
|
||||
if (UIUtil.isButtonEnabled('recording') && show) {
|
||||
$('#toolbar_button_record').css({display: "inline-block"});
|
||||
} else {
|
||||
$('#toolbar_button_record').css({display: "none"});
|
||||
}
|
||||
},
|
||||
|
||||
// Shows or hides the 'shared video' button.
|
||||
showSharedVideoButton () {
|
||||
if (UIUtil.isButtonEnabled('sharedvideo')
|
||||
|
@ -298,14 +241,6 @@ const Toolbar = {
|
|||
}
|
||||
},
|
||||
|
||||
// checks whether recording is enabled and whether we have params
|
||||
// to start automatically recording
|
||||
checkAutoRecord () {
|
||||
if (UIUtil.isButtonEnabled('recording') && config.autoRecord) {
|
||||
emitter.emit(UIEvents.RECORDING_TOGGLE, UIUtil.escapeHtml(config.autoRecordToken));
|
||||
}
|
||||
},
|
||||
|
||||
// checks whether desktop sharing is enabled and whether
|
||||
// we have params to start automatically sharing
|
||||
checkAutoEnableDesktopSharing () {
|
||||
|
@ -383,10 +318,6 @@ const Toolbar = {
|
|||
}
|
||||
},
|
||||
|
||||
updateRecordingState (state) {
|
||||
setRecordingButtonState(state);
|
||||
},
|
||||
|
||||
/**
|
||||
* Marks video icon as muted or not.
|
||||
* @param {boolean} muted if icon should look like muted or not
|
||||
|
|
|
@ -59,7 +59,8 @@ const ToolbarToggler = {
|
|||
* Shows the main toolbar.
|
||||
*/
|
||||
showToolbar () {
|
||||
if (interfaceConfig.filmStripOnly) {
|
||||
// if we are a recorder we do not want to show the toolbar
|
||||
if (interfaceConfig.filmStripOnly || config.iAmRecorder) {
|
||||
return;
|
||||
}
|
||||
let header = $("#header");
|
||||
|
|
|
@ -123,11 +123,7 @@
|
|||
},
|
||||
|
||||
isButtonEnabled: function (name) {
|
||||
var isEnabled = interfaceConfig.TOOLBAR_BUTTONS.indexOf(name) !== -1;
|
||||
if (name === 'recording') {
|
||||
return isEnabled && config.enableRecording;
|
||||
}
|
||||
return isEnabled;
|
||||
return interfaceConfig.TOOLBAR_BUTTONS.indexOf(name) !== -1;
|
||||
},
|
||||
|
||||
hideDisabledButtons: function (mappings) {
|
||||
|
|
|
@ -141,8 +141,10 @@ SmallVideo.createStreamElement = function (stream) {
|
|||
element.id = SmallVideo.getStreamElementID(stream);
|
||||
|
||||
element.onplay = function () {
|
||||
var now = APP.performanceTimes["video.render"]
|
||||
= window.performance.now();
|
||||
console.log("(TIME) Render " + (isVideo ? 'video' : 'audio') + ":\t",
|
||||
window.performance.now());
|
||||
now);
|
||||
};
|
||||
|
||||
element.oncontextmenu = function () { return false; };
|
||||
|
@ -315,11 +317,21 @@ SmallVideo.prototype.selectVideoElement = function () {
|
|||
return $(RTCUIHelper.findVideoElement($('#' + this.videoSpanId)[0]));
|
||||
};
|
||||
|
||||
/**
|
||||
* Enables / disables the css responsible for focusing/pinning a video
|
||||
* thumbnail.
|
||||
*
|
||||
* @param isFocused indicates if the thumbnail should be focused/pinned or not
|
||||
*/
|
||||
SmallVideo.prototype.focus = function(isFocused) {
|
||||
if(!isFocused) {
|
||||
this.container.classList.remove("videoContainerFocused");
|
||||
} else {
|
||||
this.container.classList.add("videoContainerFocused");
|
||||
var focusedCssClass = "videoContainerFocused";
|
||||
var isFocusClassEnabled = $(this.container).hasClass(focusedCssClass);
|
||||
|
||||
if (!isFocused && isFocusClassEnabled) {
|
||||
$(this.container).removeClass(focusedCssClass);
|
||||
}
|
||||
else if (isFocused && !isFocusClassEnabled) {
|
||||
$(this.container).addClass(focusedCssClass);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,18 +1,6 @@
|
|||
/* global $, $iq, config, interfaceConfig */
|
||||
/* global $, $iq, config, interfaceConfig, getConfigParamsFromUrl */
|
||||
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();
|
||||
|
||||
|
@ -62,4 +50,4 @@ var URLProcessor = {
|
|||
}
|
||||
};
|
||||
|
||||
module.exports = URLProcessor;
|
||||
module.exports = URLProcessor;
|
||||
|
|
|
@ -24,7 +24,7 @@ var defaultOptions = {
|
|||
fallbackOnEmpty: true,
|
||||
useDataAttrOptions: true,
|
||||
app: interfaceConfig.APP_NAME,
|
||||
getAsync: false,
|
||||
getAsync: true,
|
||||
defaultValueFromContent: false,
|
||||
customLoad: function(lng, ns, options, done) {
|
||||
var resPath = "lang/__ns__-__lng__.json";
|
||||
|
|
|
@ -62,7 +62,7 @@ export default {
|
|||
CONTACT_CLICKED: "UI.contact_clicked",
|
||||
HANGUP: "UI.hangup",
|
||||
LOGOUT: "UI.logout",
|
||||
RECORDING_TOGGLE: "UI.recording_toggle",
|
||||
RECORDING_TOGGLED: "UI.recording_toggled",
|
||||
SIP_DIAL: "UI.sip_dial",
|
||||
SUBJECT_CHANGED: "UI.subject_changed",
|
||||
VIDEO_DEVICE_CHANGED: "UI.video_device_changed",
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/* global config */
|
||||
|
||||
/**
|
||||
* Defines some utility methods that are used before the other JS files are
|
||||
* loaded.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Builds and returns the room name.
|
||||
*/
|
||||
function getRoomName () {
|
||||
var path = window.location.pathname;
|
||||
var roomName;
|
||||
|
||||
// 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
|
||||
roomName = config.getroomnode(path);
|
||||
} else {
|
||||
/* fall back to default strategy
|
||||
* this is making assumptions about how the URL->room mapping happens.
|
||||
* It currently assumes deployment at root, with a rewrite like the
|
||||
* following one (for nginx):
|
||||
location ~ ^/([a-zA-Z0-9]+)$ {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
*/
|
||||
if (path.length > 1) {
|
||||
roomName = path.substr(1).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
return roomName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the hash parameters from the URL and returns them as a JS object.
|
||||
*/
|
||||
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;
|
||||
}
|
Loading…
Reference in New Issue