mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-06 06:42:28 +00:00
Compare commits
166 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9538845bc | ||
|
|
7f1579d96d | ||
|
|
3674694d12 | ||
|
|
3e518e8040 | ||
|
|
33c92a31bf | ||
|
|
5e2e7902ce | ||
|
|
f9585430bb | ||
|
|
19de32e206 | ||
|
|
a82bc1df64 | ||
|
|
c34e841710 | ||
|
|
a5c78be52c | ||
|
|
edbbaef26f | ||
|
|
bdd133309d | ||
|
|
71da05dc96 | ||
|
|
f1cbafb097 | ||
|
|
3db557e2c9 | ||
|
|
a8b3177e20 | ||
|
|
8e6f043586 | ||
|
|
5163472392 | ||
|
|
965c811025 | ||
|
|
adc2260b63 | ||
|
|
4ef84054dc | ||
|
|
7db1c9b8eb | ||
|
|
ae06a6ce41 | ||
|
|
0316450ee2 | ||
|
|
281305147b | ||
|
|
ef41e32af5 | ||
|
|
4ef8172f8d | ||
|
|
5106f9f958 | ||
|
|
2c61d8d94b | ||
|
|
bc8c8c1bb9 | ||
|
|
cbc08eb96d | ||
|
|
f4de65a647 | ||
|
|
0f42f18100 | ||
|
|
a9d9dc6658 | ||
|
|
07cd6a8b88 | ||
|
|
ab62690b97 | ||
|
|
e7a3ee477d | ||
|
|
1ec06f4bf0 | ||
|
|
849f93375c | ||
|
|
35ba6cef4e | ||
|
|
b0d63dae16 | ||
|
|
14d394aed8 | ||
|
|
50fea44ce2 | ||
|
|
98004c2328 | ||
|
|
eb7dda85a1 | ||
|
|
2f994b1227 | ||
|
|
a9bdde193d | ||
|
|
2ffef3bdda | ||
|
|
77b789e26a | ||
|
|
031f2dfeb8 | ||
|
|
cb0eef9edd | ||
|
|
8be85de6ef | ||
|
|
9cf7f2b83d | ||
|
|
95667ef98e | ||
|
|
b211ce02a8 | ||
|
|
739298c782 | ||
|
|
a1da6bff1a | ||
|
|
18a81d7ca0 | ||
|
|
6f15903019 | ||
|
|
a26f7a1292 | ||
|
|
ae8c5287e4 | ||
|
|
fd10362bef | ||
|
|
3af6cc53d1 | ||
|
|
54bb5f1879 | ||
|
|
13e3375e8a | ||
|
|
37157dc9e2 | ||
|
|
8fe3dce649 | ||
|
|
4ec4c45a90 | ||
|
|
19f46ed4f0 | ||
|
|
0e9509ae9b | ||
|
|
618dedc58e | ||
|
|
623b7a8d6f | ||
|
|
7c76f124bf | ||
|
|
f5973e0eee | ||
|
|
32634356a6 | ||
|
|
6d0a07a4cd | ||
|
|
e0b829f92f | ||
|
|
684572bd05 | ||
|
|
334eb5d423 | ||
|
|
bcbdaaa6ea | ||
|
|
986939e501 | ||
|
|
d416fd8c0f | ||
|
|
78119df2db | ||
|
|
e2e04e3f16 | ||
|
|
d37468975c | ||
|
|
589f77ef0e | ||
|
|
1e2d88cd5d | ||
|
|
da4425b5c0 | ||
|
|
0d7cb63978 | ||
|
|
2248560699 | ||
|
|
3daae94bca | ||
|
|
7299b76faf | ||
|
|
673dc6e873 | ||
|
|
9c544c0a4b | ||
|
|
8502ecc6d2 | ||
|
|
74b5638d99 | ||
|
|
e8de8735e2 | ||
|
|
dbcd19418c | ||
|
|
a10f040df6 | ||
|
|
88a7ff891c | ||
|
|
2b4db6c3bf | ||
|
|
4ddc426966 | ||
|
|
309ce43e05 | ||
|
|
61470c0d24 | ||
|
|
2301732e2d | ||
|
|
24ee8eb16a | ||
|
|
57065bb274 | ||
|
|
4ab4aa04da | ||
|
|
0ed39dad63 | ||
|
|
08531ee675 | ||
|
|
e7140ffec7 | ||
|
|
c58c4b7938 | ||
|
|
4e276471e5 | ||
|
|
c5eac63da1 | ||
|
|
866c6d0cf9 | ||
|
|
165294bfb1 | ||
|
|
2d5f0479bd | ||
|
|
e8068cf5ac | ||
|
|
d0171cf386 | ||
|
|
3ae99ea0b9 | ||
|
|
4e9450f200 | ||
|
|
dc2c49f4a9 | ||
|
|
c461e8b63c | ||
|
|
f47bc1163b | ||
|
|
851be2d76e | ||
|
|
63034e6cba | ||
|
|
84b9c5f5fd | ||
|
|
43c8fc6847 | ||
|
|
bc60bd23b2 | ||
|
|
e29120a9c1 | ||
|
|
d383230532 | ||
|
|
9a46896600 | ||
|
|
fba086134d | ||
|
|
2973364c02 | ||
|
|
542bb7caed | ||
|
|
fb47b6ae21 | ||
|
|
aeb301c8d5 | ||
|
|
704e14f008 | ||
|
|
d1050d6b02 | ||
|
|
afc96808e8 | ||
|
|
dc2bae4ae1 | ||
|
|
1d7da21e48 | ||
|
|
affd965d5d | ||
|
|
989161159d | ||
|
|
59a74153dc | ||
|
|
6690c269ef | ||
|
|
b7fd10b905 | ||
|
|
08e1cf1b7e | ||
|
|
54d891afa7 | ||
|
|
ae41782cd4 | ||
|
|
8591fe00b6 | ||
|
|
92f58cb3c1 | ||
|
|
4ad98ca505 | ||
|
|
b9374bde6b | ||
|
|
1ff29384b3 | ||
|
|
4fc714ff10 | ||
|
|
51f0c8a388 | ||
|
|
d01a65f73d | ||
|
|
65239f9ffe | ||
|
|
e5cefcce70 | ||
|
|
8002b5ec6a | ||
|
|
a575f5cc77 | ||
|
|
ab3a80e076 | ||
|
|
dda3798ba9 | ||
|
|
5f387737a1 |
13
.flowconfig
13
.flowconfig
@@ -19,6 +19,7 @@
|
||||
.*/node_modules/babel-core/.*
|
||||
.*/node_modules/bower/.*
|
||||
.*/node_modules/jsonlint/.*
|
||||
.*/node_modules/styled-components/.*
|
||||
|
||||
[include]
|
||||
|
||||
@@ -48,5 +49,17 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
; We (i.e. the jitsi-meet project) are using the haste module system on Web as
|
||||
; well, not only on React Native. Unfortunately, Flow does not support .web.js
|
||||
; by default. Override Flow's defaults to include .web.js as well. Technically,
|
||||
; we have .native.js as well so the choice of .web.js may lead to errors.
|
||||
; Practically though, it is a potential future problem that we do not have at
|
||||
; the time of this writing.
|
||||
module.file_ext=.web.js
|
||||
; Flow's defaults:
|
||||
module.file_ext=.js
|
||||
module.file_ext=.jsx
|
||||
module.file_ext=.json
|
||||
|
||||
[version]
|
||||
^0.38.0
|
||||
|
||||
33
CONTRIBUTING.md
Normal file
33
CONTRIBUTING.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# How to contribute
|
||||
We would love to have your help. Before you start working however, please read
|
||||
and follow this short guide.
|
||||
|
||||
# Reporting Issues
|
||||
Before you open an issue on GitHub, please discuss it on one of our
|
||||
[mailing lists](https://jitsi.org/Development/MailingLists) and wait for
|
||||
confirmation from one of the committers. Once you have that confirmation,
|
||||
please proceed to reporting the issue on GitHub, while providing as much
|
||||
information as possible. Mention the version of Jitsi Meet, Jicofo and JVB
|
||||
you are using, and explain (as detailed as you can) how the problem can
|
||||
be reproduced.
|
||||
|
||||
# Code contributions
|
||||
Found a bug and know how to fix it? Great! Please read on.
|
||||
|
||||
## Contributor License Agreement
|
||||
While the Jitsi projects are released under the
|
||||
[Apache License 2.0](https://github.com/jitsi/jitsi-meet/blob/master/LICENSE), the copyright
|
||||
holder and principal creator is [Atlassian](https://www.atlassian.com/). To
|
||||
ensure that we can continue making these projects available under an Open Source license,
|
||||
we need you to sign our Apache-based contributor
|
||||
license agreement as either a [corporation](https://jitsi.org/ccla) or an
|
||||
[individual](https://jitsi.org/icla). If you cannot accept the terms laid out
|
||||
in the agreement, unfortunately, we cannot accept your contribution.
|
||||
|
||||
## Creating Pull Requests
|
||||
- Make sure your code passes the linter rules beforehand. The linter is exeuted
|
||||
automatically when committing code.
|
||||
- Perform **one** logical change per pull request.
|
||||
- Maintain a clean list of commits, squash them if necessary.
|
||||
- Rebase your topic branch on top of the master branch before creating the pull
|
||||
request.
|
||||
@@ -2,3 +2,10 @@
|
||||
* Notifies interested parties that hangup procedure will start.
|
||||
*/
|
||||
export const BEFORE_HANGUP = "conference.before_hangup";
|
||||
|
||||
/**
|
||||
* Notifies interested parties that desktop sharing enable/disable state is
|
||||
* changed.
|
||||
*/
|
||||
export const DESKTOP_SHARING_ENABLED_CHANGED
|
||||
= "conference.desktop_sharing_enabled_changed";
|
||||
|
||||
12
Makefile
12
Makefile
@@ -10,13 +10,7 @@ STYLES_DESTINATION = css/all.css
|
||||
STYLES_MAIN = css/main.scss
|
||||
WEBPACK = ./node_modules/.bin/webpack
|
||||
|
||||
all: update-deps compile deploy clean
|
||||
|
||||
# FIXME: there is a problem with node-sass not correctly installed (compiled)
|
||||
# a quick fix to make sure it is installed on every update
|
||||
# the problem appears on linux and not on macosx
|
||||
update-deps:
|
||||
$(NPM) update && $(NPM) install node-sass
|
||||
all: compile deploy clean
|
||||
|
||||
compile:
|
||||
$(WEBPACK) -p
|
||||
@@ -33,6 +27,8 @@ deploy-appbundle:
|
||||
cp \
|
||||
$(BUILD_DIR)/app.bundle.min.js \
|
||||
$(BUILD_DIR)/app.bundle.min.map \
|
||||
$(BUILD_DIR)/do_external_connect.min.js \
|
||||
$(BUILD_DIR)/do_external_connect.min.map \
|
||||
$(BUILD_DIR)/external_api.min.js \
|
||||
$(BUILD_DIR)/external_api.min.map \
|
||||
$(OUTPUT_DIR)/analytics.js \
|
||||
@@ -55,7 +51,7 @@ deploy-local:
|
||||
|
||||
source-package:
|
||||
mkdir -p source_package/jitsi-meet/css && \
|
||||
cp -r *.js *.html connection_optimization favicon.ico fonts images libs sounds LICENSE lang source_package/jitsi-meet && \
|
||||
cp -r *.js *.html connection_optimization favicon.ico fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \
|
||||
cp css/all.css source_package/jitsi-meet/css && \
|
||||
(cd source_package ; tar cjf ../jitsi-meet.tar.bz2 jitsi-meet) && \
|
||||
rm -rf source_package
|
||||
|
||||
@@ -19,14 +19,12 @@ You can download Debian/Ubuntu binaries:
|
||||
* [testing](https://download.jitsi.org/testing/) ([instructions](https://jitsi.org/Main/InstallJitsiMeetDebianTestingRepository))
|
||||
* [nightly](https://download.jitsi.org/unstable/) ([instructions](https://jitsi.org/Main/InstallJitsiMeetDebianNightlyRepository))
|
||||
|
||||
You can get our mobile versoins from here:
|
||||
You can get our mobile versions from here:
|
||||
* [Android](https://play.google.com/store/apps/details?id=org.jitsi.meet)
|
||||
* [iOS](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
|
||||
|
||||
## Building the sources
|
||||
|
||||
Jitsi Meet uses [Browserify](http://browserify.org). If you want to make changes in the code you need to [install Browserify](http://browserify.org/#install). Browserify requires [nodejs](http://nodejs.org).
|
||||
|
||||
On Debian/Ubuntu systems, the required packages can be installed with:
|
||||
```
|
||||
sudo apt-get install npm nodejs-legacy
|
||||
@@ -86,6 +84,11 @@ npm unlink lib-jitsi-meet
|
||||
npm install
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
If you are looking to contribute to Jitsi Meet, first of all, thank you! Please
|
||||
see our [guidelines for contributing](CONTRIBUTING.md).
|
||||
|
||||
## Embedding in external applications
|
||||
|
||||
Jitsi Meet provides a very flexible way of embedding it in external applications by using the [Jitsi Meet API](doc/api.md).
|
||||
|
||||
@@ -91,7 +91,7 @@ android {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
versionCode Integer.parseInt("${version}")
|
||||
versionName "1.3.${version}"
|
||||
versionName "1.4.${version}"
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'x86'
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus"/>
|
||||
|
||||
@@ -32,7 +32,8 @@ public class MainApplication extends Application implements ReactApplication {
|
||||
new com.ocetnik.timer.BackgroundTimerPackage(),
|
||||
new com.oney.WebRTCModule.WebRTCModulePackage(),
|
||||
new com.rnimmersive.RNImmersivePackage(),
|
||||
new org.jitsi.meet.audiomode.AudioModePackage()
|
||||
new org.jitsi.meet.audiomode.AudioModePackage(),
|
||||
new org.jitsi.meet.proximity.ProximityPackage()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package org.jitsi.meet.proximity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
/**
|
||||
* Module implementing a simple API to enable a proximity sensor-controlled
|
||||
* wake lock. When the lock is held, if the proximity sensor detects a nearby
|
||||
* object it will dim the screen and disable touch controls. The functionality
|
||||
* is used with the conference audio-only mode.
|
||||
*/
|
||||
public class ProximityModule extends ReactContextBaseJavaModule {
|
||||
/**
|
||||
* React Native module name.
|
||||
*/
|
||||
private static final String MODULE_NAME = "Proximity";
|
||||
|
||||
/**
|
||||
* This type of wake lock (the one activated by the proximity sensor) has
|
||||
* been available for a while, but the constant was only exported in API
|
||||
* level 21 (Android Marshmallow) so make no assumptions and use its value
|
||||
* directly.
|
||||
*
|
||||
* TODO: Remove when we bump the API level to 21.
|
||||
*/
|
||||
private static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32;
|
||||
|
||||
/**
|
||||
* {@link WakeLock} instance.
|
||||
*/
|
||||
private final WakeLock wakeLock;
|
||||
|
||||
/**
|
||||
* Initializes a new module instance. There shall be a single instance of
|
||||
* this module throughout the lifetime of the application.
|
||||
*
|
||||
* @param reactContext The {@link ReactApplicationContext} where this module
|
||||
* is created.
|
||||
*/
|
||||
public ProximityModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
|
||||
WakeLock wakeLock;
|
||||
PowerManager powerManager
|
||||
= (PowerManager)
|
||||
reactContext.getSystemService(Context.POWER_SERVICE);
|
||||
|
||||
try {
|
||||
wakeLock
|
||||
= powerManager.newWakeLock(
|
||||
PROXIMITY_SCREEN_OFF_WAKE_LOCK,
|
||||
MODULE_NAME);
|
||||
} catch (Throwable ignored) {
|
||||
wakeLock = null;
|
||||
}
|
||||
|
||||
this.wakeLock = wakeLock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of this module to be used in the React Native bridge.
|
||||
*
|
||||
* @return The name of this module to be used in the React Native bridge.
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return MODULE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires / releases the proximity sensor wake lock.
|
||||
*
|
||||
* @param enabled {@code true} to enable the proximity sensor; otherwise,
|
||||
* {@code false}.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setEnabled(final boolean enabled) {
|
||||
if (wakeLock == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (enabled) {
|
||||
if (!wakeLock.isHeld()) {
|
||||
wakeLock.acquire();
|
||||
}
|
||||
} else if (wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.jitsi.meet.proximity;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implements {@link ReactPackage} for {@link ProximityModule}.
|
||||
*/
|
||||
public class ProximityPackage implements ReactPackage {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return List of native modules to be exposed by React Native.
|
||||
*/
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(
|
||||
ReactApplicationContext reactContext) {
|
||||
List<NativeModule> modules = new ArrayList<>();
|
||||
|
||||
modules.add(new ProximityModule(reactContext));
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(
|
||||
ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
1
app.js
1
app.js
@@ -1,6 +1,5 @@
|
||||
/* application specific logic */
|
||||
|
||||
import "babel-polyfill";
|
||||
import "jquery";
|
||||
import "jquery-contextmenu";
|
||||
import "jquery-ui";
|
||||
|
||||
341
conference.js
341
conference.js
@@ -20,6 +20,8 @@ import analytics from './modules/analytics/analytics';
|
||||
|
||||
import EventEmitter from "events";
|
||||
|
||||
import { showDesktopSharingButton } from './react/features/toolbox';
|
||||
|
||||
import {
|
||||
AVATAR_ID_COMMAND,
|
||||
AVATAR_URL_COMMAND,
|
||||
@@ -28,17 +30,22 @@ import {
|
||||
conferenceLeft,
|
||||
EMAIL_COMMAND
|
||||
} from './react/features/base/conference';
|
||||
import {
|
||||
updateDeviceList
|
||||
} from './react/features/base/devices';
|
||||
import {
|
||||
isFatalJitsiConnectionError
|
||||
} from './react/features/base/lib-jitsi-meet';
|
||||
import {
|
||||
changeParticipantAvatarID,
|
||||
changeParticipantAvatarURL,
|
||||
changeParticipantEmail,
|
||||
localParticipantRoleChanged,
|
||||
participantJoined,
|
||||
participantLeft,
|
||||
participantRoleChanged
|
||||
participantRoleChanged,
|
||||
participantUpdated
|
||||
} from './react/features/base/participants';
|
||||
import {
|
||||
showDesktopPicker
|
||||
} from './react/features/desktop-picker';
|
||||
import {
|
||||
mediaPermissionPromptVisibilityChanged,
|
||||
suspendDetected
|
||||
@@ -57,7 +64,10 @@ const ConnectionQualityEvents = JitsiMeetJS.events.connectionQuality;
|
||||
|
||||
const eventEmitter = new EventEmitter();
|
||||
|
||||
let room, connection, localAudio, localVideo;
|
||||
let room;
|
||||
let connection;
|
||||
let localAudio, localVideo;
|
||||
let initialAudioMutedState = false, initialVideoMutedState = false;
|
||||
|
||||
/**
|
||||
* Indicates whether extension external installation is in progress or not.
|
||||
@@ -66,6 +76,16 @@ let DSExternalInstallationInProgress = false;
|
||||
|
||||
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/VideoContainer";
|
||||
|
||||
/*
|
||||
* Logic to open a desktop picker put on the window global for
|
||||
* lib-jitsi-meet to detect and invoke
|
||||
*/
|
||||
window.JitsiMeetScreenObtainer = {
|
||||
openDesktopPicker(onSourceChoose) {
|
||||
APP.store.dispatch(showDesktopPicker(onSourceChoose));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Known custom conference commands.
|
||||
*/
|
||||
@@ -160,7 +180,11 @@ function createInitialLocalTracksAndConnect(roomName) {
|
||||
* @param command the command
|
||||
* @param {string} value new value
|
||||
*/
|
||||
function sendData (command, value) {
|
||||
function sendData(command, value) {
|
||||
if(!room) {
|
||||
return;
|
||||
}
|
||||
|
||||
room.removeCommand(command);
|
||||
room.sendCommand(command, {value: value});
|
||||
}
|
||||
@@ -193,7 +217,7 @@ function _setupLocalParticipantProperties() {
|
||||
* @param {string} id user id
|
||||
* @returns {string?} user nickname or undefined if user is unknown.
|
||||
*/
|
||||
function getDisplayName (id) {
|
||||
function getDisplayName(id) {
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
return APP.settings.getDisplayName();
|
||||
}
|
||||
@@ -210,7 +234,7 @@ function getDisplayName (id) {
|
||||
* @param {boolean} userInteraction - indicates if this local audio mute was a
|
||||
* result of user interaction
|
||||
*/
|
||||
function muteLocalAudio (muted) {
|
||||
function muteLocalAudio(muted) {
|
||||
muteLocalMedia(localAudio, muted, 'Audio');
|
||||
}
|
||||
|
||||
@@ -230,7 +254,7 @@ function muteLocalMedia(localMedia, muted, localMediaTypeString) {
|
||||
* Mute or unmute local video stream if it exists.
|
||||
* @param {boolean} muted if video stream should be muted or unmuted.
|
||||
*/
|
||||
function muteLocalVideo (muted) {
|
||||
function muteLocalVideo(muted) {
|
||||
muteLocalMedia(localVideo, muted, 'Video');
|
||||
}
|
||||
|
||||
@@ -252,8 +276,8 @@ function maybeRedirectToWelcomePage(options) {
|
||||
// save whether current user is guest or not, before navigating
|
||||
// to close page
|
||||
window.sessionStorage.setItem('guest', APP.tokenData.isGuest);
|
||||
assignWindowLocationPathname(
|
||||
options.feedbackSubmitted ? "close.html" : "close2.html");
|
||||
assignWindowLocationPathname('static/'
|
||||
+ (options.feedbackSubmitted ? "close.html" : "close2.html"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -315,7 +339,7 @@ function assignWindowLocationPathname(pathname) {
|
||||
* for gUM permission prompt
|
||||
* @returns {Promise<JitsiLocalTrack[]>}
|
||||
*/
|
||||
function createLocalTracks (options, checkForPermissionPrompt) {
|
||||
function createLocalTracks(options, checkForPermissionPrompt) {
|
||||
options || (options = {});
|
||||
|
||||
return JitsiMeetJS
|
||||
@@ -348,23 +372,6 @@ function createLocalTracks (options, checkForPermissionPrompt) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the display name for the local user
|
||||
* @param nickname {string} the new display name
|
||||
*/
|
||||
function changeLocalDisplayName(nickname = '') {
|
||||
const formattedNickname
|
||||
= nickname.trim().substr(0, MAX_DISPLAY_NAME_LENGTH);
|
||||
|
||||
if (formattedNickname === APP.settings.getDisplayName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.settings.setDisplayName(formattedNickname);
|
||||
room.setDisplayName(formattedNickname);
|
||||
APP.UI.changeDisplayName(APP.conference.getMyUserId(), formattedNickname);
|
||||
}
|
||||
|
||||
class ConferenceConnector {
|
||||
constructor(resolve, reject, invite) {
|
||||
this._resolve = resolve;
|
||||
@@ -387,10 +394,6 @@ class ConferenceConnector {
|
||||
logger.error('CONFERENCE FAILED:', err, ...params);
|
||||
APP.UI.hideRingOverLay();
|
||||
switch (err) {
|
||||
// room is locked by the password
|
||||
case ConferenceErrors.PASSWORD_REQUIRED:
|
||||
APP.UI.emitEvent(UIEvents.PASSWORD_REQUIRED);
|
||||
break;
|
||||
|
||||
case ConferenceErrors.CONNECTION_ERROR:
|
||||
{
|
||||
@@ -402,7 +405,7 @@ class ConferenceConnector {
|
||||
case ConferenceErrors.NOT_ALLOWED_ERROR:
|
||||
{
|
||||
// let's show some auth not allowed page
|
||||
assignWindowLocationPathname('authError.html');
|
||||
assignWindowLocationPathname('static/authError.html');
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -582,6 +585,12 @@ export default {
|
||||
analytics.init();
|
||||
return createInitialLocalTracksAndConnect(options.roomName);
|
||||
}).then(([tracks, con]) => {
|
||||
tracks.forEach(track => {
|
||||
if((track.isAudioTrack() && initialAudioMutedState)
|
||||
|| (track.isVideoTrack() && initialVideoMutedState)) {
|
||||
track.mute();
|
||||
}
|
||||
});
|
||||
logger.log('initialized with %s local tracks', tracks.length);
|
||||
con.addEventListener(
|
||||
ConnectionEvents.CONNECTION_FAILED,
|
||||
@@ -589,6 +598,12 @@ export default {
|
||||
APP.connection = connection = con;
|
||||
this.isDesktopSharingEnabled =
|
||||
JitsiMeetJS.isDesktopSharingEnabled();
|
||||
eventEmitter.emit(
|
||||
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
|
||||
this.isDesktopSharingEnabled);
|
||||
|
||||
APP.store.dispatch(showDesktopSharingButton());
|
||||
|
||||
APP.remoteControl.init();
|
||||
this._createRoom(tracks);
|
||||
|
||||
@@ -625,14 +640,14 @@ export default {
|
||||
* @param {string} id id to check
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isLocalId (id) {
|
||||
isLocalId(id) {
|
||||
return this.getMyUserId() === id;
|
||||
},
|
||||
/**
|
||||
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
|
||||
* @param mute true for mute and false for unmute.
|
||||
*/
|
||||
muteAudio (mute) {
|
||||
muteAudio(mute) {
|
||||
muteLocalAudio(mute);
|
||||
},
|
||||
/**
|
||||
@@ -643,36 +658,51 @@ export default {
|
||||
return this.audioMuted;
|
||||
},
|
||||
/**
|
||||
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
|
||||
* Simulates toolbar button click for audio mute. Used by shortcuts
|
||||
* and API.
|
||||
* @param {boolean} force - If the track is not created, the operation
|
||||
* will be executed after the track is created. Otherwise the operation
|
||||
* will be ignored.
|
||||
*/
|
||||
toggleAudioMuted () {
|
||||
toggleAudioMuted(force = false) {
|
||||
if(!localAudio && force) {
|
||||
initialAudioMutedState = !initialAudioMutedState;
|
||||
return;
|
||||
}
|
||||
this.muteAudio(!this.audioMuted);
|
||||
},
|
||||
/**
|
||||
* Simulates toolbar button click for video mute. Used by shortcuts and API.
|
||||
* @param mute true for mute and false for unmute.
|
||||
*/
|
||||
muteVideo (mute) {
|
||||
muteVideo(mute) {
|
||||
muteLocalVideo(mute);
|
||||
},
|
||||
/**
|
||||
* Simulates toolbar button click for video mute. Used by shortcuts and API.
|
||||
* @param {boolean} force - If the track is not created, the operation
|
||||
* will be executed after the track is created. Otherwise the operation
|
||||
* will be ignored.
|
||||
*/
|
||||
toggleVideoMuted () {
|
||||
toggleVideoMuted(force = false) {
|
||||
if(!localVideo && force) {
|
||||
initialVideoMutedState = !initialVideoMutedState;
|
||||
return;
|
||||
}
|
||||
this.muteVideo(!this.videoMuted);
|
||||
},
|
||||
/**
|
||||
* Retrieve list of conference participants (without local user).
|
||||
* @returns {JitsiParticipant[]}
|
||||
*/
|
||||
listMembers () {
|
||||
listMembers() {
|
||||
return room.getParticipants();
|
||||
},
|
||||
/**
|
||||
* Retrieve list of ids of conference participants (without local user).
|
||||
* @returns {string[]}
|
||||
*/
|
||||
listMembersIds () {
|
||||
listMembersIds() {
|
||||
return room.getParticipants().map(p => p.getId());
|
||||
},
|
||||
/**
|
||||
@@ -680,7 +710,7 @@ export default {
|
||||
* @id id to search for participant
|
||||
* @return {boolean} whether the participant is moderator
|
||||
*/
|
||||
isParticipantModerator (id) {
|
||||
isParticipantModerator(id) {
|
||||
let user = room.getParticipantById(id);
|
||||
return user && user.isModerator();
|
||||
},
|
||||
@@ -688,10 +718,10 @@ export default {
|
||||
* Check if SIP is supported.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
sipGatewayEnabled () {
|
||||
sipGatewayEnabled() {
|
||||
return room.isSIPCallingSupported();
|
||||
},
|
||||
get membersCount () {
|
||||
get membersCount() {
|
||||
return room.getParticipants().length + 1;
|
||||
},
|
||||
/**
|
||||
@@ -701,7 +731,7 @@ export default {
|
||||
* @returns true if the callstats integration is enabled, otherwise returns
|
||||
* false.
|
||||
*/
|
||||
isCallstatsEnabled () {
|
||||
isCallstatsEnabled() {
|
||||
return room && room.isCallstatsEnabled();
|
||||
},
|
||||
/**
|
||||
@@ -711,24 +741,69 @@ export default {
|
||||
* user feedback
|
||||
* @param detailedFeedback detailed feedback from the user. Not yet used
|
||||
*/
|
||||
sendFeedback (overallFeedback, detailedFeedback) {
|
||||
sendFeedback(overallFeedback, detailedFeedback) {
|
||||
return room.sendFeedback (overallFeedback, detailedFeedback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get speaker stats that track total dominant speaker time.
|
||||
*
|
||||
* @returns {object} A hash with keys being user ids and values being the
|
||||
* library's SpeakerStats model used for calculating time as dominant
|
||||
* speaker.
|
||||
*/
|
||||
getSpeakerStats() {
|
||||
return room.getSpeakerStats();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the connection times stored in the library.
|
||||
*/
|
||||
getConnectionTimes () {
|
||||
getConnectionTimes() {
|
||||
return this._room.getConnectionTimes();
|
||||
},
|
||||
// used by torture currently
|
||||
isJoined () {
|
||||
isJoined() {
|
||||
return this._room
|
||||
&& this._room.isJoined();
|
||||
},
|
||||
getConnectionState () {
|
||||
getConnectionState() {
|
||||
return this._room
|
||||
&& this._room.getConnectionState();
|
||||
},
|
||||
/**
|
||||
* Obtains current P2P ICE connection state.
|
||||
* @return {string|null} ICE connection state or <tt>null</tt> if there's no
|
||||
* P2P connection
|
||||
*/
|
||||
getP2PConnectionState() {
|
||||
return this._room
|
||||
&& this._room.getP2PConnectionState();
|
||||
},
|
||||
/**
|
||||
* Starts P2P (for tests only)
|
||||
* @private
|
||||
*/
|
||||
_startP2P() {
|
||||
try {
|
||||
this._room && this._room.startP2PSession();
|
||||
} catch (error) {
|
||||
logger.error("Start P2P failed", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Stops P2P (for tests only)
|
||||
* @private
|
||||
*/
|
||||
_stopP2P() {
|
||||
try {
|
||||
this._room && this._room.stopP2PSession();
|
||||
} catch (error) {
|
||||
logger.error("Stop P2P failed", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Checks whether or not our connection is currently in interrupted and
|
||||
* reconnect attempts are in progress.
|
||||
@@ -736,7 +811,7 @@ export default {
|
||||
* @returns {boolean} true if the connection is in interrupted state or
|
||||
* false otherwise.
|
||||
*/
|
||||
isConnectionInterrupted () {
|
||||
isConnectionInterrupted() {
|
||||
return this._room.isConnectionInterrupted();
|
||||
},
|
||||
/**
|
||||
@@ -747,20 +822,20 @@ export default {
|
||||
* @returns {JitsiParticipant|null} participant instance for given id or
|
||||
* null if not found.
|
||||
*/
|
||||
getParticipantById (id) {
|
||||
getParticipantById(id) {
|
||||
return room ? room.getParticipantById(id) : null;
|
||||
},
|
||||
/**
|
||||
* Checks whether the user identified by given id is currently connected.
|
||||
* Get participant connection status for the participant.
|
||||
*
|
||||
* @param {string} id participant's identifier(MUC nickname)
|
||||
*
|
||||
* @returns {boolean|null} true if participant's connection is ok or false
|
||||
* if the user is having connectivity issues.
|
||||
* @returns {ParticipantConnectionStatus|null} the status of the participant
|
||||
* or null if no such participant is found or participant is the local user.
|
||||
*/
|
||||
isParticipantConnectionActive (id) {
|
||||
getParticipantConnectionStatus(id) {
|
||||
let participant = this.getParticipantById(id);
|
||||
return participant ? participant.isConnectionActive() : null;
|
||||
return participant ? participant.getConnectionStatus() : null;
|
||||
},
|
||||
/**
|
||||
* Gets the display name foe the <tt>JitsiParticipant</tt> identified by
|
||||
@@ -771,7 +846,7 @@ export default {
|
||||
* @return {string} the participant's display name or the default string if
|
||||
* absent.
|
||||
*/
|
||||
getParticipantDisplayName (id) {
|
||||
getParticipantDisplayName(id) {
|
||||
let displayName = getDisplayName(id);
|
||||
if (displayName) {
|
||||
return displayName;
|
||||
@@ -784,7 +859,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
getMyUserId () {
|
||||
getMyUserId() {
|
||||
return this._room
|
||||
&& this._room.myUserId();
|
||||
},
|
||||
@@ -810,7 +885,7 @@ export default {
|
||||
* @param id the id for the user audio level to return (the id value is
|
||||
* returned for the participant using getMyUserId() method)
|
||||
*/
|
||||
getPeerSSRCAudioLevel (id) {
|
||||
getPeerSSRCAudioLevel(id) {
|
||||
return this.audioLevelsMap[id];
|
||||
},
|
||||
/**
|
||||
@@ -830,7 +905,7 @@ export default {
|
||||
},
|
||||
// end used by torture
|
||||
|
||||
getLogs () {
|
||||
getLogs() {
|
||||
return room.getLogs();
|
||||
},
|
||||
|
||||
@@ -839,7 +914,7 @@ export default {
|
||||
* debugging.
|
||||
* @param filename (optional) specify target filename
|
||||
*/
|
||||
saveLogs (filename = 'meetlog.json') {
|
||||
saveLogs(filename = 'meetlog.json') {
|
||||
// this can be called from console and will not have reference to this
|
||||
// that's why we reference the global var
|
||||
let logs = APP.conference.getLogs();
|
||||
@@ -949,13 +1024,13 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Start using provided video stream.
|
||||
* Stops previous video stream.
|
||||
* @param {JitsiLocalTrack} [stream] new stream to use or null
|
||||
* @returns {Promise}
|
||||
*/
|
||||
useVideoStream (newStream) {
|
||||
/**
|
||||
* Start using provided video stream.
|
||||
* Stops previous video stream.
|
||||
* @param {JitsiLocalTrack} [stream] new stream to use or null
|
||||
* @returns {Promise}
|
||||
*/
|
||||
useVideoStream(newStream) {
|
||||
return room.replaceTrack(localVideo, newStream)
|
||||
.then(() => {
|
||||
// We call dispose after doing the replace because
|
||||
@@ -990,7 +1065,7 @@ export default {
|
||||
* @param {JitsiLocalTrack} [stream] new stream to use or null
|
||||
* @returns {Promise}
|
||||
*/
|
||||
useAudioStream (newStream) {
|
||||
useAudioStream(newStream) {
|
||||
return room.replaceTrack(localAudio, newStream)
|
||||
.then(() => {
|
||||
// We call dispose after doing the replace because
|
||||
@@ -1013,8 +1088,9 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
videoSwitchInProgress: false,
|
||||
toggleScreenSharing (shareScreen = !this.isSharingScreen) {
|
||||
toggleScreenSharing(shareScreen = !this.isSharingScreen) {
|
||||
if (this.videoSwitchInProgress) {
|
||||
logger.warn("Switch in progress.");
|
||||
return;
|
||||
@@ -1137,7 +1213,7 @@ export default {
|
||||
/**
|
||||
* Setup interaction between conference and UI.
|
||||
*/
|
||||
_setupListeners () {
|
||||
_setupListeners() {
|
||||
// add local streams when joined to the conference
|
||||
room.on(ConferenceEvents.CONFERENCE_JOINED, () => {
|
||||
APP.store.dispatch(conferenceJoined(room));
|
||||
@@ -1187,14 +1263,18 @@ export default {
|
||||
|
||||
|
||||
room.on(ConferenceEvents.USER_ROLE_CHANGED, (id, role) => {
|
||||
APP.store.dispatch(participantRoleChanged(id, role));
|
||||
if (this.isLocalId(id)) {
|
||||
logger.info(`My role changed, new role: ${role}`);
|
||||
|
||||
APP.store.dispatch(localParticipantRoleChanged(role));
|
||||
|
||||
if (this.isModerator !== room.isModerator()) {
|
||||
this.isModerator = room.isModerator();
|
||||
APP.UI.updateLocalRole(room.isModerator());
|
||||
}
|
||||
} else {
|
||||
APP.store.dispatch(participantRoleChanged(id, role));
|
||||
|
||||
let user = room.getParticipantById(id);
|
||||
if (user) {
|
||||
APP.UI.updateUserRole(user);
|
||||
@@ -1259,24 +1339,16 @@ export default {
|
||||
APP.UI.showCustomToolbarPopup('#talkWhileMutedPopup', true, 5000);
|
||||
});
|
||||
|
||||
/*
|
||||
room.on(ConferenceEvents.IN_LAST_N_CHANGED, (inLastN) => {
|
||||
//FIXME
|
||||
if (config.muteLocalVideoIfNotInLastN) {
|
||||
// TODO mute or unmute if required
|
||||
// mark video on UI
|
||||
// APP.UI.markVideoMuted(true/false);
|
||||
}
|
||||
});
|
||||
*/
|
||||
room.on(
|
||||
ConferenceEvents.LAST_N_ENDPOINTS_CHANGED, (ids, enteringIds) => {
|
||||
APP.UI.handleLastNEndpoints(ids, enteringIds);
|
||||
ConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
|
||||
(leavingIds, enteringIds) => {
|
||||
APP.UI.handleLastNEndpoints(leavingIds, enteringIds);
|
||||
});
|
||||
|
||||
room.on(
|
||||
ConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED,
|
||||
(id, isActive) => {
|
||||
APP.UI.participantConnectionStatusChanged(id, isActive);
|
||||
id => {
|
||||
APP.UI.participantConnectionStatusChanged(id);
|
||||
});
|
||||
room.on(ConferenceEvents.DOMINANT_SPEAKER_CHANGED, (id) => {
|
||||
if (this.isLocalId(id)) {
|
||||
@@ -1299,10 +1371,15 @@ export default {
|
||||
room.on(ConferenceEvents.CONNECTION_RESTORED, () => {
|
||||
APP.UI.markVideoInterrupted(false);
|
||||
});
|
||||
room.on(ConferenceEvents.MESSAGE_RECEIVED, (id, text, ts) => {
|
||||
room.on(ConferenceEvents.MESSAGE_RECEIVED, (id, body, ts) => {
|
||||
let nick = getDisplayName(id);
|
||||
APP.API.notifyReceivedChatMessage(id, nick, text, ts);
|
||||
APP.UI.addMessage(id, nick, text, ts);
|
||||
APP.API.notifyReceivedChatMessage({
|
||||
id,
|
||||
nick,
|
||||
body,
|
||||
ts
|
||||
});
|
||||
APP.UI.addMessage(id, nick, body, ts);
|
||||
});
|
||||
APP.UI.addListener(UIEvents.MESSAGE_CREATED, (message) => {
|
||||
APP.API.notifySendingChatMessage(message);
|
||||
@@ -1449,7 +1526,10 @@ export default {
|
||||
|
||||
APP.UI.addListener(UIEvents.EMAIL_CHANGED, this.changeLocalEmail);
|
||||
room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
|
||||
APP.store.dispatch(changeParticipantEmail(from, data.value));
|
||||
APP.store.dispatch(participantUpdated({
|
||||
id: from,
|
||||
email: data.value
|
||||
}));
|
||||
APP.UI.setUserEmail(from, data.value);
|
||||
});
|
||||
|
||||
@@ -1457,18 +1537,25 @@ export default {
|
||||
this.commands.defaults.AVATAR_URL,
|
||||
(data, from) => {
|
||||
APP.store.dispatch(
|
||||
changeParticipantAvatarURL(from, data.value));
|
||||
participantUpdated({
|
||||
id: from,
|
||||
avatarURL: data.value
|
||||
}));
|
||||
APP.UI.setUserAvatarUrl(from, data.value);
|
||||
});
|
||||
|
||||
room.addCommandListener(this.commands.defaults.AVATAR_ID,
|
||||
(data, from) => {
|
||||
APP.store.dispatch(
|
||||
changeParticipantAvatarID(from, data.value));
|
||||
participantUpdated({
|
||||
id: from,
|
||||
avatarID: data.value
|
||||
}));
|
||||
APP.UI.setUserAvatarID(from, data.value);
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.NICKNAME_CHANGED, changeLocalDisplayName);
|
||||
APP.UI.addListener(UIEvents.NICKNAME_CHANGED,
|
||||
this.changeLocalDisplayName.bind(this));
|
||||
|
||||
APP.UI.addListener(UIEvents.START_MUTED_CHANGED,
|
||||
(startAudioMuted, startVideoMuted) => {
|
||||
@@ -1575,7 +1662,6 @@ export default {
|
||||
})
|
||||
.catch((err) => {
|
||||
APP.UI.showDeviceErrorDialog(null, err);
|
||||
APP.UI.setSelectedCameraFromSettings();
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1597,7 +1683,6 @@ export default {
|
||||
})
|
||||
.catch((err) => {
|
||||
APP.UI.showDeviceErrorDialog(err, null);
|
||||
APP.UI.setSelectedMicFromSettings();
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1613,7 +1698,6 @@ export default {
|
||||
logger.warn('Failed to change audio output device. ' +
|
||||
'Default or previously set audio output device ' +
|
||||
'will be used instead.', err);
|
||||
APP.UI.setSelectedAudioOutputFromSettings();
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1709,8 +1793,8 @@ export default {
|
||||
}
|
||||
|
||||
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
||||
|
||||
APP.UI.onAvailableDevicesChanged(devices);
|
||||
APP.store.dispatch(updateDeviceList(devices));
|
||||
});
|
||||
|
||||
this.deviceChangeListener = (devices) =>
|
||||
@@ -1843,7 +1927,7 @@ export default {
|
||||
* @param {boolean} [requestFeedback=false] if user feedback should be
|
||||
* requested
|
||||
*/
|
||||
hangup (requestFeedback = false) {
|
||||
hangup(requestFeedback = false) {
|
||||
eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
|
||||
APP.UI.hideRingOverLay();
|
||||
let requestFeedbackPromise = requestFeedback
|
||||
@@ -1875,10 +1959,17 @@ export default {
|
||||
if (email === APP.settings.getEmail()) {
|
||||
return;
|
||||
}
|
||||
APP.store.dispatch(changeParticipantEmail(room.myUserId(), email));
|
||||
|
||||
const localId = room ? room.myUserId() : undefined;
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
id: localId,
|
||||
local: true,
|
||||
email
|
||||
}));
|
||||
|
||||
APP.settings.setEmail(email);
|
||||
APP.UI.setUserEmail(room.myUserId(), email);
|
||||
APP.UI.setUserEmail(localId, email);
|
||||
sendData(commands.EMAIL, email);
|
||||
},
|
||||
|
||||
@@ -1892,10 +1983,17 @@ export default {
|
||||
if (url === APP.settings.getAvatarUrl()) {
|
||||
return;
|
||||
}
|
||||
APP.store.dispatch(changeParticipantAvatarURL(room.myUserId(), url));
|
||||
|
||||
const localId = room ? room.myUserId() : undefined;
|
||||
|
||||
APP.store.dispatch(participantUpdated({
|
||||
id: localId,
|
||||
local: true,
|
||||
avatarURL: url
|
||||
}));
|
||||
|
||||
APP.settings.setAvatarUrl(url);
|
||||
APP.UI.setUserAvatarUrl(room.myUserId(), url);
|
||||
APP.UI.setUserAvatarUrl(localId, url);
|
||||
sendData(commands.AVATAR_URL, url);
|
||||
},
|
||||
|
||||
@@ -1907,7 +2005,7 @@ export default {
|
||||
* @throws NetworkError or InvalidStateError or Error if the operation
|
||||
* fails.
|
||||
*/
|
||||
sendEndpointMessage (to, payload) {
|
||||
sendEndpointMessage(to, payload) {
|
||||
room.sendEndpointMessage(to, payload);
|
||||
},
|
||||
|
||||
@@ -1916,7 +2014,7 @@ export default {
|
||||
* @param {String} eventName the name of the event
|
||||
* @param {Function} listener the listener.
|
||||
*/
|
||||
addListener (eventName, listener) {
|
||||
addListener(eventName, listener) {
|
||||
eventEmitter.addListener(eventName, listener);
|
||||
},
|
||||
|
||||
@@ -1926,7 +2024,38 @@ export default {
|
||||
* listener
|
||||
* @param {Function} listener the listener.
|
||||
*/
|
||||
removeListener (eventName, listener) {
|
||||
removeListener(eventName, listener) {
|
||||
eventEmitter.removeListener(eventName, listener);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the participant given by participantId is currently in the
|
||||
* last N set if there's one supported.
|
||||
*
|
||||
* @param participantId the identifier of the participant
|
||||
* @returns {boolean} {true} if the participant given by the participantId
|
||||
* is currently in the last N set or if there's no last N set at this point
|
||||
* and {false} otherwise
|
||||
*/
|
||||
isInLastN(participantId) {
|
||||
return room.isInLastN(participantId);
|
||||
},
|
||||
/**
|
||||
* Changes the display name for the local user
|
||||
* @param nickname {string} the new display name
|
||||
*/
|
||||
changeLocalDisplayName(nickname = '') {
|
||||
const formattedNickname
|
||||
= nickname.trim().substr(0, MAX_DISPLAY_NAME_LENGTH);
|
||||
|
||||
if (formattedNickname === APP.settings.getDisplayName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.settings.setDisplayName(formattedNickname);
|
||||
if (room) {
|
||||
room.setDisplayName(formattedNickname);
|
||||
APP.UI.changeDisplayName(this.getMyUserId(), formattedNickname);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
19
config.js
19
config.js
@@ -20,6 +20,13 @@ var config = { // eslint-disable-line no-unused-vars
|
||||
//focusUserJid: 'focus@auth.jitsi-meet.example.com', // The real JID of focus participant - can be overridden here
|
||||
//defaultSipNumber: '', // Default SIP number
|
||||
|
||||
// The STUN servers that will be used in the peer to peer connections
|
||||
p2pStunServers: [
|
||||
{ urls: "stun:stun.l.google.com:19302" },
|
||||
{ urls: "stun:stun1.l.google.com:19302" },
|
||||
{ urls: "stun:stun2.l.google.com:19302" }
|
||||
],
|
||||
|
||||
// The ID of the jidesha extension for Chrome.
|
||||
desktopSharingChromeExtId: null,
|
||||
// Whether desktop sharing should be disabled on Chrome.
|
||||
@@ -58,7 +65,6 @@ var config = { // eslint-disable-line no-unused-vars
|
||||
//enableClosePage: false, // enabling the close page will ignore the welcome
|
||||
// page redirection when call is hangup
|
||||
disableSimulcast: false,
|
||||
logStats: false, // Enable logging of PeerConnection stats via the focus
|
||||
// requireDisplayName: true, // Forces the participants that doesn't have display name to enter it when they enter the room.
|
||||
// startAudioMuted: 10, // every participant after the Nth will start audio muted
|
||||
// startVideoMuted: 10, // every participant after the Nth will start video muted
|
||||
@@ -80,5 +86,14 @@ var config = { // eslint-disable-line no-unused-vars
|
||||
// disables or enables RTX (RFC 4588) (defaults to false).
|
||||
disableRtx: false,
|
||||
// Sets the preferred resolution (height) for local video. Defaults to 360.
|
||||
resolution: 720
|
||||
resolution: 720,
|
||||
// Enables peer to peer mode. When enabled system will try to establish
|
||||
// direct connection given that there are exactly 2 participants in
|
||||
// the room. If that succeeds the conference will stop sending data through
|
||||
// the JVB and use the peer to peer connection instead. When 3rd participant
|
||||
// joins the conference will be moved back to the JVB connection.
|
||||
//enableP2P: true
|
||||
// How long we're going to wait, before going back to P2P after
|
||||
// the 3rd participant has left the conference (to filter out page reload)
|
||||
//backToP2PDelay: 5
|
||||
};
|
||||
|
||||
3
connection_optimization/.eslintrc.js
Normal file
3
connection_optimization/.eslintrc.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
'extends': '../react/.eslintrc.js'
|
||||
};
|
||||
@@ -1,75 +1,87 @@
|
||||
/* global config, getRoomName, getConfigParamsFromUrl */
|
||||
/* global createConnectionExternally */
|
||||
/* global config,
|
||||
createConnectionExternally,
|
||||
getConfigParamsFromUrl,
|
||||
getRoomName */
|
||||
|
||||
/**
|
||||
* Implements extrnal connect using createConnectionExtenally function defined
|
||||
* Implements external connect using createConnectionExternally function defined
|
||||
* in external_connect.js for Jitsi Meet. Parses the room name and token from
|
||||
* the url and executes createConnectionExtenally.
|
||||
* the URL and executes createConnectionExternally.
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
* external_connect.js.
|
||||
*/
|
||||
|
||||
const hashParams = getConfigParamsFromUrl('hash', true);
|
||||
const searchParams = getConfigParamsFromUrl('search', true);
|
||||
|
||||
// URL params have higher proirity than config params.
|
||||
let url
|
||||
= hashParams.hasOwnProperty('config.externalConnectUrl')
|
||||
? hashParams['config.externalConnectUrl']
|
||||
: config.externalConnectUrl;
|
||||
|
||||
if (url && window.createConnectionExternally) {
|
||||
const roomName = getRoomName();
|
||||
|
||||
if (roomName) {
|
||||
url += `?room=${roomName}`;
|
||||
|
||||
const token
|
||||
= hashParams['config.token'] || config.token || searchParams.jwt;
|
||||
|
||||
if (token) {
|
||||
url += `&token=${token}`;
|
||||
}
|
||||
|
||||
createConnectionExternally(
|
||||
url,
|
||||
connectionInfo => {
|
||||
// Sets that global variable to be used later by connect method
|
||||
// in connection.js.
|
||||
window.XMPPAttachInfo = {
|
||||
status: 'success',
|
||||
data: connectionInfo
|
||||
};
|
||||
checkForConnectHandlerAndConnect();
|
||||
},
|
||||
errorCallback);
|
||||
} else {
|
||||
errorCallback();
|
||||
}
|
||||
} else {
|
||||
errorCallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes createConnectionExternally function.
|
||||
* Check if connect from connection.js was executed and executes the handler
|
||||
* that is going to finish the connect work.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
(function () {
|
||||
var hashParams = getConfigParamsFromUrl("hash", true);
|
||||
var searchParams = getConfigParamsFromUrl("search", true);
|
||||
function checkForConnectHandlerAndConnect() {
|
||||
window.APP
|
||||
&& window.APP.connect.status === 'ready'
|
||||
&& window.APP.connect.handler();
|
||||
}
|
||||
|
||||
//Url params have higher proirity than config params
|
||||
var url = config.externalConnectUrl;
|
||||
if(hashParams.hasOwnProperty('config.externalConnectUrl'))
|
||||
url = hashParams["config.externalConnectUrl"];
|
||||
/**
|
||||
* Implements a callback to be invoked if anything goes wrong.
|
||||
*
|
||||
* @param {Error} error - The specifics of what went wrong.
|
||||
* @returns {void}
|
||||
*/
|
||||
function errorCallback(error) {
|
||||
// The value of error is undefined if external connect is disabled.
|
||||
error && console.warn(error);
|
||||
|
||||
/**
|
||||
* 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 = hashParams["config.token"] || config.token ||
|
||||
searchParams.jwt;
|
||||
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);
|
||||
})();
|
||||
// Sets that global variable to be used later by connect method in
|
||||
// connection.js.
|
||||
window.XMPPAttachInfo = {
|
||||
status: 'error'
|
||||
};
|
||||
checkForConnectHandlerAndConnect();
|
||||
}
|
||||
|
||||
@@ -66,18 +66,4 @@
|
||||
@include keyframes(slideInExtContainer) {
|
||||
from { width: 0; }
|
||||
to { width: $sidebarWidth; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Fade in / out animations
|
||||
**/
|
||||
|
||||
@include keyframes(fadeIn) {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@include keyframes(fadeOut) {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
@@ -84,7 +84,7 @@ form {
|
||||
height: 74px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
z-index: 2;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
.leftwatermark {
|
||||
@@ -106,7 +106,7 @@ form {
|
||||
font-size: 11pt;
|
||||
color: rgba(255,255,255,.50);
|
||||
text-decoration: none;
|
||||
z-index: 100;
|
||||
z-index: $poweredByZ;
|
||||
}
|
||||
|
||||
.connected {
|
||||
|
||||
@@ -212,24 +212,24 @@
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
:not(.default-scrollbar)::-webkit-scrollbar {
|
||||
background: #06a5df;
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button {
|
||||
:not(.default-scrollbar)::-webkit-scrollbar-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
:not(.default-scrollbar)::-webkit-scrollbar-track {
|
||||
background: black;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track-piece {
|
||||
:not(.default-scrollbar)::-webkit-scrollbar-track-piece {
|
||||
background: black;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
:not(.default-scrollbar)::-webkit-scrollbar-thumb {
|
||||
background: #06a5df;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
.settingsContent {
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
|
||||
#localVideoPreview {
|
||||
width: 50%;
|
||||
align-self: baseline;
|
||||
}
|
||||
|
||||
.deviceSelection {
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: left;
|
||||
margin-left: 10px;
|
||||
|
||||
.device {
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
|
||||
select {
|
||||
flex: 1;
|
||||
margin_right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@
|
||||
flex-direction: column-reverse;
|
||||
flex-wrap: nowrap;
|
||||
position: relative;
|
||||
z-index: 1; // Set z-index to make element visible
|
||||
width: $hideFilmstripButtonWidth;
|
||||
z-index: $zindex1; // Set z-index to make element visible.
|
||||
width: $filmstripToggleButtonWidth;
|
||||
|
||||
button {
|
||||
font-size: 14px;
|
||||
@@ -50,16 +50,16 @@
|
||||
position:relative;
|
||||
height:196px;
|
||||
padding: 0;
|
||||
/*The filmstrip should not be covered by the left toolbar*/
|
||||
/* The filmstrip should not be covered by the left toolbar. */
|
||||
padding-left: $defaultToolbarSize + 5;
|
||||
bottom: 0;
|
||||
width:auto;
|
||||
border: $thumbnailsBorder solid transparent;
|
||||
z-index: 5;
|
||||
z-index: $filmstripVideosZ;
|
||||
transition: bottom 2s;
|
||||
overflow: visible !important;
|
||||
/*!!!Removes the gap between the local video container and the remote
|
||||
videos.*/
|
||||
/*!!! Removes the gap between the local video container and the remote
|
||||
videos. */
|
||||
font-size: 0pt;
|
||||
|
||||
&.hidden {
|
||||
@@ -79,8 +79,8 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Focused video thumbnail.
|
||||
*/
|
||||
* Focused video thumbnail.
|
||||
*/
|
||||
&.videoContainerFocused {
|
||||
transition-duration: 0.5s;
|
||||
-webkit-transition-duration: 0.5s;
|
||||
@@ -97,8 +97,8 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Hovered video thumbnail.
|
||||
*/
|
||||
* Hovered video thumbnail.
|
||||
*/
|
||||
&:hover {
|
||||
cursor: hand;
|
||||
border: $thumbnailVideoBorder solid $videoThumbnailHovered;
|
||||
@@ -110,7 +110,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* With TemasysWebRTC plugin <object/> element is used
|
||||
/* With the TemasysWebRTC plugin <object/> element is used
|
||||
instead of <video/> */
|
||||
& > video,
|
||||
& > object {
|
||||
@@ -121,4 +121,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Style the filmstrip videos in filmstrip-only mode.
|
||||
*/
|
||||
&__videos-filmstripOnly {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
padding-right: $defaultToolbarSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@
|
||||
.icon-download:before {
|
||||
content: "\e902";
|
||||
}
|
||||
.icon-dialpad:before {
|
||||
content: "\e61c";
|
||||
}
|
||||
.icon-edit:before {
|
||||
content: "\e907";
|
||||
}
|
||||
|
||||
@@ -27,7 +27,86 @@
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
&__button {
|
||||
float: none !important;
|
||||
&-filmstrip-only {
|
||||
background-color: $inlayFilmstripOnlyBg;
|
||||
color: $inlayFilmstripOnlyColor;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
margin-top: 20px;
|
||||
bottom: 30px;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
max-height: 120px;
|
||||
height: 80%;
|
||||
right: 0px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
&__content {
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
> .button-control {
|
||||
align-self: center;
|
||||
}
|
||||
> #reloadProgressBar {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
margin-bottom: 0px;
|
||||
width: 100%;
|
||||
border-radius: 0px;
|
||||
> .aui-progress-indicator-value {
|
||||
border-radius: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__container {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
&__text {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 50px;
|
||||
align-self: center;
|
||||
color: $inlayIconColor;
|
||||
opacity: 0.6;
|
||||
}
|
||||
&__icon-container {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
&__avatar-container {
|
||||
position: relative;
|
||||
> img {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon-background {
|
||||
background: $inlayIconBg;
|
||||
opacity: 0.6;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1010;
|
||||
z-index: $jitsipopoverZ;
|
||||
display: none;
|
||||
max-width: 300px;
|
||||
min-width: 100px;
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 8px;
|
||||
z-index: 1;
|
||||
z-index: $zindex1;
|
||||
width: 0;
|
||||
height: 0;
|
||||
content: '';
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
overflow: hidden;
|
||||
padding: 20px;
|
||||
margin-left: 10px;
|
||||
z-index: 10;
|
||||
z-index: $zindex10;
|
||||
border-radius: $borderRadius;
|
||||
background-attachment: scroll;
|
||||
background-size: auto auto;
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#notice {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
.notice {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
z-index: $zindex3;
|
||||
margin-top: 6px;
|
||||
|
||||
@include transform(translateX(-50%));
|
||||
|
||||
&__message {
|
||||
background-color: #000000;
|
||||
color: white;
|
||||
padding: 3px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
#noticeText {
|
||||
background-color: #000000;
|
||||
color: white;
|
||||
padding: 3px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1015;
|
||||
z-index: $popoverZ;
|
||||
display: none;
|
||||
max-width: 300px;
|
||||
min-width: 100px;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 0;
|
||||
z-index: 800;
|
||||
z-index: $sideToolbarContainerZ;
|
||||
|
||||
/**
|
||||
* Labels inside the side panel.
|
||||
@@ -113,6 +113,12 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#deviceOptionsWrapper {
|
||||
button {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile
|
||||
*/
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
#toast-container.notification-bottom-right {
|
||||
$videoOffset: 2 * ($thumbnailVideoMargin + $thumbnailsBorder) + $thumbnailVideoBorder;
|
||||
bottom: 135px;
|
||||
right: $hideFilmstripButtonWidth + $videoOffset;
|
||||
right: $filmstripToggleButtonWidth + $videoOffset;
|
||||
}
|
||||
|
||||
#toast-container * {
|
||||
|
||||
@@ -1,184 +1,256 @@
|
||||
.toolbar {
|
||||
background-color: $toolbarBackground;
|
||||
position: relative;
|
||||
z-index: $toolbarZ;
|
||||
height: 100%;
|
||||
pointer-events: auto;
|
||||
|
||||
/**
|
||||
* Splitter button in the toolbar.
|
||||
*/
|
||||
&__splitter {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 1px;
|
||||
height: 50%;
|
||||
margin: 0 $splitterToolbarButtonMargin;
|
||||
background: $splitterColor;
|
||||
}
|
||||
}
|
||||
|
||||
#mainToolbarContainer{
|
||||
display: block;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top:0;
|
||||
left:0;
|
||||
right:0;
|
||||
z-index: $toolbarZ;
|
||||
pointer-events: none;
|
||||
min-height: 100px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#subject {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
width: auto;
|
||||
padding: 5px;
|
||||
margin-left: 40%;
|
||||
margin-right: 40%;
|
||||
text-align: center;
|
||||
background: linear-gradient(to bottom, rgba(255,255,255,.85) , rgba(255,255,255,.35));
|
||||
box-shadow: 0 0 2px #000000, 0 0 10px #000000;
|
||||
border-bottom-left-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
}
|
||||
|
||||
#mainToolbar {
|
||||
height: $defaultToolbarSize;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 30px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: auto;
|
||||
border-radius: 3px;
|
||||
.button:first-child {
|
||||
border-bottom-left-radius: 3px;
|
||||
border-top-left-radius: 3px;
|
||||
}
|
||||
.button:last-child {
|
||||
border-bottom-right-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
#extendedToolbar {
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
width: $defaultToolbarSize;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding-top: 10px;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
transform: translateX(-100%);
|
||||
-webkit-transform: translateX(-100%);
|
||||
}
|
||||
|
||||
#toolbar_button_hangup {
|
||||
color: #BF2117;
|
||||
font-size: $hangupFontSize !important;
|
||||
}
|
||||
|
||||
#toolbar_button_etherpad {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mainToolbar a.button:last-child::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
color: #FFFFFF;
|
||||
top:0px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
font-size: $toolbarFontSize !important;
|
||||
line-height: 50px !important;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.button[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.button.unclickable {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.button.toggled {
|
||||
background: $toolbarToggleBackground !important;
|
||||
}
|
||||
|
||||
a.button.unclickable:hover,
|
||||
a.button.unclickable:active,
|
||||
a.button.unclickable.selected{
|
||||
cursor: default;
|
||||
background: none;
|
||||
}
|
||||
|
||||
a.button:hover,
|
||||
a.button:active,
|
||||
a.button.selected {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
// sum opacity with background layer should give us 0.8
|
||||
background: $toolbarSelectBackground;
|
||||
}
|
||||
|
||||
a.button>#avatar {
|
||||
width: 30px;
|
||||
border-radius: 50%;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
#feedbackButton {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Round badge.
|
||||
*/
|
||||
.badge-round {
|
||||
background-color: $toolbarBadgeBackground;
|
||||
color: $toolbarBadgeColor;
|
||||
font-size: 9px;
|
||||
line-height: 13px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
min-width: 13px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
box-sizing: border-box;
|
||||
vertical-align: middle;
|
||||
color: $toolbarBadgeColor;
|
||||
// Do not inherit the font-family from the toolbar button, because it's an
|
||||
// icon style.
|
||||
font-family: $baseFontFamily;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
line-height: 13px;
|
||||
min-width: 13px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbar button styles.
|
||||
*/
|
||||
.button {
|
||||
color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
z-index: $zindex1;
|
||||
display: inline-block;
|
||||
font-size: $toolbarFontSize !important;
|
||||
height: 50px;
|
||||
line-height: 50px !important;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
top:0px;
|
||||
vertical-align: middle;
|
||||
width: 50px;
|
||||
|
||||
&_hangup {
|
||||
color: $hangupColor;
|
||||
font-size: $hangupFontSize !important;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:hover, &:active {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:not(.toggled) {
|
||||
&:hover, &:active {
|
||||
// sum opacity with background layer should give us 0.8
|
||||
background: $toolbarSelectBackground;
|
||||
}
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
background: $toolbarToggleBackground;
|
||||
|
||||
&.icon-camera {
|
||||
@extend .icon-camera-disabled;
|
||||
}
|
||||
|
||||
&.icon-full-screen {
|
||||
@extend .icon-exit-full-screen;
|
||||
}
|
||||
|
||||
&.icon-microphone {
|
||||
@extend .icon-mic-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
|
||||
&:hover, &:active, &.selected {
|
||||
background: none;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-container {
|
||||
display: block;
|
||||
left:0;
|
||||
min-height: 100px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right:0;
|
||||
text-align: center;
|
||||
top:0;
|
||||
z-index: $toolbarZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbar specific round badge.
|
||||
* Common toolbar styles.
|
||||
*/
|
||||
.toolbar .badge-round {
|
||||
position: absolute;
|
||||
right: 9px;
|
||||
bottom: 9px;
|
||||
.toolbar {
|
||||
background-color: $toolbarBackground;
|
||||
height: 100%;
|
||||
pointer-events: auto;
|
||||
position: relative;
|
||||
z-index: $toolbarZ;
|
||||
|
||||
/**
|
||||
* Splitter button in the toolbar.
|
||||
*/
|
||||
&__splitter {
|
||||
background: $splitterColor;
|
||||
display: inline-block;
|
||||
height: 50%;
|
||||
margin: 0 $splitterToolbarButtonMargin;
|
||||
vertical-align: middle;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary toolbar styles.
|
||||
*/
|
||||
&_primary {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 30px;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: $defaultToolbarSize;
|
||||
border-radius: 3px;
|
||||
opacity: 0;
|
||||
|
||||
@include transform(translateX(-50%));
|
||||
|
||||
.button:first-child {
|
||||
border-bottom-left-radius: 3px;
|
||||
border-top-left-radius: 3px;
|
||||
}
|
||||
.button:last-child {
|
||||
border-bottom-right-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&_primary a.button:last-child::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Secondary toolbar styles.
|
||||
*/
|
||||
&_secondary {
|
||||
position: absolute;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
height: 100%;
|
||||
justify-content: flex-start;
|
||||
left: 0;
|
||||
padding-top: 10px;
|
||||
top: 0;
|
||||
transform: translateX(-100%);
|
||||
width: $defaultToolbarSize;
|
||||
-webkit-transform: translateX(-100%);
|
||||
|
||||
.button.toggled:not(.icon-raised-hand) {
|
||||
background: $toolbarSelectBackground;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
|
||||
&:hover, &:active, &.selected {
|
||||
background: none;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Styles the toolbar in filmstrip-only mode.
|
||||
*/
|
||||
&_filmstrip-only {
|
||||
border-radius: 3px;
|
||||
bottom: 0;
|
||||
display: inline-block;
|
||||
height: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: $defaultToolbarSize;
|
||||
|
||||
.button:first-child {
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
.button:last-child {
|
||||
border-bottom-right-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbar specific round badge.
|
||||
*/
|
||||
.badge-round {
|
||||
bottom: 9px;
|
||||
position: absolute;
|
||||
right: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
.subject {
|
||||
background: linear-gradient(to bottom, rgba(255,255,255,.85) , rgba(255,255,255,.35));
|
||||
border-bottom-left-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
box-shadow: 0 0 2px #000000, 0 0 10px #000000;
|
||||
margin-left: 40%;
|
||||
margin-right: 40%;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: auto;
|
||||
z-index: $zindex3;
|
||||
|
||||
&.subject_slide-in {
|
||||
top: 80px;
|
||||
@include transition(top .3s ease-in);
|
||||
}
|
||||
|
||||
&.subject_slide-out {
|
||||
top: 0;
|
||||
@include transition(top .3s ease-out);
|
||||
}
|
||||
}
|
||||
|
||||
a.button>#avatar {
|
||||
border-radius: 50%;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
#feedbackButton {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,9 +344,13 @@ a.button>#avatar {
|
||||
* START of fade in animation for main toolbar
|
||||
*/
|
||||
.fadeIn {
|
||||
@include animation('fadeIn .3s linear .2s forwards');
|
||||
opacity: 1;
|
||||
|
||||
@include transition(all .3s ease-in);
|
||||
}
|
||||
|
||||
.fadeOut {
|
||||
@include animation('fadeOut .5s linear forwards');
|
||||
opacity: 0;
|
||||
|
||||
@include transition(all .3s ease-out);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
* Style variables
|
||||
*/
|
||||
$baseFontFamily: 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
$toolbarFontSize: 1.9em;
|
||||
$hangupColor: #bf2117;
|
||||
$hangupFontSize: 2em;
|
||||
|
||||
/**
|
||||
* Size variables.
|
||||
*/
|
||||
$defaultToolbarSize: 50px;
|
||||
|
||||
// Video layout.
|
||||
$thumbnailToolbarHeight: 22px;
|
||||
@@ -19,7 +18,7 @@ $thumbnailIndicatorSize: $thumbnailToolbarHeight;
|
||||
$thumbnailVideoMargin: 2px;
|
||||
$thumbnailsBorder: 2px;
|
||||
$thumbnailVideoBorder: 2px;
|
||||
$hideFilmstripButtonWidth: 17px;
|
||||
$filmstripToggleButtonWidth: 17px;
|
||||
|
||||
|
||||
/**
|
||||
@@ -34,14 +33,16 @@ $tooltipBg: rgba(0,0,0, 0.7);
|
||||
/**
|
||||
* Toolbar
|
||||
*/
|
||||
$toolbarTitleColor: #FFFFFF;
|
||||
$toolbarTitleFontSize: 19px;
|
||||
$defaultToolbarSize: 50px;
|
||||
$splitterToolbarButtonMargin: 18px;
|
||||
$toolbarBackground: rgba(0, 0, 0, 0.5);
|
||||
$toolbarSelectBackground: rgba(0, 0, 0, .6);
|
||||
$toolbarBadgeBackground: #165ECC;
|
||||
$toolbarBadgeColor: #FFFFFF;
|
||||
$toolbarFontSize: 1.9em;
|
||||
$toolbarSelectBackground: rgba(0, 0, 0, .6);
|
||||
$toolbarTitleColor: #FFFFFF;
|
||||
$toolbarTitleFontSize: 19px;
|
||||
$toolbarToggleBackground: #12499C;
|
||||
$splitterToolbarButtonMargin: 18px;
|
||||
|
||||
/**
|
||||
* Main controls
|
||||
@@ -78,6 +79,12 @@ $rateStarDefault: #ccc;
|
||||
$rateStarActivity: #165ecc;
|
||||
$rateStarSize: 34px;
|
||||
|
||||
/**
|
||||
* Modals
|
||||
*/
|
||||
$modalButtonFontSize: 14px;
|
||||
$modalTextColor: #333;
|
||||
|
||||
/**
|
||||
* Notifications
|
||||
*/
|
||||
@@ -104,13 +111,26 @@ $happySoftwareBackground: transparent;
|
||||
/**
|
||||
* Z-indexes. TODO: Replace this by a function.
|
||||
*/
|
||||
$tooltipsZ: 901;
|
||||
$toolbarZ: 900;
|
||||
$overlayZ: 902;
|
||||
$notificationZ: 1012;
|
||||
$ringingZ: 800;
|
||||
$dropdownZ: 901;
|
||||
$zindex0: 0;
|
||||
$zindex1: 1;
|
||||
$zindex2: 2;
|
||||
$zindex3: 3;
|
||||
$filmstripVideosZ: 5;
|
||||
$zindex10: 10;
|
||||
$reloadZ: 20;
|
||||
$poweredByZ: 100;
|
||||
$ringingZ: 300;
|
||||
$sideToolbarContainerZ: 300;
|
||||
$toolbarZ: 400;
|
||||
$tooltipsZ: 401;
|
||||
$dropdownMaskZ: 900;
|
||||
$dropdownZ: 901;
|
||||
$overlayZ: 902;
|
||||
$jitsipopoverZ: 1010;
|
||||
$centeredVideoLabelZ: 1011;
|
||||
$notificationZ: 1012;
|
||||
$popoverZ: 1015;
|
||||
|
||||
|
||||
/**
|
||||
* Font Colors
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
&__toptoolbar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
z-index: $zindex3;
|
||||
width: 100%;
|
||||
box-sizing: border-box; // Includes the padding in the 100% width.
|
||||
}
|
||||
@@ -59,7 +59,7 @@
|
||||
float: left;
|
||||
@include circle($thumbnailIndicatorSize);
|
||||
box-sizing: border-box;
|
||||
z-index: 3;
|
||||
z-index: $zindex3;
|
||||
background: $dominantSpeakerBg;
|
||||
color: $thumbnailPictogramColor;
|
||||
border: $thumbnailIndicatorBorder solid $thumbnailPictogramColor;
|
||||
@@ -113,7 +113,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
visibility: hidden;
|
||||
z-index: 2;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
z-index: $zindex1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -171,7 +171,7 @@
|
||||
}
|
||||
|
||||
#etherpad {
|
||||
z-index: 0;
|
||||
z-index: $zindex0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,7 +193,7 @@
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
line-height: $thumbnailToolbarHeight;
|
||||
z-index: 2;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,7 +233,7 @@
|
||||
padding: 3px 5px;
|
||||
font-size: 9pt;
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,7 +283,7 @@
|
||||
top: 0px;
|
||||
right: 0;
|
||||
margin: 7px;
|
||||
z-index: 3;
|
||||
z-index: $zindex3;
|
||||
width: 18px;
|
||||
height: 13px;
|
||||
color: #FFF;
|
||||
@@ -301,7 +301,7 @@
|
||||
margin-top: -17px;
|
||||
width: 6px;
|
||||
height: 35px;
|
||||
z-index: 2;
|
||||
z-index: $zindex2;
|
||||
border: none;
|
||||
|
||||
.audiodot-top,
|
||||
@@ -344,13 +344,13 @@
|
||||
background-clip: padding-box;
|
||||
-webkit-border-radius: 5px;
|
||||
-webkit-background-clip: padding-box;
|
||||
z-index: 20; /*The reload button should appear on top of the header!*/
|
||||
z-index: $reloadZ; /*The reload button should appear on top of the header!*/
|
||||
}
|
||||
|
||||
.audiolevel {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
z-index: $zindex0;
|
||||
border-radius:1px;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -408,7 +408,7 @@
|
||||
.noMic {
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
z-index: 1;
|
||||
z-index: $zindex1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url("../images/noMic.png");
|
||||
@@ -420,7 +420,7 @@
|
||||
.noVideo {
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
z-index: 1;
|
||||
z-index: $zindex1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url("../images/noVideo.png");
|
||||
@@ -453,7 +453,7 @@
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: auto;
|
||||
z-index: 2;
|
||||
z-index: $zindex2;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
@@ -477,7 +477,7 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
top:50%;
|
||||
z-index: 2;
|
||||
z-index: $zindex2;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
@@ -506,7 +506,7 @@
|
||||
#videoResolutionLabel,
|
||||
.centeredVideoLabel {
|
||||
display: none;
|
||||
z-index: 1011;
|
||||
z-index: $centeredVideoLabelZ;
|
||||
}
|
||||
|
||||
.centeredVideoLabel {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: #acacac;
|
||||
z-index: 2;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
#disable_welcome:checked + label
|
||||
@@ -35,7 +35,7 @@
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
color: #acacac;
|
||||
z-index: 2;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
#enter_room_form {
|
||||
@@ -74,7 +74,7 @@
|
||||
float: left;
|
||||
background-color: #FFFFFF;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
|
||||
&__reload {
|
||||
@@ -83,7 +83,7 @@
|
||||
color: #acacac;
|
||||
font-size: 1.9em;
|
||||
line-height: 55px;
|
||||
z-index: 3;
|
||||
z-index: $zindex3;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
@@ -104,7 +104,7 @@
|
||||
outline: none;
|
||||
float:left;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
z-index: $zindex2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
&_overlay {
|
||||
color: $primaryButtonColor;
|
||||
background-color: $overlayButtonBg;
|
||||
border-radius: 2px;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
background-color: $primaryButtonBackground;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
&_primary {
|
||||
background-color: $primaryButtonBackground;
|
||||
border: 1px solid $primaryButtonBackground;
|
||||
@@ -86,4 +98,4 @@
|
||||
&_center {
|
||||
float: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,11 @@
|
||||
@import 'overlay/overlay';
|
||||
@import 'inlay';
|
||||
@import 'reload_overlay/reload_overlay';
|
||||
@import 'modals/desktop-picker/desktop-picker';
|
||||
@import 'modals/device-selection/device-selection';
|
||||
@import 'modals/dialog';
|
||||
@import 'modals/feedback/feedback';
|
||||
@import 'modals/speaker_stats/speaker_stats';
|
||||
@import 'videolayout_default';
|
||||
@import 'notice';
|
||||
@import 'popup_menu';
|
||||
@@ -52,7 +55,6 @@
|
||||
@import 'welcome_page';
|
||||
@import 'toolbars';
|
||||
@import 'side_toolbar_container';
|
||||
@import 'device_settings_dialog';
|
||||
@import 'jquery.contextMenu';
|
||||
@import 'keyboard-shortcuts';
|
||||
@import 'redirect_page';
|
||||
|
||||
@@ -76,3 +76,10 @@
|
||||
border-bottom: 1px solid $auiBorderColor;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-dialog-form {
|
||||
color: $modalTextColor;
|
||||
}
|
||||
.modal-dialog-footer {
|
||||
font-size: $modalButtonFontSize;
|
||||
}
|
||||
|
||||
59
css/modals/desktop-picker/_desktop-picker.scss
Normal file
59
css/modals/desktop-picker/_desktop-picker.scss
Normal file
@@ -0,0 +1,59 @@
|
||||
.desktop-picker-pane {
|
||||
height: 320px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
|
||||
&.source-type-screen {
|
||||
.desktop-picker-source {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.desktop-source-preview-thumbnail {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.desktop-source-preview-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.source-type-window {
|
||||
.desktop-picker-source {
|
||||
display: inline-block;
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.desktop-picker-source {
|
||||
color: $defaultDarkFontColor;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
|
||||
&.is-selected {
|
||||
.desktop-source-preview-image-container {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: $borderRadius;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.desktop-source-preview-label {
|
||||
margin-top: 3px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.desktop-source-preview-thumbnail {
|
||||
box-shadow: 5px 5px 5px grey;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.desktop-source-preview-image-container {
|
||||
padding: 10px;
|
||||
}
|
||||
105
css/modals/device-selection/_device-selection.scss
Normal file
105
css/modals/device-selection/_device-selection.scss
Normal file
@@ -0,0 +1,105 @@
|
||||
.device-selection {
|
||||
color: $feedbackInputTextColor;
|
||||
|
||||
.device-selectors {
|
||||
font-size: 14px;
|
||||
|
||||
/* ensure all child components do not exceed parent width */
|
||||
button,
|
||||
div {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
> div:last-child {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.device-selector-icon {
|
||||
color: inherit;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.device-selection-column {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
|
||||
&.column-selectors {
|
||||
margin-left: 15px;
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
&.column-video {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.device-selection-video-container {
|
||||
/* TOFIX: to be removed when we move out from muted preview */
|
||||
background: black;
|
||||
border-radius: 3px;
|
||||
/* TOFIX-END */
|
||||
height: 160px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.video-input-preview {
|
||||
margin-top: 2px;
|
||||
position: relative;
|
||||
|
||||
> video {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.video-input-preview-muted {
|
||||
color: $participantNameColor;
|
||||
display: none;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
&.video-muted .video-input-preview-muted {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-input-preview-display {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.audio-output-preview {
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
a {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-input-preview {
|
||||
background: #f4f5f7;
|
||||
border-radius: 5px;
|
||||
height: 6px;
|
||||
|
||||
.audio-input-preview-level {
|
||||
background: #0052cc;
|
||||
border-radius: 5px;
|
||||
height: 100%;
|
||||
-webkit-transition: width .1s ease-in-out;
|
||||
-moz-transition: width .1s ease-in-out;
|
||||
-o-transition: width .1s ease-in-out;
|
||||
transition: width .1s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
css/modals/speaker_stats/_speaker_stats.scss
Normal file
55
css/modals/speaker_stats/_speaker_stats.scss
Normal file
@@ -0,0 +1,55 @@
|
||||
.speaker-stats {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
|
||||
.speaker-stats-item__status-dot {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
margin: 0 auto;
|
||||
|
||||
&.status-active {
|
||||
background: green;
|
||||
}
|
||||
|
||||
&.status-inactive {
|
||||
background: gray;
|
||||
}
|
||||
}
|
||||
|
||||
.status-user-left {
|
||||
color: $placeHolderColor;
|
||||
}
|
||||
|
||||
.speaker-stats-item__status,
|
||||
.speaker-stats-item__name,
|
||||
.speaker-stats-item__time {
|
||||
display: inline-block;
|
||||
margin: 5px 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.speaker-stats-item__status {
|
||||
width: 5%;
|
||||
}
|
||||
.speaker-stats-item__name {
|
||||
width: 40%;
|
||||
}
|
||||
.speaker-stats-item__time {
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
.speaker-stats-item:nth-child(even) {
|
||||
background: whitesmoke;
|
||||
}
|
||||
|
||||
.speaker-stats-item__name,
|
||||
.speaker-stats-item__time {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,16 @@
|
||||
position: fixed;
|
||||
z-index: $overlayZ;
|
||||
background: $defaultBackground;
|
||||
&.filmstrip-only {
|
||||
@include transparentBg($filmstripOnlyOverlayBg, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&__container-light {
|
||||
@include transparentBg($defaultBackground, 0.7);
|
||||
&.filmstrip-only {
|
||||
@include transparentBg($filmstripOnlyOverlayBg, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
@@ -21,6 +27,11 @@
|
||||
width: 56%;
|
||||
left: 50%;
|
||||
@include transform(translateX(-50%));
|
||||
&.filmstrip-only {
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
@include transform(none);
|
||||
}
|
||||
|
||||
&_bottom {
|
||||
position: absolute;
|
||||
@@ -33,4 +44,4 @@
|
||||
bottom: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,4 +13,7 @@
|
||||
#reloadProgressBar {
|
||||
width: 180px;
|
||||
margin: 5px auto;
|
||||
> .aui-progress-indicator-value {
|
||||
background: $reloadProgressBarBg;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,14 @@ $primaryButtonFontWeight: 400;
|
||||
|
||||
$buttonShadowColor: #192d4f;
|
||||
|
||||
$overlayButtonBg: #0074E0;
|
||||
|
||||
/**
|
||||
* Color variables
|
||||
**/
|
||||
$defaultBackground: #474747;
|
||||
$filmstripOnlyOverlayBg: #000;
|
||||
$reloadProgressBarBg: #0074E0;
|
||||
|
||||
/**
|
||||
* Connection indicator
|
||||
@@ -60,6 +64,10 @@ $dialogTitleFontWeight: 400;
|
||||
**/
|
||||
$inlayColorBg: lighten($defaultBackground, 20%);
|
||||
$inlayBorderColor: lighten($auiDialogContentBg, 10%);
|
||||
$inlayIconBg: #000;
|
||||
$inlayIconColor: #fff;
|
||||
$inlayFilmstripOnlyColor: #474747;
|
||||
$inlayFilmstripOnlyBg: #fff;
|
||||
|
||||
// Main controls
|
||||
$inputBackground: $controlBackground;
|
||||
|
||||
9
debian/jitsi-meet-web-config.postinst
vendored
9
debian/jitsi-meet-web-config.postinst
vendored
@@ -65,7 +65,7 @@ case "$1" in
|
||||
# SSL for nginx
|
||||
db_get jitsi-meet/cert-choice
|
||||
CERT_CHOICE="$RET"
|
||||
UPLOADED_CERT_CHOICE="A certificate is available and the files are uploaded on the server"
|
||||
UPLOADED_CERT_CHOICE="I want to use my own certificate"
|
||||
|
||||
if [ "$CERT_CHOICE" = "$UPLOADED_CERT_CHOICE" ] ; then
|
||||
db_set jitsi-meet/cert-path-key "/etc/ssl/$JVB_HOSTNAME.key"
|
||||
@@ -223,6 +223,13 @@ case "$1" in
|
||||
invoke-rc.d apache2 reload
|
||||
fi
|
||||
|
||||
echo "----------------"
|
||||
echo ""
|
||||
echo "You can now switch to a Let’s Encrypt certificate. To do so, execute:"
|
||||
echo "/usr/share/jitsi-meet/scripts/install-letsencrypt-cert.sh"
|
||||
echo ""
|
||||
echo "----------------"
|
||||
|
||||
# and we're done with debconf
|
||||
db_stop
|
||||
;;
|
||||
|
||||
3
debian/jitsi-meet-web-config.templates
vendored
3
debian/jitsi-meet-web-config.templates
vendored
@@ -1,9 +1,10 @@
|
||||
Template: jitsi-meet/cert-choice
|
||||
Type: select
|
||||
__Choices: Self-signed certificate will be generated, A certificate is available and the files are uploaded on the server
|
||||
__Choices: Generate a new self-signed certificate (You will later get a chance to obtain a Let's encrypt certificate), I want to use my own certificate
|
||||
_Description: SSL certificate for the Jitsi Meet instance
|
||||
Jitsi Meet is best to be set up with an SSL certificate.
|
||||
Having no certificate, a self-signed one will be generated.
|
||||
By choosing self-signed you will later have a chance to install Let’s Encrypt certificates.
|
||||
Having a certificate signed by a recognised CA, it can be uploaded on the server
|
||||
and point its location. The default filenames will be /etc/ssl/--domain.name--.key
|
||||
for the key and /etc/ssl/--domain.name--.crt for the certificate.
|
||||
|
||||
5
debian/jitsi-meet-web.install
vendored
5
debian/jitsi-meet-web.install
vendored
@@ -1,11 +1,14 @@
|
||||
*.js /usr/share/jitsi-meet/
|
||||
interface_config.js /usr/share/jitsi-meet/
|
||||
logging_config.js /usr/share/jitsi-meet/
|
||||
*.json /usr/share/jitsi-meet/
|
||||
*.html /usr/share/jitsi-meet/
|
||||
*.ico /usr/share/jitsi-meet/
|
||||
libs /usr/share/jitsi-meet/
|
||||
static /usr/share/jitsi-meet/
|
||||
css/all.css /usr/share/jitsi-meet/css/
|
||||
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/
|
||||
resources/*.sh /usr/share/jitsi-meet/scripts/
|
||||
|
||||
4
debian/po/templates.pot
vendored
4
debian/po/templates.pot
vendored
@@ -20,13 +20,13 @@ msgstr ""
|
||||
#. Type: select
|
||||
#. Choices
|
||||
#: ../jitsi-meet-web-config.templates:1001
|
||||
msgid "Self-signed certificate will be generated"
|
||||
msgid "Generate a new self-signed certificate (You will later get a chance to obtain a Let's encrypt certificate)"
|
||||
msgstr ""
|
||||
|
||||
#. Type: select
|
||||
#. Choices
|
||||
#: ../jitsi-meet-web-config.templates:1001
|
||||
msgid "A certificate is available and the files are uploaded on the server"
|
||||
msgid "I want to use my own certificate"
|
||||
msgstr ""
|
||||
|
||||
#. Type: select
|
||||
|
||||
71
doc/api.md
71
doc/api.md
@@ -10,37 +10,56 @@ To embed Jitsi Meet in your application you need to add the Jitsi Meet API libra
|
||||
<script src="https://meet.jit.si/external_api.js"></script>
|
||||
```
|
||||
|
||||
The next step for embedding Jitsi Meet is to create the Jitsi Meet API object:
|
||||
|
||||
```javascript
|
||||
<script>
|
||||
var domain = "meet.jit.si";
|
||||
var room = "JitsiMeetAPIExample";
|
||||
var width = 700;
|
||||
var height = 700;
|
||||
var api = new JitsiMeetExternalAPI(domain, room, width, height);
|
||||
</script>
|
||||
```
|
||||
|
||||
You can use the above lines to indicate where exactly you want the Jitsi Meet conference to be placed in your HTML code,
|
||||
or you can specify the parent HTML element for the Jitsi Meet conference in the `JitsiMeetExternalAPI`
|
||||
constructor:
|
||||
## API
|
||||
|
||||
### `api = new JitsiMeetExternalAPI(domain, room, [width], [height], [htmlElement], [configOverwite], [interfaceConfigOverwrite], [noSsl], [jwt])`
|
||||
|
||||
The next step for embedding Jitsi Meet is to create the Jitsi Meet API object.
|
||||
Its constructor gets a number of options:
|
||||
|
||||
* **domain**: domain used to build the conference URL, "meet.jit.si" for
|
||||
example.
|
||||
* **room**: name of the room to join.
|
||||
* **width**: (optional) width for the iframe which will be created.
|
||||
* **height**: (optional) height for the iframe which will be created.
|
||||
* **htmlElement**: (optional) HTL DOM Element where the iframe will be added as
|
||||
a child.
|
||||
* **configOverwite**: (optional) JS object with overrides for options defined in
|
||||
[config.js].
|
||||
* **interfaceConfigOverwrite**: (optional) JS object with overrides for options
|
||||
defined in [interface_config.js].
|
||||
* **noSsl**: (optional, defaults to true) Boolean indicating if the server
|
||||
should be contacted using HTTP or HTTPS.
|
||||
* **jwt**: (optional) [JWT](https://jwt.io/) token.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
var domain = "meet.jit.si";
|
||||
var room = "JitsiMeetAPIExample";
|
||||
var width = 700;
|
||||
var height = 700;
|
||||
var htmlElement = document.querySelector('#meet');
|
||||
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement);
|
||||
```
|
||||
|
||||
If you don't specify the room the user will enter in new conference with a 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:
|
||||
You can overwrite options set in [config.js] and [interface_config.js].
|
||||
For example, to enable the filmstrip-only interface mode, you can use:
|
||||
|
||||
```javascript
|
||||
var configOverwrite = {disableSimulcast: true};
|
||||
var interfaceConfigOverwrite = {filmStripOnly: true};
|
||||
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, configOverwrite, interfaceConfigOverwrite);
|
||||
var api = new JitsiMeetExternalAPI(domain, room, width, height, undefined, undefined, interfaceConfigOverwrite);
|
||||
```
|
||||
|
||||
## Controlling the embedded Jitsi Meet Conference
|
||||
You can also pass a jwt token to Jitsi Meet:
|
||||
|
||||
```javascript
|
||||
var jwt = "<jwt_token>";
|
||||
var noSsl = false;
|
||||
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, configOverwrite, interfaceConfigOverwrite, noSsl, jwt);
|
||||
```
|
||||
|
||||
### Controlling the embedded Jitsi Meet Conference
|
||||
|
||||
You can control the embedded Jitsi Meet conference using the `JitsiMeetExternalAPI` object by using `executeCommand`:
|
||||
|
||||
@@ -65,7 +84,7 @@ api.executeCommand('toggleAudio')
|
||||
api.executeCommand('toggleVideo')
|
||||
```
|
||||
|
||||
* **toggleFilmStrip** - Hides / shows the film strip. No arguments are required.
|
||||
* **toggleFilmStrip** - Hides / shows the filmstrip. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleFilmStrip')
|
||||
```
|
||||
@@ -104,12 +123,13 @@ You can also execute multiple commands using the `executeCommands` method:
|
||||
```javascript
|
||||
api.executeCommands(commands)
|
||||
```
|
||||
The `commands` parameter is an object with the names of the commands as keys and the arguments for the commands asvalues:
|
||||
The `commands` parameter is an object with the names of the commands as keys and the arguments for the commands as values:
|
||||
```javascript
|
||||
api.executeCommands({displayName: ['nickname'], toggleAudio: []});
|
||||
```
|
||||
|
||||
You can add event listeners to the embedded Jitsi Meet using the `addEventListener` method.
|
||||
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods (`addListener` or `on`).**
|
||||
```javascript
|
||||
api.addEventListener(event, listener)
|
||||
```
|
||||
@@ -179,6 +199,7 @@ changes. The listener will receive an object with the following structure:
|
||||
You can also add multiple event listeners by using `addEventListeners`.
|
||||
This method requires one argument of type Object. The object argument must
|
||||
have the names of the events as keys and the listeners of the events as values.
|
||||
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods.**
|
||||
|
||||
```javascript
|
||||
function incomingMessageListener(object)
|
||||
@@ -197,12 +218,13 @@ api.addEventListeners({
|
||||
```
|
||||
|
||||
If you want to remove a listener you can use `removeEventListener` method with argument the name of the event.
|
||||
|
||||
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods( `removeListener`).**
|
||||
```javascript
|
||||
api.removeEventListener("incomingMessage");
|
||||
```
|
||||
|
||||
If you want to remove more than one event you can use `removeEventListeners` method with an Array with the names of the events as an argument.
|
||||
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods.**
|
||||
```javascript
|
||||
api.removeEventListeners(["incomingMessage", "outgoingMessageListener"]);
|
||||
```
|
||||
@@ -221,3 +243,4 @@ NOTE: It's a good practice to remove the conference before the page is unloaded.
|
||||
|
||||
[config.js]: https://github.com/jitsi/jitsi-meet/blob/master/config.js
|
||||
[interface_config.js]: https://github.com/jitsi/jitsi-meet/blob/master/interface_config.js
|
||||
[EventEmitter]: https://nodejs.org/api/events.html
|
||||
|
||||
@@ -82,3 +82,12 @@
|
||||
imports in other files should use the same name. Don't define the class
|
||||
`Registry` in ReducerRegistry.js and then import it as `Reducers` in other
|
||||
files.
|
||||
|
||||
* The names of global constants (including ES6 module-global constants) should
|
||||
be written in uppercase with underscores to separate words. For example,
|
||||
`BACKGROUND_COLOR`.
|
||||
|
||||
* The underscore character at the beginning of a name signals that the
|
||||
respective variable, function, property is non-public i.e. private, protected,
|
||||
or internal. In contrast, the lack of an underscore at the beginning of a name
|
||||
signals public API.
|
||||
|
||||
@@ -23,6 +23,8 @@ VirtualHost "jitmeet.example.com"
|
||||
"ping"; -- Enable mod_ping
|
||||
}
|
||||
|
||||
c2s_require_encryption = false
|
||||
|
||||
Component "conference.jitmeet.example.com" "muc"
|
||||
--modules_enabled = { "token_verification" }
|
||||
admins = { "focusUser@auth.jitmeet.example.com" }
|
||||
|
||||
@@ -20,7 +20,7 @@ server {
|
||||
|
||||
root /usr/share/jitsi-meet;
|
||||
index index.html index.htm;
|
||||
error_page 404 /404.html;
|
||||
error_page 404 /static/404.html;
|
||||
|
||||
location /config.js {
|
||||
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
ErrorDocument 404 /static/404.html
|
||||
|
||||
Alias "/config.js" "/etc/jitsi/meet/jitsi-meet.example.com-config.js"
|
||||
<Location /config.js>
|
||||
Require all granted
|
||||
|
||||
@@ -179,6 +179,8 @@ VirtualHost "jitsi.example.com"
|
||||
certificate = "/var/lib/prosody/jitsi.example.com.crt";
|
||||
}
|
||||
|
||||
c2s_require_encryption = false
|
||||
|
||||
------ Components ------
|
||||
-- You can specify components to add hosts that provide special services,
|
||||
-- like multi-user conferences, and transports.
|
||||
|
||||
@@ -6,6 +6,34 @@ change references to that to match your host, and generate some passwords for
|
||||
|
||||
There are also some complete [example config files](https://github.com/jitsi/jitsi-meet/tree/master/doc/example-config-files/) available, mentioned in each section.
|
||||
|
||||
## Network description
|
||||
|
||||
This how the network look like:
|
||||
```
|
||||
+ +
|
||||
| |
|
||||
| |
|
||||
v |
|
||||
443 |
|
||||
+-------+ |
|
||||
| | |
|
||||
| NginX | |
|
||||
| | |
|
||||
+--+-+--+ |
|
||||
| | |
|
||||
+------------+ | | +--------------+ |
|
||||
| | | | | | |
|
||||
| jitsi-meet +<---+ +--->+ prosody/xmpp | |
|
||||
| |files 5280 | | |
|
||||
+------------+ +--------------+ v
|
||||
5222,5347^ ^5347 4443
|
||||
+--------+ | | +-------------+
|
||||
| | | | | |
|
||||
| jicofo +----^ ^----+ videobridge |
|
||||
| | | |
|
||||
+--------+ +-------------+
|
||||
```
|
||||
|
||||
## Install prosody
|
||||
```sh
|
||||
apt-get install prosody
|
||||
@@ -27,6 +55,7 @@ VirtualHost "jitsi.example.com"
|
||||
"bosh";
|
||||
"pubsub";
|
||||
}
|
||||
c2s_require_encryption = false
|
||||
```
|
||||
- add domain with authentication for conference focus user:
|
||||
```
|
||||
@@ -76,7 +105,9 @@ Add a new file `jitsi.example.com` in `/etc/nginx/sites-available` (see also the
|
||||
server_names_hash_bucket_size 64;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen 443;
|
||||
# tls configuration that is not covered in this guide
|
||||
# we recommend the use of https://certbot.eff.org/
|
||||
server_name jitsi.example.com;
|
||||
# set the root
|
||||
root /srv/jitsi.example.com;
|
||||
|
||||
BIN
fonts/jitsi.eot
Normal file → Executable file
BIN
fonts/jitsi.eot
Normal file → Executable file
Binary file not shown.
1
fonts/jitsi.svg
Normal file → Executable file
1
fonts/jitsi.svg
Normal file → Executable file
@@ -11,6 +11,7 @@
|
||||
<glyph unicode="" glyph-name="recDisable" horiz-adv-x="1140" d="M1123.444 1003.015c-23.593 26.481-64.131 28.989-90.74 5.395l-1008.269-893.436c-26.609-23.468-28.991-64.131-5.46-90.676 12.674-14.306 30.308-21.649 48.126-21.649 15.123 0 30.372 5.401 42.544 16.195l130.045 115.22c90.743-81.844 210.569-132.165 342.473-132.101 282.816 0.061 510.913 227.969 511.287 510.972 0.126 109.934-34.682 211.367-93.499 294.72l118.088 104.625c26.483 23.526 28.997 64.129 5.404 90.735zM944.422 513.818c0.128-200.922-161.896-363.201-362.509-362.952-87.56 0.123-167.573 31.151-230.061 82.569l331.277 293.509v-73.176c1.071-60.993 32.696-92.18 94.944-93.692 61.997 1.512 93.686 32.763 95.131 93.756v41.096h-72.227v-47.499c0.251-4.642-0.564-10.607-2.511-17.949-1.25-3.261-3.448-6.020-6.525-8.093-3.197-2.572-7.845-3.828-13.868-3.828-10.543 0.31-17.132 4.268-19.827 11.921-1.068 3.512-1.947 6.905-2.508 10.163-0.254 2.887-0.377 5.532-0.377 7.786v143.511l42.477 37.634c0.215-0.432 0.452-0.851 0.63-1.303 1.947-6.467 2.762-12.799 2.511-19.076v-36.772h72.227v30.121c-0.246 31.245-9.086 54.699-26.363 70.447l40.711 36.069c35.787-56.055 56.803-122.585 56.867-194.244zM239.795 395.47c-12.613 37.023-19.827 76.557-19.827 117.913-0.19 200.236 161.584 362.009 361.945 362.135 56.853 0 110.313-13.302 158.133-36.398l117.846 104.421c-79.444 50.952-173.758 80.817-275.292 80.948-283.377 0.181-511.354-227.729-511.789-511.675-0.126-79.567 18.636-154.679 51.137-221.882l117.848 104.538zM388.576 690.020h-97.514v-249.057l72.23 64.070v0.689h0.815l117.72 104.418c0 0.564 0.123 0.94 0.123 1.509 0.753 53.898-30.369 80.069-93.374 78.37zM405.959 625.517c1.942-2.767 3.074-6.469 3.323-11.112 0.312-4.452 0.438-9.6 0.438-15.246 0.251-10.916-0.689-19.83-2.949-26.985-2.952-7.594-10.983-11.357-24.159-11.357h-19.325v74.043h15.31c7.842 0 13.865-0.683 18.072-2.19 4.397-1.573 7.468-3.953 9.29-7.153z" />
|
||||
<glyph unicode="" glyph-name="recEnable" horiz-adv-x="1142" d="M581.278 1025.708c284.857-0.19 514.807-230.517 514.427-514.997-0.378-285.047-230.073-514.553-514.869-514.615-284.541-0.062-515.311 230.517-514.933 514.422 0.439 285.936 230.009 515.439 515.375 515.19zM580.579 875.756c-201.764-0.123-364.666-163.032-364.478-364.663 0-202.018 162.524-364.735 364.478-364.984 202.018-0.316 365.174 163.030 365.048 365.423-0.252 201.767-163.156 364.35-365.048 364.224zM287.698 688.907h98.196c63.442 1.767 94.785-24.518 94.027-78.863 0.254-19.081-2.211-34.882-7.456-47.521-6.005-12.508-18.706-21.988-38.167-28.181v-0.819c28.373-6.259 43.031-23.573 43.981-51.946v-57.689c0-11.247 0.254-22.813 0.758-34.756 0.819-12.005 3.033-20.979 6.696-27.043h-71.846c-3.727 6.064-6.128 15.038-7.14 27.043-1.012 11.943-1.454 23.509-1.138 34.756v52.321c0 9.603-2.214 16.553-6.573 20.979-4.675 4.107-12.701 6.19-24.012 6.19h-14.599v-141.291h-72.73v326.82zM360.428 558.861h19.463c13.271 0 21.359 3.794 24.331 11.375 2.276 7.204 3.221 16.304 2.969 27.171 0 5.815-0.126 10.867-0.442 15.418-0.252 4.675-1.392 8.404-3.352 11.247-1.831 3.157-4.926 5.561-9.352 7.14-4.233 1.454-10.299 2.211-18.2 2.211h-15.418v-74.564zM498.372 688.907h162.082v-62.687h-89.35v-65.587h78.103v-62.685h-78.103v-73.11h92.822v-62.749h-165.557v326.818zM682.507 599.999c0.316 31.782 9.416 55.542 27.425 71.407 17.44 15.29 40.185 22.936 68.181 22.936 28.247 0 51.119-7.646 68.623-23 17.82-15.798 26.92-39.623 27.171-71.407v-30.333h-72.73v37.031c0.254 6.192-0.57 12.639-2.527 19.209-1.264 3.157-3.475 5.938-6.573 8.214-3.221 1.515-7.898 2.404-13.964 2.404-10.615-0.316-17.249-3.855-19.967-10.618-2.211-6.573-3.223-13.017-2.907-19.209v-161.956c0-2.273 0.126-4.865 0.38-7.772 0.568-3.411 1.454-6.824 2.527-10.233 2.717-7.775 9.352-11.756 19.967-12.007 6.067 0 10.744 1.261 13.964 3.791 3.098 2.15 5.309 4.867 6.573 8.216 1.96 7.33 2.782 13.33 2.527 18.007v47.837h72.73v-41.328c-1.451-61.547-33.364-93.015-95.794-94.469-62.685 1.454-94.53 32.922-95.607 94.343v148.937z" />
|
||||
<glyph unicode="" glyph-name="connection" horiz-adv-x="1444" d="M3.881 210.835h220.26v-210.835h-220.26v210.835zM308.817 414.143h220.27v-414.143h-220.27v414.143zM613.764 617.412h220.268v-617.412h-220.268v617.412zM918.685 820.715h220.265v-820.715h-220.265v820.715zM1223.629 1024h220.263v-1024h-220.263v1024z" />
|
||||
<glyph unicode="" glyph-name="dialpad" horiz-adv-x="1026" d="M74.418 881.299h239.304v-228.491h-239.304v228.491zM393.455 881.299h239.304v-228.491h-239.304v228.491zM712.494 881.299h239.263v-228.491h-239.263v228.491zM74.418 562.265h239.304v-228.555h-239.304v228.555zM393.455 562.265h239.304v-228.555h-239.304v228.555zM712.494 562.265h239.263v-228.555h-239.263v228.555zM74.418 243.166h239.304v-228.465h-239.304v228.465zM393.455 243.166h239.304v-228.465h-239.304v228.465zM712.494 243.166h239.263v-228.465h-239.263v228.465z" />
|
||||
<glyph unicode="" glyph-name="connection-lost" horiz-adv-x="1414" d="M0 299.153h196.337v-187.951h-196.337v187.951zM271.842 480.372h196.337v-369.169h-196.337v369.169zM543.656 661.562h196.337v-550.36h-196.337v550.36zM815.47 842.766v-731.564h119.56c-14.589 33.025-23.125 71.503-23.232 111.943 0.132 86.42 38.697 163.851 99.656 216.468l0.348 403.153h-196.332zM1087.292 1024v-533.672c28.874 10.572 62.222 16.73 97.009 16.825 35.717-0.129 69.823-6.614 101.322-18.371l-1.999 535.218h-196.332zM1192.868 439.852c-0.009 0-0.020 0-0.031 0-122.247 0-221.351-98.447-221.372-219.896 0-0.007 0-0.014 0-0.021 0-121.467 99.111-219.935 221.372-219.935 0.011 0 0.021 0 0.032 0 122.248 0.014 221.345 98.477 221.345 219.935 0 0.007 0 0.013 0 0.020-0.021 121.441-99.11 219.883-221.345 219.897zM1194.706 372.607c87.601-0.006 158.614-69.787 158.614-155.866 0-0.006 0-0.012 0-0.019-0.022-86.062-71.026-155.822-158.614-155.828-87.588 0.006-158.593 69.766-158.615 155.826 0 0.007 0 0.014 0 0.020 0 86.079 71.013 155.86 158.613 155.866zM1286.795 355.682l48.348-52.528-236.375-217.567-48.348 52.528 236.375 217.567z" />
|
||||
<glyph unicode="" glyph-name="avatar" d="M512 204c106 0 200 56 256 138-2 84-172 132-256 132-86 0-254-48-256-132 56-82 150-138 256-138zM512 810c-70 0-128-58-128-128s58-128 128-128 128 58 128 128-58 128-128 128zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
|
||||
<glyph unicode="" glyph-name="download" d="M726 470h-128v170h-172v-170h-128l214-214zM826 596c110-8 198-100 198-212 0-118-96-214-214-214h-554c-142 0-256 114-256 256 0 132 100 240 228 254 54 102 160 174 284 174 156 0 284-110 314-258z" />
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
BIN
fonts/jitsi.ttf
Normal file → Executable file
BIN
fonts/jitsi.ttf
Normal file → Executable file
Binary file not shown.
BIN
fonts/jitsi.woff
Normal file → Executable file
BIN
fonts/jitsi.woff
Normal file → Executable file
Binary file not shown.
@@ -127,9 +127,9 @@
|
||||
'error', loadErrHandler, true /* capture phase type of listener */);
|
||||
</script>
|
||||
<script><!--#include virtual="/config.js" --></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script src="utils.js?v=1"></script>
|
||||
<script src="static/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="libs/do_external_connect.min.js?v=1"></script>
|
||||
<script><!--#include virtual="/interface_config.js" --></script>
|
||||
<script><!--#include virtual="/logging_config.js" --></script>
|
||||
<script src="libs/lib-jitsi-meet.min.js?v=139"></script>
|
||||
|
||||
@@ -38,7 +38,7 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
|
||||
//main toolbar
|
||||
'microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'hangup',
|
||||
//extended toolbar
|
||||
'profile', 'contacts', 'chat', 'recording', 'etherpad', 'sharedvideo', 'sip', 'dialpad', 'settings', 'raisehand', 'filmstrip'], // jshint ignore:line
|
||||
'profile', 'contacts', 'chat', 'recording', 'etherpad', 'sharedvideo', 'sip', 'settings', 'raisehand', 'filmstrip'], // jshint ignore:line
|
||||
/**
|
||||
* Main Toolbar Buttons
|
||||
* All of them should be in TOOLBAR_BUTTONS
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.3</string>
|
||||
<string>1.4</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@@ -78,6 +78,8 @@
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleLightContent</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
|
||||
24
ios/app/Proximity.m
Normal file
24
ios/app/Proximity.m
Normal file
@@ -0,0 +1,24 @@
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface Proximity : NSObject<RCTBridgeModule>
|
||||
@end
|
||||
|
||||
@implementation Proximity
|
||||
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
/**
|
||||
* Enables / disables the proximity sensor monitoring. On iOS enabling the
|
||||
* proximity sensor automatically dims the screen and disables touch controls,
|
||||
* so there is nothing else to do (unlike on Android)!
|
||||
*
|
||||
* @param enabled {@code YES} to enable proximity (sensor) monitoring;
|
||||
* {@code NO}, otherwise.
|
||||
*/
|
||||
RCT_EXPORT_METHOD(setEnabled:(BOOL)enabled) {
|
||||
[[UIDevice currentDevice] setProximityMonitoringEnabled:enabled];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -14,6 +14,7 @@
|
||||
00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; };
|
||||
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; };
|
||||
0B42DFAE1E2FD90700111B12 /* AudioMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B42DFAD1E2FD90700111B12 /* AudioMode.m */; };
|
||||
0B96CAF11E8CF0E8005F348C /* Proximity.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B96CAF01E8CF0E8005F348C /* Proximity.m */; };
|
||||
133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; };
|
||||
139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; };
|
||||
139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; };
|
||||
@@ -80,6 +81,13 @@
|
||||
remoteGlobalIDString = 832C81801AAF6DEF007FA2F7;
|
||||
remoteInfo = RCTVibration;
|
||||
};
|
||||
0B50D60B1EA7891100B34818 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5B09C20C78C74A548AAAC1FA /* KCKeepAwake.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 325F5AB71E6763EC00E6CDE4;
|
||||
remoteInfo = "KCKeepAwake-tvOS";
|
||||
};
|
||||
0B8752851E26E54A004C5CAB /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5B09C20C78C74A548AAAC1FA /* KCKeepAwake.xcodeproj */;
|
||||
@@ -273,6 +281,7 @@
|
||||
00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = "<group>"; };
|
||||
0965153BB98645B4A8B6AA10 /* RNBackgroundTimer.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNBackgroundTimer.xcodeproj; path = "../node_modules/react-native-background-timer/ios/RNBackgroundTimer.xcodeproj"; sourceTree = "<group>"; };
|
||||
0B42DFAD1E2FD90700111B12 /* AudioMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AudioMode.m; path = app/AudioMode.m; sourceTree = "<group>"; };
|
||||
0B96CAF01E8CF0E8005F348C /* Proximity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Proximity.m; path = app/Proximity.m; sourceTree = "<group>"; };
|
||||
0EA8C046B2BF46279796F07D /* libKCKeepAwake.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libKCKeepAwake.a; sourceTree = "<group>"; };
|
||||
139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = "<group>"; };
|
||||
139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = "<group>"; };
|
||||
@@ -392,6 +401,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0B8752861E26E54A004C5CAB /* libKCKeepAwake.a */,
|
||||
0B50D60C1EA7891100B34818 /* libKCKeepAwake-tvOS.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -426,6 +436,7 @@
|
||||
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
|
||||
13B07FB71A68108700A75B9A /* main.m */,
|
||||
B3A9D0241E0481E10009343D /* POSIX.m */,
|
||||
0B96CAF01E8CF0E8005F348C /* Proximity.m */,
|
||||
);
|
||||
name = app;
|
||||
sourceTree = "<group>";
|
||||
@@ -725,6 +736,13 @@
|
||||
remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
0B50D60C1EA7891100B34818 /* libKCKeepAwake-tvOS.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = "libKCKeepAwake-tvOS.a";
|
||||
remoteRef = 0B50D60B1EA7891100B34818 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
0B8752861E26E54A004C5CAB /* libKCKeepAwake.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
@@ -945,6 +963,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B3A9D0251E0481E10009343D /* POSIX.m in Sources */,
|
||||
0B96CAF11E8CF0E8005F348C /* Proximity.m in Sources */,
|
||||
0B42DFAE1E2FD90700111B12 /* AudioMode.m in Sources */,
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
||||
|
||||
19
lang/languages-eo.json
Normal file
19
lang/languages-eo.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"en": "",
|
||||
"bg": "",
|
||||
"de": "",
|
||||
"es": "",
|
||||
"fr": "",
|
||||
"hy": "",
|
||||
"it": "",
|
||||
"oc": "",
|
||||
"pl": "",
|
||||
"ptBR": "",
|
||||
"ru": "",
|
||||
"sk": "",
|
||||
"sl": "",
|
||||
"sv": "",
|
||||
"tr": "",
|
||||
"zhCN": "",
|
||||
"nb": ""
|
||||
}
|
||||
@@ -13,5 +13,7 @@
|
||||
"sk": "Eslovac",
|
||||
"sl": "Eslovèn",
|
||||
"sv": "Suedés",
|
||||
"tr": "Turc"
|
||||
"tr": "Turc",
|
||||
"zhCN": "Chinés (China)",
|
||||
"nb": "Norvegian Bokmål"
|
||||
}
|
||||
@@ -6,12 +6,14 @@
|
||||
"fr": "Francês",
|
||||
"hy": "Armênio",
|
||||
"it": "Italiano",
|
||||
"oc": "Provençal",
|
||||
"oc": "Occitano",
|
||||
"pl": "Polonês",
|
||||
"ptBR": "Português (Brasil)",
|
||||
"ru": "Russo",
|
||||
"sk": "Eslovaco",
|
||||
"sl": "Esloveno",
|
||||
"sv": "Sueco",
|
||||
"tr": "Turco"
|
||||
"tr": "Turco",
|
||||
"zhCN": "Chinês (China)",
|
||||
"nb": "Bokmal norueguês"
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
{
|
||||
"en": "",
|
||||
"bg": "",
|
||||
"de": "",
|
||||
"es": "",
|
||||
"fr": "",
|
||||
"hy": "",
|
||||
"it": "",
|
||||
"oc": "",
|
||||
"pl": "",
|
||||
"ptBR": "",
|
||||
"ru": "",
|
||||
"sk": "",
|
||||
"sl": "",
|
||||
"sv": "",
|
||||
"tr": ""
|
||||
"en": "英语",
|
||||
"bg": "保加利亚语",
|
||||
"de": "德语",
|
||||
"es": "西班牙语",
|
||||
"fr": "法语",
|
||||
"hy": "亚美尼亚语",
|
||||
"it": "意大利语",
|
||||
"oc": "欧西坦语",
|
||||
"pl": "波兰语",
|
||||
"ptBR": "葡萄牙语(巴西)",
|
||||
"ru": "俄语",
|
||||
"sk": "斯洛伐克语",
|
||||
"sl": "斯洛文尼亚语",
|
||||
"sv": "瑞典语",
|
||||
"tr": "土耳其语",
|
||||
"zhCN": "中文(中国)",
|
||||
"nb": "",
|
||||
"eo": ""
|
||||
}
|
||||
@@ -15,5 +15,6 @@
|
||||
"sv": "Swedish",
|
||||
"tr": "Turkish",
|
||||
"zhCN": "Chinese (China)",
|
||||
"nb": "Norwegian Bokmal"
|
||||
"nb": "Norwegian Bokmal",
|
||||
"eo": "Esperanto"
|
||||
}
|
||||
|
||||
374
lang/main-eo.json
Normal file
374
lang/main-eo.json
Normal file
@@ -0,0 +1,374 @@
|
||||
{
|
||||
"contactlist": "",
|
||||
"addParticipants": "",
|
||||
"roomLocked": "",
|
||||
"roomUnlocked": "",
|
||||
"passwordSetRemotely": "",
|
||||
"connectionsettings": "",
|
||||
"poweredby": "",
|
||||
"feedback": "",
|
||||
"inviteUrlDefaultMsg": "",
|
||||
"me": "",
|
||||
"speaker": "",
|
||||
"raisedHand": "",
|
||||
"defaultNickname": "",
|
||||
"defaultLink": "",
|
||||
"callingName": "",
|
||||
"userMedia": {
|
||||
"react-nativeGrantPermissions": "",
|
||||
"chromeGrantPermissions": "",
|
||||
"androidGrantPermissions": "",
|
||||
"firefoxGrantPermissions": "",
|
||||
"operaGrantPermissions": "",
|
||||
"iexplorerGrantPermissions": "",
|
||||
"safariGrantPermissions": "",
|
||||
"nwjsGrantPermissions": ""
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
"keyboardShortcuts": "",
|
||||
"raiseHand": "",
|
||||
"pushToTalk": "",
|
||||
"toggleScreensharing": "",
|
||||
"toggleFilmstrip": "",
|
||||
"toggleShortcuts": "",
|
||||
"focusLocal": "",
|
||||
"focusRemote": "",
|
||||
"toggleChat": "",
|
||||
"mute": "",
|
||||
"fullScreen": "",
|
||||
"videoMute": ""
|
||||
},
|
||||
"welcomepage": {
|
||||
"disable": "",
|
||||
"feature1": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"feature2": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"feature3": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"feature4": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"feature5": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"feature6": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"feature7": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"feature8": {
|
||||
"content": "",
|
||||
"title": ""
|
||||
},
|
||||
"go": "",
|
||||
"join": "",
|
||||
"privacy": "",
|
||||
"roomname": "",
|
||||
"roomnamePlaceHolder": "",
|
||||
"sendFeedback": "",
|
||||
"terms": ""
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": "",
|
||||
"title": ""
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"title": "",
|
||||
"rejoinKeyTitle": ""
|
||||
},
|
||||
"toolbar": {
|
||||
"mute": "",
|
||||
"videomute": "",
|
||||
"authenticate": "",
|
||||
"lock": "",
|
||||
"invite": "",
|
||||
"chat": "",
|
||||
"etherpad": "",
|
||||
"sharedvideo": "",
|
||||
"sharescreen": "",
|
||||
"fullscreen": "",
|
||||
"sip": "",
|
||||
"Settings": "",
|
||||
"hangup": "",
|
||||
"login": "",
|
||||
"logout": "",
|
||||
"dialpad": "",
|
||||
"sharedVideoMutedPopup": "",
|
||||
"micMutedPopup": "",
|
||||
"talkWhileMutedPopup": "",
|
||||
"unableToUnmutePopup": "",
|
||||
"cameraDisabled": "",
|
||||
"micDisabled": "",
|
||||
"filmstrip": "",
|
||||
"profile": "",
|
||||
"raiseHand": ""
|
||||
},
|
||||
"unsupportedBrowser": {
|
||||
"appInstalled": "",
|
||||
"appNotInstalled": "",
|
||||
"downloadApp": "",
|
||||
"joinConversation": "",
|
||||
"startConference": ""
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "",
|
||||
"filmstrip": "",
|
||||
"contactlist": ""
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "",
|
||||
"popover": ""
|
||||
},
|
||||
"messagebox": ""
|
||||
},
|
||||
"settings": {
|
||||
"title": "",
|
||||
"update": "",
|
||||
"name": "",
|
||||
"startAudioMuted": "",
|
||||
"startVideoMuted": "",
|
||||
"selectCamera": "",
|
||||
"selectMic": "",
|
||||
"selectAudioOutput": "",
|
||||
"followMe": "",
|
||||
"noDevice": "",
|
||||
"noPermission": "",
|
||||
"cameraAndMic": "",
|
||||
"moderator": "",
|
||||
"password": "",
|
||||
"audioVideo": "",
|
||||
"setPasswordLabel": ""
|
||||
},
|
||||
"profile": {
|
||||
"title": "",
|
||||
"setDisplayNameLabel": "",
|
||||
"setEmailLabel": "",
|
||||
"setEmailInput": ""
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "",
|
||||
"moderator": "",
|
||||
"videomute": "",
|
||||
"mute": "",
|
||||
"kick": "",
|
||||
"muted": "",
|
||||
"domute": "",
|
||||
"flip": "",
|
||||
"remoteControl": ""
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "",
|
||||
"bitrate": "",
|
||||
"packetloss": "",
|
||||
"resolution": "",
|
||||
"less": "",
|
||||
"more": "",
|
||||
"address": "",
|
||||
"remoteport": "",
|
||||
"remoteport_plural": "",
|
||||
"localport": "",
|
||||
"localport_plural": "",
|
||||
"localaddress": "",
|
||||
"localaddress_plural": "",
|
||||
"remoteaddress": "",
|
||||
"remoteaddress_plural": "",
|
||||
"transport": "",
|
||||
"bandwidth": "",
|
||||
"na": ""
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "",
|
||||
"moderator": "",
|
||||
"connected": "",
|
||||
"somebody": "",
|
||||
"me": "",
|
||||
"focus": "",
|
||||
"focusFail": "",
|
||||
"grantedTo": "",
|
||||
"grantedToUnknown": "",
|
||||
"muted": "",
|
||||
"mutedTitle": "",
|
||||
"raisedHand": ""
|
||||
},
|
||||
"dialog": {
|
||||
"add": "",
|
||||
"kickMessage": "",
|
||||
"popupError": "",
|
||||
"passwordErrorTitle": "",
|
||||
"passwordError": "",
|
||||
"passwordError2": "",
|
||||
"connectError": "",
|
||||
"connectErrorWithMsg": "",
|
||||
"incorrectPassword": "",
|
||||
"connecting": "",
|
||||
"copy": "",
|
||||
"error": "",
|
||||
"roomLocked": "",
|
||||
"addPassword": "",
|
||||
"createPassword": "",
|
||||
"detectext": "",
|
||||
"failtoinstall": "",
|
||||
"failedpermissions": "",
|
||||
"conferenceReloadTitle": "",
|
||||
"conferenceReloadMsg": "",
|
||||
"conferenceDisconnectTitle": "",
|
||||
"conferenceDisconnectMsg": "",
|
||||
"reconnectNow": "",
|
||||
"conferenceReloadTimeLeft": "",
|
||||
"maxUsersLimitReached": "",
|
||||
"lockTitle": "",
|
||||
"lockMessage": "",
|
||||
"warning": "",
|
||||
"passwordNotSupported": "",
|
||||
"internalErrorTitle": "",
|
||||
"internalError": "",
|
||||
"unableToSwitch": "",
|
||||
"SLDFailure": "",
|
||||
"SRDFailure": "",
|
||||
"oops": "",
|
||||
"currentPassword": "",
|
||||
"passwordLabel": "",
|
||||
"defaultError": "",
|
||||
"passwordRequired": "",
|
||||
"Ok": "",
|
||||
"done": "",
|
||||
"Remove": "",
|
||||
"removePassword": "",
|
||||
"shareVideoTitle": "",
|
||||
"shareVideoLinkError": "",
|
||||
"removeSharedVideoTitle": "",
|
||||
"removeSharedVideoMsg": "",
|
||||
"alreadySharedVideoMsg": "",
|
||||
"WaitingForHost": "",
|
||||
"WaitForHostMsg": "",
|
||||
"IamHost": "",
|
||||
"Cancel": "",
|
||||
"Submit": "",
|
||||
"retry": "",
|
||||
"logoutTitle": "",
|
||||
"logoutQuestion": "",
|
||||
"sessTerminated": "",
|
||||
"hungUp": "",
|
||||
"joinAgain": "",
|
||||
"Share": "",
|
||||
"Save": "",
|
||||
"recording": "",
|
||||
"recordingToken": "",
|
||||
"Dial": "",
|
||||
"sipMsg": "",
|
||||
"passwordCheck": "",
|
||||
"passwordMsg": "",
|
||||
"shareLink": "",
|
||||
"settings1": "",
|
||||
"settings2": "",
|
||||
"settings3": "",
|
||||
"yourPassword": "",
|
||||
"Back": "",
|
||||
"serviceUnavailable": "",
|
||||
"gracefulShutdown": "",
|
||||
"Yes": "",
|
||||
"reservationError": "",
|
||||
"reservationErrorMsg": "",
|
||||
"password": "",
|
||||
"userPassword": "",
|
||||
"token": "",
|
||||
"tokenAuthFailedTitle": "",
|
||||
"tokenAuthFailed": "",
|
||||
"displayNameRequired": "",
|
||||
"enterDisplayName": "",
|
||||
"extensionRequired": "",
|
||||
"firefoxExtensionPrompt": "",
|
||||
"rateExperience": "",
|
||||
"feedbackHelp": "",
|
||||
"feedbackQuestion": "",
|
||||
"thankYou": "",
|
||||
"sorryFeedback": "",
|
||||
"liveStreaming": "",
|
||||
"streamKey": "",
|
||||
"startLiveStreaming": "",
|
||||
"stopStreamingWarning": "",
|
||||
"stopRecordingWarning": "",
|
||||
"stopLiveStreaming": "",
|
||||
"stopRecording": "",
|
||||
"doNotShowWarningAgain": "",
|
||||
"doNotShowMessageAgain": "",
|
||||
"permissionDenied": "",
|
||||
"screenSharingPermissionDeniedError": "",
|
||||
"micErrorPresent": "",
|
||||
"cameraErrorPresent": "",
|
||||
"cameraUnsupportedResolutionError": "",
|
||||
"cameraUnknownError": "",
|
||||
"cameraPermissionDeniedError": "",
|
||||
"cameraNotFoundError": "",
|
||||
"cameraConstraintFailedError": "",
|
||||
"micUnknownError": "",
|
||||
"micPermissionDeniedError": "",
|
||||
"micNotFoundError": "",
|
||||
"micConstraintFailedError": "",
|
||||
"micNotSendingData": "",
|
||||
"cameraNotSendingData": "",
|
||||
"goToStore": "",
|
||||
"externalInstallationTitle": "",
|
||||
"externalInstallationMsg": "",
|
||||
"muteParticipantTitle": "",
|
||||
"muteParticipantBody": "",
|
||||
"muteParticipantButton": "",
|
||||
"remoteControlTitle": "",
|
||||
"remoteControlDeniedMessage": "",
|
||||
"remoteControlAllowedMessage": "",
|
||||
"remoteControlErrorMessage": "",
|
||||
"remoteControlStopMessage": ""
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": "",
|
||||
"subject": "",
|
||||
"body": "",
|
||||
"and": ""
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "",
|
||||
"CONNECTING": "",
|
||||
"RECONNECTING": "",
|
||||
"CONNFAIL": "",
|
||||
"AUTHENTICATING": "",
|
||||
"AUTHFAIL": "",
|
||||
"CONNECTED": "",
|
||||
"DISCONNECTED": "",
|
||||
"DISCONNECTING": "",
|
||||
"ATTACHED": ""
|
||||
},
|
||||
"recording": {
|
||||
"pending": "",
|
||||
"on": "",
|
||||
"off": "",
|
||||
"failedToStart": "",
|
||||
"buttonTooltip": "",
|
||||
"error": "",
|
||||
"unavailable": ""
|
||||
},
|
||||
"liveStreaming": {
|
||||
"pending": "",
|
||||
"on": "",
|
||||
"off": "",
|
||||
"unavailable": "",
|
||||
"failedToStart": "",
|
||||
"buttonTooltip": "",
|
||||
"streamIdRequired": "",
|
||||
"streamIdHelp": "",
|
||||
"error": "",
|
||||
"busy": ""
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,12 @@
|
||||
"roomUnlocked": "Qual que siá amb lo ligam pòt participar",
|
||||
"passwordSetRemotely": "causit per qualqu'un mai",
|
||||
"connectionsettings": "Paramètres de connexion",
|
||||
"poweredby": "Produit per",
|
||||
"feedback": "Donatz-nos vòstre vejaire",
|
||||
"poweredby": "produit per",
|
||||
"feedback": "Donatz-nos lo vòstre vejaire",
|
||||
"inviteUrlDefaultMsg": "Vòstra conferéncia es en cors de creacion...",
|
||||
"me": "ieu",
|
||||
"speaker": "Nautparlaire",
|
||||
"raisedHand": "Volriatz charrar",
|
||||
"raisedHand": "Volriá charrar",
|
||||
"defaultNickname": "ex. Joan Delpuèch",
|
||||
"defaultLink": "ex. __url__",
|
||||
"callingName": "__name__",
|
||||
@@ -39,41 +39,46 @@
|
||||
"videoMute": "Aviar o arrestar vòstra camerà"
|
||||
},
|
||||
"welcomepage": {
|
||||
"go": "Crear",
|
||||
"roomname": "Sasissètz un nom de sala",
|
||||
"disable": "Afichar pas mai aquesta pagina",
|
||||
"feature1": {
|
||||
"title": "De bon utilizar",
|
||||
"content": "Cap de telecargament pas requesit. __app__ s'utiliza dirèctament dempuèi vòstre navigador. Partejar simplament l'URL de vòstra conferéncia amb los autres per començar."
|
||||
"content": "Cap de telecargament pas requesit. __app__ s'utiliza dirèctament dempuèi vòstre navigador. Partejar simplament l'URL de vòstra conferéncia amb los autres per començar.",
|
||||
"title": "De bon utilizar"
|
||||
},
|
||||
"feature2": {
|
||||
"title": "Benda passanta febla",
|
||||
"content": "Las videoconferéncias de mantun participant necessitan mens de 128 kbps. Lo partiment d'ecran e las conferéncias amb solament d'àudio son possiblas amb plan mens de debit."
|
||||
"content": "Las videoconferéncias de mantun participant necessitan mens de 128 kbps. Lo partiment d'ecran e las conferéncias amb solament d'àudio son possiblas amb plan mens de debit.",
|
||||
"title": "Benda passanta febla"
|
||||
},
|
||||
"feature3": {
|
||||
"title": "Open source",
|
||||
"content": "__app__ es jos licéncia Apache. Sètz liure de telecargar, d'utilizar, de modificar e de partejar __app__ segon aquesta licéncia liura."
|
||||
"content": "__app__ es jos licéncia Apache. Sètz liure de telecargar, d'utilizar, de modificar e de partejar __app__ segon aquesta licéncia liura.",
|
||||
"title": "Open source"
|
||||
},
|
||||
"feature4": {
|
||||
"title": "Nombre d'utilizaires illimitat",
|
||||
"content": "I a pas de restriccions artificialas a prepaus del nombre d'utilizaires o de participants a una conferéncia. La poténcia del servidor e la benda passanta son los sols factors limitants."
|
||||
"content": "I a pas de restriccions artificialas a prepaus del nombre d'utilizaires o de participants a una conferéncia. La poténcia del servidor e la benda passanta son los sols factors limitants.",
|
||||
"title": "Nombre d'utilizaires illimitat"
|
||||
},
|
||||
"feature5": {
|
||||
"title": "Partiment d'ecran",
|
||||
"content": "Es aisit de partejar vòstre ecran amb d'autras personas. __app__ es ideal per las presentacions en linha, los corses, e las sessions de supòrt tecnic."
|
||||
"content": "Es aisit de partejar vòstre ecran amb d'autras personas. __app__ es ideal per las presentacions en linha, los corses, e las sessions de supòrt tecnic.",
|
||||
"title": "Partiment d'ecran"
|
||||
},
|
||||
"feature6": {
|
||||
"title": "Salas securizadas",
|
||||
"content": "Besonh de confidencialitat ? Las salas de conferéncia __app__ pòdon èsser securizadas per un senhal per exclure los convidats pas desirats, e prevenir de las interrupcions."
|
||||
"content": "Besonh de confidencialitat ? Las salas de conferéncia __app__ pòdon èsser securizadas per un senhal per exclure los convidats pas desirats, e prevenir de las interrupcions.",
|
||||
"title": "Salas securizadas"
|
||||
},
|
||||
"feature7": {
|
||||
"title": "Nòtas partejadas",
|
||||
"content": "__app__ prepausa Etherpad, un editor de tèxte collaboratiu en temps real qu'es parfèit pels procèsses verbals, l'edicion d'articles e plan mai encara."
|
||||
"content": "__app__ prepausa Etherpad, un editor de tèxte collaboratiu en temps real qu'es parfèit pels procèsses verbals, l'edicion d'articles e plan mai encara.",
|
||||
"title": "Nòtas partejadas"
|
||||
},
|
||||
"feature8": {
|
||||
"title": "Estatisticas d'utilizacion",
|
||||
"content": "Aprenètz mai a prepaus de vòstres utilizaires amb una integracion aisida de Piwik, Google Analytics e d'autres sistèmas d'estatisticas e supervision d'utilizacion."
|
||||
}
|
||||
"content": "Aprenètz mai a prepaus de vòstres utilizaires amb una integracion aisida de Piwik, Google Analytics e d'autres sistèmas d'estatisticas e supervision d'utilizacion.",
|
||||
"title": "Estatisticas d'utilizacion"
|
||||
},
|
||||
"go": "Crear",
|
||||
"join": "PARTICIPATZ",
|
||||
"privacy": "Vida privada",
|
||||
"roomname": "Sasissètz un nom de sala",
|
||||
"roomnamePlaceHolder": "nom de la sala",
|
||||
"sendFeedback": "Mandar vòstra opinion",
|
||||
"terms": "Tèrmes"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
@@ -108,7 +113,14 @@
|
||||
"micDisabled": "Lo microfòn es pas disponible",
|
||||
"filmstrip": "Mostrar / escondre vidèos",
|
||||
"profile": "Modificar vòstre perfil",
|
||||
"raiseHand": "Demandar / Demandar pas mai la paraula"
|
||||
"raiseHand": "o se l'avètz ja<br /><strong>alara</strong>"
|
||||
},
|
||||
"unsupportedBrowser": {
|
||||
"appInstalled": "o se ja l'avètz<br /><strong>alara</strong>",
|
||||
"appNotInstalled": "Vos cal <strong>__app__</strong> per participar a la conversacion a partir de vòstre mobil",
|
||||
"downloadApp": "Telecargar l'aplicacion",
|
||||
"joinConversation": "Participar a la conversacion",
|
||||
"startConference": "Començar una conferéncia"
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Dobrir / tampar lo chat",
|
||||
@@ -126,15 +138,15 @@
|
||||
"title": "Paramètres",
|
||||
"update": "Mesa a jorn",
|
||||
"name": "Nom",
|
||||
"startAudioMuted": "Tot lo mond comença sens son",
|
||||
"startVideoMuted": "Tot lo mond comença escondut",
|
||||
"selectCamera": "Camèra",
|
||||
"startAudioMuted": "Començan totes sens son",
|
||||
"startVideoMuted": "Començan totes sens vidèo",
|
||||
"selectCamera": "Camerà",
|
||||
"selectMic": "Microfòn",
|
||||
"selectAudioOutput": "Sortida àudio",
|
||||
"followMe": "Tot lo mond me sèc",
|
||||
"noDevice": "Pas cap",
|
||||
"noPermission": "La permission d'utilizar l'aparelh es pas estada donada",
|
||||
"cameraAndMic": "Camèra e microfòn",
|
||||
"cameraAndMic": "Camerà e microfòn",
|
||||
"moderator": "MODERATOR",
|
||||
"password": "DEFINIR UN SENHAL",
|
||||
"audioVideo": "ÀUDIO E VIDÈO",
|
||||
@@ -149,12 +161,13 @@
|
||||
"videothumbnail": {
|
||||
"editnickname": "Clicatz per modificar<br/>vòstre nom",
|
||||
"moderator": "Lo proprietari de<br/>aquesta conferéncia",
|
||||
"videomute": "Un participant a<br/>arrestat sa camèra.",
|
||||
"videomute": "Un participant a<br/>arrestat sa camerà.",
|
||||
"mute": "Un participant a copat son micro",
|
||||
"kick": "Exclure",
|
||||
"muted": "Mut",
|
||||
"domute": "Copar lo son",
|
||||
"flip": "Revirar"
|
||||
"flip": "Revirar",
|
||||
"remoteControl": "Contraròtle alonhat"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "Donadas de connexion",
|
||||
@@ -162,7 +175,7 @@
|
||||
"packetloss": "Pèrda de paquets :",
|
||||
"resolution": "Resolucion :",
|
||||
"less": "Amagar lo detalh",
|
||||
"more": "Mostrar lo deta1h",
|
||||
"more": "Ne veire mai",
|
||||
"address": "Adreça :",
|
||||
"remoteport": "Pòrt distant :",
|
||||
"remoteport_plural": "Pòrts distants :",
|
||||
@@ -208,7 +221,7 @@
|
||||
"createPassword": "Crear un senhal",
|
||||
"detectext": "Una error s'es produita pendent la deteccion de l'extension de partiment d'ecran.",
|
||||
"failtoinstall": "Fracàs de l'installacion de l'extension de partiment d'ecran",
|
||||
"failedpermissions": "Fracàs d'obtencion de las permissions per utilizar lo micro e/o la camèra.",
|
||||
"failedpermissions": "Fracàs d'obtencion de las permissions per utilizar lo micro e/o la camerà.",
|
||||
"conferenceReloadTitle": "Malurosament, quicòm truquèt",
|
||||
"conferenceReloadMsg": "Sèm a trobar una solucion per aquò",
|
||||
"conferenceDisconnectTitle": "Sètz estat desconnectat. Vos cal benlèu verificar vòstra connexion.",
|
||||
@@ -224,7 +237,7 @@
|
||||
"internalError": "Ops ! Quicòm a pas fonccionat. L'error seguenta s'es produsida : [setRemoteDescription]",
|
||||
"unableToSwitch": "Impossible de cambiar lo flux vidèo.",
|
||||
"SLDFailure": "Ops! Quicòm a trucat e lo micro es pas estat copat! (Fracàs SLD)",
|
||||
"SRDFailure": "Ops! Quicòm a trucat e la camèra es pas estada copada! (Fracàs SRD)",
|
||||
"SRDFailure": "Ops ! Quicòm a trucat e la camerà es pas estada arrestada ! (Fracàs SRD)",
|
||||
"oops": "Ops !",
|
||||
"currentPassword": "L'actual senhal es",
|
||||
"passwordLabel": "Senhal",
|
||||
@@ -298,21 +311,26 @@
|
||||
"cameraErrorPresent": "I a agut una error pendent la connexion a la camerà.",
|
||||
"cameraUnsupportedResolutionError": "Vòstra camerà pren pas en carga la resolucion vidèo que cal.",
|
||||
"cameraUnknownError": "Impossible d'emplegar la camerà per una rason desconeguda.",
|
||||
"cameraPermissionDeniedError": "La camèra es pas estada trobada.",
|
||||
"cameraNotFoundError": "La camèra es pas estada trobada.",
|
||||
"cameraPermissionDeniedError": "La camerà es pas estada trobada.",
|
||||
"cameraNotFoundError": "La camerà es pas estada trobada.",
|
||||
"cameraConstraintFailedError": "Vòstra camerà satisfà pas totas las constrentas necessàrias.",
|
||||
"micUnknownError": "Impossible d'utilizar lo microfòn per una rason desconeguda.",
|
||||
"micPermissionDeniedError": "Avètz pas donat l'autorizacion d'utilizar vòstre microfòn. Podètz encara participar a la conferéncia mai los demai vos ausiràn pas. Utilizatz lo boton del microfòn dins la barra d'adreça per resòlvre aquò.",
|
||||
"micNotFoundError": "Lo microfòn es pas estat trobat.",
|
||||
"micConstraintFailedError": "Vòstre microfòn satisfà pas totas las constrentas necessàrias.",
|
||||
"micNotSendingData": "Podèm pas aver l'accès a vòstre microfòn. Mercés de ne causir un autre dins lo menú de paramètres o ensajatz de tornar dubrir l'aplicacion.",
|
||||
"cameraNotSendingData": "Podèm pas aver l'accès a vòstra camèra. Mercés de verificar se una autra aplicacion es pas a l'utilizar, causissètz una autra camèra dins lo menú de paramètres o ensajatz de tornar dubrir l'aplicacion.",
|
||||
"cameraNotSendingData": "Podèm pas aver l'accès a vòstra camerà. Mercés de verificar se una autra aplicacion es pas a l'utilizar, causissètz una autra camerà dins lo menú de paramètres o ensajatz de tornar dubrir l'aplicacion.",
|
||||
"goToStore": "Anar sul webstore",
|
||||
"externalInstallationTitle": "Extension requesida :",
|
||||
"externalInstallationMsg": "Avètz d'installar nòstra extension de partiment d'ecran.",
|
||||
"muteParticipantTitle": "Copar lo micro als participants ?",
|
||||
"muteParticipantBody": "Poiretz pas lo tornar activar lo microfòn, mai eles pòdon o far quand vòlon.",
|
||||
"muteParticipantButton": "Copar lo son"
|
||||
"muteParticipantButton": "Copar lo son",
|
||||
"remoteControlTitle": "Contraròtle alonhat",
|
||||
"remoteControlDeniedMessage": "__user__ a refusat vòstra demanda de contraròtle alonhat !",
|
||||
"remoteControlAllowedMessage": "__user__ a acceptat vòstra demanda de contraròtle alonhat !",
|
||||
"remoteControlErrorMessage": "Error al moment de demandar lo contraròtle alonhat a __user__ !",
|
||||
"remoteControlStopMessage": "La session de contraròtle alonhat es acabada !"
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
@@ -371,6 +389,7 @@
|
||||
"failedToStart": "Lo dirècte a pas capitat de s'aviar",
|
||||
"buttonTooltip": "Aviar / arrestar lo dirècte",
|
||||
"streamIdRequired": "Mercé de completar lo stream id per aviar lo dirècte.",
|
||||
"streamIdHelp": "Ont tròbi aquò ?",
|
||||
"error": "Fracàs de la transmission en dirècte. Mercés de tornar ensajar.",
|
||||
"busy": "Tots los enresgistraires son ocupats. Mercés de tornar ensajar mai tard."
|
||||
}
|
||||
|
||||
@@ -39,41 +39,46 @@
|
||||
"videoMute": "Iniciar ou parar sua câmera"
|
||||
},
|
||||
"welcomepage": {
|
||||
"go": "IR",
|
||||
"roomname": "Digite o nome da sala",
|
||||
"disable": "Não mostre esta página novamente",
|
||||
"feature1": {
|
||||
"title": "Simples para usar",
|
||||
"content": "Não precisa baixar nada. __app__ trabalha diretamente no seu navegador. Simplesmente compartilhe sua URL da conferência com outros para começar."
|
||||
"content": "Não precisa baixar nada. __app__ trabalha diretamente no seu navegador. Simplesmente compartilhe sua URL da conferência com outros para começar.",
|
||||
"title": "Simples para usar"
|
||||
},
|
||||
"feature2": {
|
||||
"title": "Largura de banda baixa",
|
||||
"content": "Conferências de vídeo de multipartes trabalha com tão pouco quanto 128 kbps. Compartilhamento de tela e conferências de áudio somente são possíveis com muito menos."
|
||||
"content": "Conferências de vídeo de multipartes trabalha com tão pouco quanto 128 kbps. Compartilhamento de tela e conferências de áudio somente são possíveis com muito menos.",
|
||||
"title": "Largura de banda baixa"
|
||||
},
|
||||
"feature3": {
|
||||
"title": "Código aberto",
|
||||
"content": "__app__ é licenciado sob a Licença Apache. Você é livre para baixar, usar, modificar e compartilhar ela com a mesma licença."
|
||||
"content": "__app__ é licenciado sob a Licença Apache. Você é livre para baixar, usar, modificar e compartilhar ela com a mesma licença.",
|
||||
"title": "Código aberto"
|
||||
},
|
||||
"feature4": {
|
||||
"title": "Usuários ilimitados",
|
||||
"content": "Aqui não há restrições artificiais no número de usuários ou participantes da conferência. A potência do servidor e largura de banda são os únicos fatores limitantes."
|
||||
"content": "Aqui não há restrições artificiais no número de usuários ou participantes da conferência. A potência do servidor e largura de banda são os únicos fatores limitantes.",
|
||||
"title": "Usuários ilimitados"
|
||||
},
|
||||
"feature5": {
|
||||
"title": "Compartilhamento de tela",
|
||||
"content": "É fácil compartilhar sua tela com outros. __app__ é ideal para apresentações online, leituras, e sessões de suporte técnico."
|
||||
"content": "É fácil compartilhar sua tela com outros. __app__ é ideal para apresentações online, leituras, e sessões de suporte técnico.",
|
||||
"title": "Compartilhamento de tela"
|
||||
},
|
||||
"feature6": {
|
||||
"title": "Salas seguras",
|
||||
"content": "Precisa alguma privacidade? Salas de conferência do __app__ podem ser seguras com uma senha para excluir visitantes indesejados e prevenir interrupções."
|
||||
"content": "Precisa alguma privacidade? Salas de conferência do __app__ podem ser seguras com uma senha para excluir visitantes indesejados e prevenir interrupções.",
|
||||
"title": "Salas seguras"
|
||||
},
|
||||
"feature7": {
|
||||
"title": "Notas compartilhadas",
|
||||
"content": "__app_ disponibiliza o Etherpad, um editor de texto colaborativo em tempo real, que é ótimo para reuniões rápidas, escrevendo artigos, e mais."
|
||||
"content": "__app_ disponibiliza o Etherpad, um editor de texto colaborativo em tempo real, que é ótimo para reuniões rápidas, escrevendo artigos, e mais.",
|
||||
"title": "Notas compartilhadas"
|
||||
},
|
||||
"feature8": {
|
||||
"title": "Estatísticas de uso",
|
||||
"content": "Aprenda sobre seus usuários através de integração fácil com o Piwik, Google Analytics, e outros sistemas de monitoramento e estatísticas."
|
||||
}
|
||||
"content": "Aprenda sobre seus usuários através de integração fácil com o Piwik, Google Analytics, e outros sistemas de monitoramento e estatísticas.",
|
||||
"title": "Estatísticas de uso"
|
||||
},
|
||||
"go": "IR",
|
||||
"join": "Entrar",
|
||||
"privacy": "Política de Privacidade",
|
||||
"roomname": "Digite o nome da sala",
|
||||
"roomnamePlaceHolder": "Nome da sala",
|
||||
"sendFeedback": "Enviar comentários",
|
||||
"terms": "Termos"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
@@ -110,6 +115,13 @@
|
||||
"profile": "Editar seu perfil",
|
||||
"raiseHand": "Erguer o baixar sua mão"
|
||||
},
|
||||
"unsupportedBrowser": {
|
||||
"appInstalled": "ou se você já tenha isso<br /> <strong>então</strong>",
|
||||
"appNotInstalled": "Você precisa do <strong>__app__</strong> para começar um bate-papo no seu celular",
|
||||
"downloadApp": "Baixe o Aplicativo",
|
||||
"joinConversation": "Entre no bate-papo",
|
||||
"startConference": "Comece uma conferência"
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Abrir / fechar bate-papo",
|
||||
"filmstrip": "Mostrar/ocultar vídeos",
|
||||
@@ -154,7 +166,8 @@
|
||||
"kick": "Chutar fora",
|
||||
"muted": "Mudo",
|
||||
"domute": "Mudo",
|
||||
"flip": "Inverter"
|
||||
"flip": "Inverter",
|
||||
"remoteControl": "Controle remoto"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "Dados da conexão",
|
||||
@@ -312,7 +325,12 @@
|
||||
"externalInstallationMsg": "Você precisa instalar nossa extensão de compartilhamento de tela.",
|
||||
"muteParticipantTitle": "Deixar mudo este participante?",
|
||||
"muteParticipantBody": "Você não está habilitado para tirar o mudo deles, mas eles podem tirar o mudo deles mesmos a qualquer tempo.",
|
||||
"muteParticipantButton": "Mudo"
|
||||
"muteParticipantButton": "Mudo",
|
||||
"remoteControlTitle": "Controle remoto",
|
||||
"remoteControlDeniedMessage": "__user__ rejeitou sua requisição de controle remoto!",
|
||||
"remoteControlAllowedMessage": "__user__ aceitou sua requisição de controle remoto!",
|
||||
"remoteControlErrorMessage": "Um erro ocorreu enquanto tentava requerer a permissão de controle remoto de __user__!",
|
||||
"remoteControlStopMessage": "A sessão de controle remoto terminou!"
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
@@ -371,6 +389,7 @@
|
||||
"failedToStart": "Falha ao iniciar a transmissão ao vivo",
|
||||
"buttonTooltip": "Iniciar / parar live stream",
|
||||
"streamIdRequired": "Por favor digite o id da transmissão ao vivo para começar a transmitir.",
|
||||
"streamIdHelp": "Aonde eu encontro isto?",
|
||||
"error": "Falha na transmissão ao vivo. Tente novamente",
|
||||
"busy": "Todos os gravadores estão ocupados no momento. Por favor, tente mais tarde."
|
||||
}
|
||||
|
||||
@@ -1,355 +1,396 @@
|
||||
{
|
||||
"contactlist": "",
|
||||
"addParticipants": "",
|
||||
"roomLocked": "",
|
||||
"roomUnlocked": "",
|
||||
"passwordSetRemotely": "",
|
||||
"connectionsettings": "",
|
||||
"poweredby": "",
|
||||
"feedback": "",
|
||||
"inviteUrlDefaultMsg": "",
|
||||
"me": "",
|
||||
"speaker": "",
|
||||
"raisedHand": "",
|
||||
"defaultNickname": "",
|
||||
"defaultLink": "",
|
||||
"callingName": "",
|
||||
"contactlist": "与会者 (__pcount__)",
|
||||
"addParticipants": "分享链接",
|
||||
"roomLocked": "与会者必须输入密码",
|
||||
"roomUnlocked": "任何人都可以通过此链接参加会议",
|
||||
"passwordSetRemotely": "由其他与会者设置",
|
||||
"connectionsettings": "连接设置",
|
||||
"poweredby": "技术支持",
|
||||
"feedback": "请给我们您的反馈",
|
||||
"inviteUrlDefaultMsg": "您的会议正在被创建。。。",
|
||||
"me": "我",
|
||||
"speaker": "发言人",
|
||||
"raisedHand": "请求发言",
|
||||
"defaultNickname": "例如 星视通",
|
||||
"defaultLink": "例如 __url__",
|
||||
"callingName": "__name__",
|
||||
"userMedia": {
|
||||
"react-nativeGrantPermissions": "",
|
||||
"chromeGrantPermissions": "",
|
||||
"androidGrantPermissions": "",
|
||||
"firefoxGrantPermissions": "",
|
||||
"operaGrantPermissions": "",
|
||||
"iexplorerGrantPermissions": "",
|
||||
"safariGrantPermissions": "",
|
||||
"nwjsGrantPermissions": ""
|
||||
"react-nativeGrantPermissions": "请点击<b><i>允许</i></b>按钮授权使用您的摄像头和麦克风",
|
||||
"chromeGrantPermissions": "请点击<b><i>允许</i></b>按钮授权使用您的摄像头和麦克风",
|
||||
"androidGrantPermissions": "请点击<b><i>允许</i></b>按钮授权使用您的摄像头和麦克风",
|
||||
"firefoxGrantPermissions": "请点击<b><i>共享选择的设备</i></b>按钮授权使用您的摄像头和麦克风",
|
||||
"operaGrantPermissions": "请点击<b><i>允许</i></b>按钮授权使用您的摄像头和麦克风",
|
||||
"iexplorerGrantPermissions": "请点击<b><i>确认</i></b>按钮授权使用您的摄像头和麦克风",
|
||||
"safariGrantPermissions": "请点击<b><i>确认</i></b>按钮授权使用您的摄像头和麦克风",
|
||||
"nwjsGrantPermissions": "请授权使用您的摄像头和麦克风"
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
"keyboardShortcuts": "",
|
||||
"raiseHand": "",
|
||||
"pushToTalk": "",
|
||||
"toggleScreensharing": "",
|
||||
"toggleFilmstrip": "",
|
||||
"toggleShortcuts": "",
|
||||
"focusLocal": "",
|
||||
"focusRemote": "",
|
||||
"toggleChat": "",
|
||||
"mute": "",
|
||||
"fullScreen": "",
|
||||
"videoMute": ""
|
||||
"keyboardShortcuts": "快捷键",
|
||||
"raiseHand": "申请或取消发言",
|
||||
"pushToTalk": "按住说话",
|
||||
"toggleScreensharing": "在摄像头和屏幕共享之间切换",
|
||||
"toggleFilmstrip": "显示或隐藏视频",
|
||||
"toggleShortcuts": "显示或隐藏帮助菜单",
|
||||
"focusLocal": "切换到本地视频上",
|
||||
"focusRemote": "切换到远端视频上",
|
||||
"toggleChat": "打开或关闭聊天",
|
||||
"mute": "静音或取消静音",
|
||||
"fullScreen": "全屏或退出全屏",
|
||||
"videoMute": "开启或关闭视频"
|
||||
},
|
||||
"welcomepage": {
|
||||
"go": "",
|
||||
"roomname": "",
|
||||
"disable": "",
|
||||
"disable": "不再显示该页",
|
||||
"feature1": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"content": "无需下载. __app__ 直接通过浏览器使用。 分享您的会议链接给其他人即可参与会议。",
|
||||
"title": "简单易用"
|
||||
},
|
||||
"feature2": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"content": "多方视频会议所需带宽仅需128Kbps。 屏幕共享和语音会议所需的带宽更少。",
|
||||
"title": "低带宽"
|
||||
},
|
||||
"feature3": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"content": "__app__ 有Apache许可. 在此许可下,您可以免费下载,使用,修改和分享该代码",
|
||||
"title": "开源"
|
||||
},
|
||||
"feature4": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"content": "对于使用者和与会者没有人数的限制。 服务器的性能和带宽是唯一的限制因素。",
|
||||
"title": "不限用户数"
|
||||
},
|
||||
"feature5": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"content": "和他人共享屏幕非常简单。 __app__ 对于在线演示、讲座和技术支持会议再合适不过了。",
|
||||
"title": "屏幕共享"
|
||||
},
|
||||
"feature6": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"content": "是否担心隐私安全? __app__ 可以设定会议室密码防止他人进入会议。",
|
||||
"title": "安全"
|
||||
},
|
||||
"feature7": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"content": "__app__ 的一大特色是Etherpad——一个完美适用于会议、写作等场景,可实时协作的文本编辑器。",
|
||||
"title": "共享笔记"
|
||||
},
|
||||
"feature8": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
}
|
||||
"content": "通过简单地整合Piwik, Google Analytics或者其他使用监控和统计系统来了解您的使用者。",
|
||||
"title": "使用统计"
|
||||
},
|
||||
"go": "开始",
|
||||
"join": "",
|
||||
"privacy": "",
|
||||
"roomname": "请输入房间名",
|
||||
"roomnamePlaceHolder": "",
|
||||
"sendFeedback": "",
|
||||
"terms": ""
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": "",
|
||||
"title": ""
|
||||
"policyText": " ",
|
||||
"title": "__app__ 需要使用您的麦克风和摄像头。"
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"title": "",
|
||||
"rejoinKeyTitle": ""
|
||||
"title": "由于您的电脑休眠,视频通话已经中断。",
|
||||
"rejoinKeyTitle": "重新加入"
|
||||
},
|
||||
"toolbar": {
|
||||
"mute": "",
|
||||
"videomute": "",
|
||||
"authenticate": "",
|
||||
"lock": "",
|
||||
"invite": "",
|
||||
"chat": "",
|
||||
"etherpad": "",
|
||||
"sharedvideo": "",
|
||||
"sharescreen": "",
|
||||
"fullscreen": "",
|
||||
"sip": "",
|
||||
"Settings": "",
|
||||
"hangup": "",
|
||||
"login": "",
|
||||
"logout": "",
|
||||
"dialpad": "",
|
||||
"sharedVideoMutedPopup": "",
|
||||
"micMutedPopup": "",
|
||||
"talkWhileMutedPopup": "",
|
||||
"unableToUnmutePopup": "",
|
||||
"cameraDisabled": "",
|
||||
"micDisabled": "",
|
||||
"filmstrip": "",
|
||||
"profile": "",
|
||||
"raiseHand": ""
|
||||
"mute": "静音 / 解除静音",
|
||||
"videomute": "开启 / 关闭 摄像头",
|
||||
"authenticate": "认证",
|
||||
"lock": "锁定 / 解锁 房间",
|
||||
"invite": "分享链接",
|
||||
"chat": "开启 / 关闭 聊天",
|
||||
"etherpad": "开启 / 关闭 共享文档",
|
||||
"sharedvideo": "分享YouTube视频",
|
||||
"sharescreen": "开启 / 关闭 屏幕共享",
|
||||
"fullscreen": "开启 / 关闭 全屏",
|
||||
"sip": "呼叫SIP号码",
|
||||
"Settings": "设置",
|
||||
"hangup": "离开",
|
||||
"login": "登录",
|
||||
"logout": "登出",
|
||||
"dialpad": "开启 / 关闭 拨号盘",
|
||||
"sharedVideoMutedPopup": "您共享的视频已被关闭 <br/> 您可以与其他与会者交谈。",
|
||||
"micMutedPopup": "您的麦克风已被静音 您<br/>可以尽情享受您的共享视频。",
|
||||
"talkWhileMutedPopup": "您在尝试发言吗? 当前您已被静音。",
|
||||
"unableToUnmutePopup": "正在共享视频的时候您不能解除静音。",
|
||||
"cameraDisabled": "摄像头不可用",
|
||||
"micDisabled": "麦克风不可用",
|
||||
"filmstrip": "显示 / 隐藏 视频",
|
||||
"profile": "编辑您的简介",
|
||||
"raiseHand": "请求 / 取消 发言"
|
||||
},
|
||||
"unsupportedBrowser": {
|
||||
"appInstalled": "",
|
||||
"appNotInstalled": "",
|
||||
"downloadApp": "",
|
||||
"joinConversation": "",
|
||||
"startConference": ""
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "",
|
||||
"filmstrip": "",
|
||||
"contactlist": ""
|
||||
"chat": "开启 / 关闭 聊天",
|
||||
"filmstrip": "显示 / 隐藏 视频",
|
||||
"contactlist": "查看和邀请与会者"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "",
|
||||
"popover": ""
|
||||
"title": "请在下面的方框内输入昵称",
|
||||
"popover": "选择一个昵称"
|
||||
},
|
||||
"messagebox": ""
|
||||
"messagebox": "请输入文本..."
|
||||
},
|
||||
"settings": {
|
||||
"title": "",
|
||||
"update": "",
|
||||
"name": "",
|
||||
"startAudioMuted": "",
|
||||
"startVideoMuted": "",
|
||||
"selectCamera": "",
|
||||
"selectMic": "",
|
||||
"selectAudioOutput": "",
|
||||
"followMe": "",
|
||||
"noDevice": "",
|
||||
"noPermission": "",
|
||||
"cameraAndMic": "",
|
||||
"moderator": "",
|
||||
"password": "",
|
||||
"audioVideo": "",
|
||||
"setPasswordLabel": ""
|
||||
"title": "设置",
|
||||
"update": "更新",
|
||||
"name": "名称",
|
||||
"startAudioMuted": "所有人开始时静音",
|
||||
"startVideoMuted": "所有人开始时隐藏视频画面",
|
||||
"selectCamera": "摄像头",
|
||||
"selectMic": "麦克风",
|
||||
"selectAudioOutput": "音频输出",
|
||||
"followMe": "所有人跟随我",
|
||||
"noDevice": "未发现设备",
|
||||
"noPermission": "未授权使用设备",
|
||||
"cameraAndMic": "摄像头和麦克风",
|
||||
"moderator": "主持人",
|
||||
"password": "设定密码",
|
||||
"audioVideo": "音频和视频",
|
||||
"setPasswordLabel": "用密码锁定房间"
|
||||
},
|
||||
"profile": {
|
||||
"title": "",
|
||||
"setDisplayNameLabel": "",
|
||||
"setEmailLabel": "",
|
||||
"setEmailInput": ""
|
||||
"title": "简介",
|
||||
"setDisplayNameLabel": "设定您的显示名称",
|
||||
"setEmailLabel": "设置您的个人全球统一标识邮箱",
|
||||
"setEmailInput": "输入您的邮箱"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "",
|
||||
"moderator": "",
|
||||
"videomute": "",
|
||||
"mute": "",
|
||||
"kick": "",
|
||||
"muted": "",
|
||||
"domute": "",
|
||||
"flip": ""
|
||||
"editnickname": "点击编辑您的<br/>显示名称",
|
||||
"moderator": "<br/>此会议的拥有者",
|
||||
"videomute": "与会者已经<br/>关闭了摄像头",
|
||||
"mute": "与会者已被静音",
|
||||
"kick": "踢出",
|
||||
"muted": "已静音",
|
||||
"domute": "静音",
|
||||
"flip": "翻转",
|
||||
"remoteControl": "远程控制"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "",
|
||||
"bitrate": "",
|
||||
"packetloss": "",
|
||||
"resolution": "",
|
||||
"less": "",
|
||||
"more": "",
|
||||
"address": "",
|
||||
"remoteport_plural": "",
|
||||
"remoteport": "",
|
||||
"localport_plural": "",
|
||||
"localport": "",
|
||||
"localaddress_plural": "",
|
||||
"localaddress": "",
|
||||
"remoteaddress_plural": "",
|
||||
"remoteaddress": "",
|
||||
"transport": "",
|
||||
"bandwidth": "",
|
||||
"na": ""
|
||||
"header": "连接数据",
|
||||
"bitrate": "比特率",
|
||||
"packetloss": "丢包",
|
||||
"resolution": "分辨率",
|
||||
"less": "显示更少",
|
||||
"more": "显示更多",
|
||||
"address": "地址",
|
||||
"remoteport_plural": "远程端口:",
|
||||
"remoteport": "远程端口:",
|
||||
"localport_plural": "本地端口:",
|
||||
"localport": "本地端口:",
|
||||
"localaddress_plural": "本地地址:",
|
||||
"localaddress": "本地地址:",
|
||||
"remoteaddress_plural": "远程地址:",
|
||||
"remoteaddress": "远程地址:",
|
||||
"transport": "传输:",
|
||||
"bandwidth": "估计带宽:",
|
||||
"na": "会议开始可回到此处查看连接信息"
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "",
|
||||
"moderator": "",
|
||||
"connected": "",
|
||||
"somebody": "",
|
||||
"me": "",
|
||||
"focus": "",
|
||||
"focusFail": "",
|
||||
"grantedTo": "",
|
||||
"grantedToUnknown": "",
|
||||
"muted": "",
|
||||
"mutedTitle": "",
|
||||
"raisedHand": ""
|
||||
"disconnected": "已断开连接",
|
||||
"moderator": "已授权主持人权限!",
|
||||
"connected": "已连接",
|
||||
"somebody": "某人",
|
||||
"me": "自己",
|
||||
"focus": "会议聚焦",
|
||||
"focusFail": "__component__ 不可用 - 在__ms__秒后重试",
|
||||
"grantedTo": "主持权限已授予__to__!",
|
||||
"grantedToUnknown": "主持权限已授予$t(somebody)!",
|
||||
"muted": "您已经开始了通话,并处于静音状态。",
|
||||
"mutedTitle": "您已被静音!",
|
||||
"raisedHand": "请求发言"
|
||||
},
|
||||
"dialog": {
|
||||
"add": "",
|
||||
"kickMessage": "",
|
||||
"popupError": "",
|
||||
"passwordErrorTitle": "",
|
||||
"passwordError": "",
|
||||
"passwordError2": "",
|
||||
"connectError": "",
|
||||
"connectErrorWithMsg": "",
|
||||
"incorrectPassword": "",
|
||||
"connecting": "",
|
||||
"copy": "",
|
||||
"error": "",
|
||||
"roomLocked": "",
|
||||
"addPassword": "",
|
||||
"createPassword": "",
|
||||
"detectext": "",
|
||||
"failtoinstall": "",
|
||||
"failedpermissions": "",
|
||||
"conferenceReloadTitle": "",
|
||||
"conferenceReloadMsg": "",
|
||||
"conferenceDisconnectTitle": "",
|
||||
"conferenceDisconnectMsg": "",
|
||||
"reconnectNow": "",
|
||||
"conferenceReloadTimeLeft": "",
|
||||
"maxUsersLimitReached": "",
|
||||
"lockTitle": "",
|
||||
"lockMessage": "",
|
||||
"warning": "",
|
||||
"passwordNotSupported": "",
|
||||
"internalErrorTitle": "",
|
||||
"internalError": "",
|
||||
"unableToSwitch": "",
|
||||
"SLDFailure": "",
|
||||
"SRDFailure": "",
|
||||
"oops": "",
|
||||
"currentPassword": "",
|
||||
"passwordLabel": "",
|
||||
"defaultError": "",
|
||||
"passwordRequired": "",
|
||||
"Ok": "",
|
||||
"done": "",
|
||||
"Remove": "",
|
||||
"removePassword": "",
|
||||
"shareVideoTitle": "",
|
||||
"shareVideoLinkError": "",
|
||||
"removeSharedVideoTitle": "",
|
||||
"removeSharedVideoMsg": "",
|
||||
"alreadySharedVideoMsg": "",
|
||||
"WaitingForHost": "",
|
||||
"WaitForHostMsg": "",
|
||||
"IamHost": "",
|
||||
"Cancel": "",
|
||||
"Submit": "",
|
||||
"retry": "",
|
||||
"logoutTitle": "",
|
||||
"logoutQuestion": "",
|
||||
"sessTerminated": "",
|
||||
"hungUp": "",
|
||||
"joinAgain": "",
|
||||
"Share": "",
|
||||
"Save": "",
|
||||
"recording": "",
|
||||
"recordingToken": "",
|
||||
"Dial": "",
|
||||
"sipMsg": "",
|
||||
"passwordCheck": "",
|
||||
"passwordMsg": "",
|
||||
"shareLink": "",
|
||||
"settings1": "",
|
||||
"settings2": "",
|
||||
"settings3": "",
|
||||
"yourPassword": "",
|
||||
"Back": "",
|
||||
"serviceUnavailable": "",
|
||||
"gracefulShutdown": "",
|
||||
"Yes": "",
|
||||
"reservationError": "",
|
||||
"reservationErrorMsg": "",
|
||||
"password": "",
|
||||
"userPassword": "",
|
||||
"token": "",
|
||||
"tokenAuthFailedTitle": "",
|
||||
"tokenAuthFailed": "",
|
||||
"displayNameRequired": "",
|
||||
"enterDisplayName": "",
|
||||
"extensionRequired": "",
|
||||
"firefoxExtensionPrompt": "",
|
||||
"rateExperience": "",
|
||||
"feedbackHelp": "",
|
||||
"feedbackQuestion": "",
|
||||
"thankYou": "",
|
||||
"sorryFeedback": "",
|
||||
"liveStreaming": "",
|
||||
"streamKey": "",
|
||||
"startLiveStreaming": "",
|
||||
"stopStreamingWarning": "",
|
||||
"stopRecordingWarning": "",
|
||||
"stopLiveStreaming": "",
|
||||
"stopRecording": "",
|
||||
"doNotShowWarningAgain": "",
|
||||
"doNotShowMessageAgain": "",
|
||||
"permissionDenied": "",
|
||||
"screenSharingPermissionDeniedError": "",
|
||||
"micErrorPresent": "",
|
||||
"cameraErrorPresent": "",
|
||||
"cameraUnsupportedResolutionError": "",
|
||||
"cameraUnknownError": "",
|
||||
"cameraPermissionDeniedError": "",
|
||||
"cameraNotFoundError": "",
|
||||
"cameraConstraintFailedError": "",
|
||||
"micUnknownError": "",
|
||||
"micPermissionDeniedError": "",
|
||||
"micNotFoundError": "",
|
||||
"micConstraintFailedError": "",
|
||||
"micNotSendingData": "",
|
||||
"cameraNotSendingData": "",
|
||||
"goToStore": "",
|
||||
"externalInstallationTitle": "",
|
||||
"externalInstallationMsg": "",
|
||||
"muteParticipantTitle": "",
|
||||
"muteParticipantBody": "",
|
||||
"muteParticipantButton": ""
|
||||
"add": "添加",
|
||||
"kickMessage": "您已被踢出会议!",
|
||||
"popupError": "您的浏览器禁止弹窗,请到浏览器安全设置里设定允许弹窗后重试。",
|
||||
"passwordErrorTitle": "密码错误",
|
||||
"passwordError": "此会议现在受密码保护。只有会议的拥有者可以设定密码。",
|
||||
"passwordError2": "此会议现在受密码保护。只有会议的拥有者可以设定密码。",
|
||||
"connectError": "发生错误,无法连接至会议!",
|
||||
"connectErrorWithMsg": "发生错误,无法连接至会议: __msg__",
|
||||
"incorrectPassword": "密码不正确",
|
||||
"connecting": "连接中",
|
||||
"copy": "复制",
|
||||
"error": "错误",
|
||||
"roomLocked": "此会话已被锁定,新与会者必须有链接地址和密码才能加入",
|
||||
"addPassword": "添加密码",
|
||||
"createPassword": "创建密码",
|
||||
"detectext": "尝试检测桌面共享扩展时发生错误",
|
||||
"failtoinstall": "安装桌面共享扩展失败",
|
||||
"failedpermissions": "未能获取使用本地麦克风或摄像头的权限。",
|
||||
"conferenceReloadTitle": "不好意思,出错了",
|
||||
"conferenceReloadMsg": "正在尝试修复",
|
||||
"conferenceDisconnectTitle": "您已断开连接,请检查您的网络连接。",
|
||||
"conferenceDisconnectMsg": "重新连接中。。。",
|
||||
"reconnectNow": "现在开始重新连接",
|
||||
"conferenceReloadTimeLeft": "__seconds__秒。",
|
||||
"maxUsersLimitReached": "已达到会议的最大人数限制,请稍后尝试!",
|
||||
"lockTitle": "锁定失败",
|
||||
"lockMessage": "锁定会议失败。",
|
||||
"warning": "警告",
|
||||
"passwordNotSupported": "当前不支持给房间加密码。",
|
||||
"internalErrorTitle": "内部错误",
|
||||
"internalError": "发生以下错误: [setRemoteDescription]",
|
||||
"unableToSwitch": "无法转换视频流。",
|
||||
"SLDFailure": "发生错误,无法静音! (SLD故障)",
|
||||
"SRDFailure": "发生错误,无法关闭视频! (SRD故障)",
|
||||
"oops": "哎呀!",
|
||||
"currentPassword": "当前的密码是",
|
||||
"passwordLabel": "密码",
|
||||
"defaultError": "有某种错误",
|
||||
"passwordRequired": "需要密码",
|
||||
"Ok": "好的",
|
||||
"done": "完成",
|
||||
"Remove": "移除",
|
||||
"removePassword": "移除密码",
|
||||
"shareVideoTitle": "分享视频",
|
||||
"shareVideoLinkError": "请提供正确的youtube链接。",
|
||||
"removeSharedVideoTitle": "移除共享的视频",
|
||||
"removeSharedVideoMsg": "您确定要移除共享的视频吗?",
|
||||
"alreadySharedVideoMsg": "其他的与会者正在分享视频,同一时间只能有一个人分享视频。",
|
||||
"WaitingForHost": "等待主持人。。。",
|
||||
"WaitForHostMsg": "会议<b>__room__ </b>还没有开始。如果您是主持人请授权开始,否则请等待主持人。",
|
||||
"IamHost": "我是主持人。",
|
||||
"Cancel": "取消",
|
||||
"Submit": "提交",
|
||||
"retry": "重试",
|
||||
"logoutTitle": "登出",
|
||||
"logoutQuestion": "你确定要登出并停止会议吗",
|
||||
"sessTerminated": "会话终止",
|
||||
"hungUp": "挂断",
|
||||
"joinAgain": "重新加入",
|
||||
"Share": "分享",
|
||||
"Save": "保存",
|
||||
"recording": "录制中",
|
||||
"recordingToken": "输入记录标识",
|
||||
"Dial": "拨号",
|
||||
"sipMsg": "输入SIP号码",
|
||||
"passwordCheck": "确定要移除密码吗?",
|
||||
"passwordMsg": "设定密码来锁定房间",
|
||||
"shareLink": "分享此会议的链接",
|
||||
"settings1": "配置您的会议",
|
||||
"settings2": "与会者静音后加入",
|
||||
"settings3": "需要昵称<br/><br/>设定密码来锁定房间:",
|
||||
"yourPassword": "输入新的密码",
|
||||
"Back": "返回",
|
||||
"serviceUnavailable": "服务不可用",
|
||||
"gracefulShutdown": "服务器正在维护,请稍后再试。",
|
||||
"Yes": "是",
|
||||
"reservationError": "预定系统错误",
|
||||
"reservationErrorMsg": "错误代号: __code__, 提示信息: __msg__",
|
||||
"password": "输入密码",
|
||||
"userPassword": "用户密码",
|
||||
"token": "标识",
|
||||
"tokenAuthFailedTitle": "认证问题",
|
||||
"tokenAuthFailed": "对不起,您未被允许参加此会议。",
|
||||
"displayNameRequired": "需要显示名称",
|
||||
"enterDisplayName": "请输入您的显示名称",
|
||||
"extensionRequired": "需要扩展程序",
|
||||
"firefoxExtensionPrompt": "您需要安装Firefox的扩展才能使用屏幕共享功能。请从<a href='__url__'>这里获取后</a>!重试。",
|
||||
"rateExperience": "请评价您的会议体验。",
|
||||
"feedbackHelp": "您的反馈将帮助我们提高我们的视频体验。",
|
||||
"feedbackQuestion": "告诉我们您的联系方式。",
|
||||
"thankYou": "感谢使用__appName__!",
|
||||
"sorryFeedback": "很抱歉听到这些,能告诉我们更多详细情况吗?",
|
||||
"liveStreaming": "流媒体直播中",
|
||||
"streamKey": "流 名称/关键字",
|
||||
"startLiveStreaming": "开始流媒体直播",
|
||||
"stopStreamingWarning": "确定要停止流媒体直播吗?",
|
||||
"stopRecordingWarning": "确定要停止录制吗",
|
||||
"stopLiveStreaming": "停止流媒体直播",
|
||||
"stopRecording": "停止录制",
|
||||
"doNotShowWarningAgain": "不再显示此警告",
|
||||
"doNotShowMessageAgain": "不再显示此信息",
|
||||
"permissionDenied": "许可禁止",
|
||||
"screenSharingPermissionDeniedError": "您并未授权分享屏幕。",
|
||||
"micErrorPresent": "连接到麦克风时发生错误。",
|
||||
"cameraErrorPresent": "连接到摄像头时发生错误。",
|
||||
"cameraUnsupportedResolutionError": "您的摄像头不支持所需分辨率。",
|
||||
"cameraUnknownError": "由于不可预知的错误,无法使用摄像头。",
|
||||
"cameraPermissionDeniedError": "您未授权使用您的摄像头。您仍可参加会议但是其他人无法看到,使用地址栏里的摄像头按钮来启动摄像头。",
|
||||
"cameraNotFoundError": "未发现摄像头",
|
||||
"cameraConstraintFailedError": "摄像头不可用",
|
||||
"micUnknownError": "未知错误,麦克风不可用",
|
||||
"micPermissionDeniedError": "您未授权使用麦克风,您仍可参加会议但是其他人无法听到,使用地址栏里的摄像头按钮来启动麦克风",
|
||||
"micNotFoundError": "未发现麦克风",
|
||||
"micConstraintFailedError": "麦克风不满足所要求的限制",
|
||||
"micNotSendingData": "麦克风无法使用,请从设置菜单选择其他设备或者重启应用",
|
||||
"cameraNotSendingData": "摄像头无法使用,请检查摄像头是否被占用,从设置菜单选择其他设备或者重启应用。",
|
||||
"goToStore": "跳转至应用商店",
|
||||
"externalInstallationTitle": "需要扩展程序",
|
||||
"externalInstallationMsg": "您需要安装桌面共享扩展",
|
||||
"muteParticipantTitle": "静音该与会者吗?",
|
||||
"muteParticipantBody": "您无法对他们解除静音,但是他们自己可以随时解除静音。",
|
||||
"muteParticipantButton": "静音",
|
||||
"remoteControlTitle": "远程控制",
|
||||
"remoteControlDeniedMessage": "__user__ 拒绝了您的远程控制请求",
|
||||
"remoteControlAllowedMessage": "__user__ 接受了您的远程控制请求",
|
||||
"remoteControlErrorMessage": "在尝试向__user__请求远程控制权限时发生了一个错误",
|
||||
"remoteControlStopMessage": "远程控制结束!"
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": "",
|
||||
"subject": "",
|
||||
"body": "",
|
||||
"and": ""
|
||||
"sharedKey": [
|
||||
"该会议受密码保护,请在加入会议时使用下列密码:",
|
||||
"",
|
||||
"",
|
||||
"__sharedKey__",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"subject": "邀请至__appName__ (__conferenceName__)",
|
||||
"body": [
|
||||
"嗨, 我想请你加入刚建立的__appName__这个会议。",
|
||||
"",
|
||||
"",
|
||||
"请点击下面的链接来加入会议。",
|
||||
"",
|
||||
"",
|
||||
"__roomUrl__",
|
||||
"",
|
||||
"",
|
||||
"__sharedKeyText__",
|
||||
" 请注意__appName__现在只支持下列浏览器:__supportedBrowsers__。",
|
||||
"",
|
||||
"",
|
||||
"马上就可以和你交流了!"
|
||||
],
|
||||
"and": "添加"
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "",
|
||||
"CONNECTING": "",
|
||||
"RECONNECTING": "",
|
||||
"CONNFAIL": "",
|
||||
"AUTHENTICATING": "",
|
||||
"AUTHFAIL": "",
|
||||
"CONNECTED": "",
|
||||
"DISCONNECTED": "",
|
||||
"DISCONNECTING": "",
|
||||
"ATTACHED": ""
|
||||
"ERROR": "错误",
|
||||
"CONNECTING": "连接中",
|
||||
"RECONNECTING": "网络错误,重连中。。。",
|
||||
"CONNFAIL": "连接失败",
|
||||
"AUTHENTICATING": "认证中",
|
||||
"AUTHFAIL": "认证失败",
|
||||
"CONNECTED": "已连接",
|
||||
"DISCONNECTED": "已断开连接",
|
||||
"DISCONNECTING": "断开连接中",
|
||||
"ATTACHED": "已接入"
|
||||
},
|
||||
"recording": {
|
||||
"pending": "",
|
||||
"on": "",
|
||||
"off": "",
|
||||
"failedToStart": "",
|
||||
"buttonTooltip": "",
|
||||
"error": "",
|
||||
"unavailable": ""
|
||||
"pending": "录制中,等待一位与会者加入",
|
||||
"on": "录制中",
|
||||
"off": "录制已停止",
|
||||
"failedToStart": "录制启动失败",
|
||||
"buttonTooltip": "开始 / 结束录制",
|
||||
"error": "录制失败。请重新尝试。",
|
||||
"unavailable": "录制服务目前不可用。请稍后再试。"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"pending": "",
|
||||
"on": "",
|
||||
"off": "",
|
||||
"unavailable": "",
|
||||
"failedToStart": "",
|
||||
"buttonTooltip": "",
|
||||
"streamIdRequired": "",
|
||||
"error": "",
|
||||
"busy": ""
|
||||
"pending": "启动流媒体。。。",
|
||||
"on": "流媒体直播中",
|
||||
"off": "流媒体直播已结束",
|
||||
"unavailable": "流媒体直播服务当前不可用,请稍后再试。",
|
||||
"failedToStart": "流媒体直播启动失败",
|
||||
"buttonTooltip": "启动 / 停止流媒体直播",
|
||||
"streamIdRequired": "请填写媒体流ID来启动流媒体直播。",
|
||||
"streamIdHelp": "在哪里找到这个",
|
||||
"error": "流媒体直播失败。请重试。",
|
||||
"busy": "所有的录制器都在忙。请稍后重试。"
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,15 @@
|
||||
"defaultLink": "e.g. __url__",
|
||||
"callingName": "__name__",
|
||||
"userMedia": {
|
||||
"react-nativeGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button",
|
||||
"chromeGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button",
|
||||
"androidGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button",
|
||||
"firefoxGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Share Selected Device</i></b> button",
|
||||
"operaGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button",
|
||||
"iexplorerGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>OK</i></b> button",
|
||||
"safariGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>OK</i></b> button",
|
||||
"nwjsGrantPermissions": "Please grant permissions to use your camera and microphone"
|
||||
"react-nativeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
|
||||
"chromeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
|
||||
"androidGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
|
||||
"firefoxGrantPermissions": "Select <b><i>Share Selected Device</i></b> when your browser asks for permissions.",
|
||||
"operaGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
|
||||
"iexplorerGrantPermissions": "Select <b><i>OK</i></b> when your browser asks for permissions.",
|
||||
"safariGrantPermissions": "Select <b><i>OK</i></b> when your browser asks for permissions.",
|
||||
"nwjsGrantPermissions": "Please grant permissions to use your camera and microphone",
|
||||
"edgeGrantPermissions": "Select <b><i>Yes</i></b> when your browser asks for permissions."
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
"keyboardShortcuts": "Keyboard shortcuts",
|
||||
@@ -36,7 +37,8 @@
|
||||
"toggleChat": "Open or close the chat",
|
||||
"mute": "Mute or unmute your microphone",
|
||||
"fullScreen": "Enter or exit full screen",
|
||||
"videoMute": "Start or stop your camera"
|
||||
"videoMute": "Start or stop your camera",
|
||||
"showSpeakerStats": "Show speaker stats"
|
||||
},
|
||||
"welcomepage":{
|
||||
"disable": "Don't show this page again",
|
||||
@@ -81,11 +83,12 @@
|
||||
"terms": "Terms"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
"policyText": " ",
|
||||
"title": "__app__ needs to use your microphone and camera."
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"title": "Your video call was interrupted, because this computer went to sleep.",
|
||||
"text": "Press <i>Rejoin</i> button to connect back to your conversation.",
|
||||
"rejoinKeyTitle": "Rejoin"
|
||||
},
|
||||
"toolbar": {
|
||||
@@ -177,6 +180,7 @@
|
||||
"bitrate": "Bitrate:",
|
||||
"packetloss": "Packet loss:",
|
||||
"resolution": "Resolution:",
|
||||
"framerate": "Frame rate:",
|
||||
"less": "Show less",
|
||||
"more": "Show more",
|
||||
"address": "Address:",
|
||||
@@ -189,8 +193,10 @@
|
||||
"remoteaddress": "Remote address:",
|
||||
"remoteaddress_plural": "Remote addresses:",
|
||||
"transport": "Transport:",
|
||||
"transport_plural": "Transports:",
|
||||
"bandwidth": "Estimated bandwidth:",
|
||||
"na": "Come back here for connection information once the conference starts"
|
||||
"na": "Come back here for connection information once the conference starts",
|
||||
"peer_to_peer": " (p2p)"
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "disconnected",
|
||||
@@ -201,7 +207,7 @@
|
||||
"focus": "Conference focus",
|
||||
"focusFail": "__component__ not available - retry in __ms__ sec",
|
||||
"grantedTo": "Moderator rights granted to __to__!",
|
||||
"grantedToUnknown": "Moderator rights granted to $t(somebody)!",
|
||||
"grantedToUnknown": "Moderator rights granted to $t(notify.somebody)!",
|
||||
"muted": "You have started the conversation muted.",
|
||||
"mutedTitle": "You're muted!",
|
||||
"raisedHand": "Would like to speak."
|
||||
@@ -225,12 +231,11 @@
|
||||
"detectext": "Error when trying to detect desktopsharing extension.",
|
||||
"failtoinstall": "Failed to install desktop sharing extension",
|
||||
"failedpermissions": "Failed to obtain permissions to use the local microphone and/or camera.",
|
||||
"conferenceReloadTitle": "Unfortunately, something went wrong",
|
||||
"conferenceReloadMsg": "We're trying to fix this",
|
||||
"conferenceDisconnectTitle": "You have been disconnected. You may want to check your network connection.",
|
||||
"conferenceDisconnectMsg": "Reconnecting in...",
|
||||
"reconnectNow": "Reconnect now",
|
||||
"conferenceReloadTimeLeft": "__seconds__ sec.",
|
||||
"conferenceReloadTitle": "Unfortunately, something went wrong.",
|
||||
"conferenceReloadMsg": "We're trying to fix this. Reconnecting in __seconds__ sec...",
|
||||
"conferenceDisconnectTitle": "You have been disconnected.",
|
||||
"conferenceDisconnectMsg": "You may want to check your network connection. Reconnecting in __seconds__ sec...",
|
||||
"rejoinNow": "Rejoin now",
|
||||
"maxUsersLimitReached": "The limit for maximum number of participants in the conference has been reached. The conference is full. Please try again later!",
|
||||
"lockTitle": "Lock failed",
|
||||
"lockMessage": "Failed to lock the conference.",
|
||||
@@ -316,11 +321,11 @@
|
||||
"cameraUnknownError": "Cannot use camera for a unknown reason.",
|
||||
"cameraPermissionDeniedError": "You have not granted permission to use your camera. You can still join the conference but others won't see you. Use the camera button in the address bar to fix this.",
|
||||
"cameraNotFoundError": "Camera was not found.",
|
||||
"cameraConstraintFailedError": "Yor camera does not satisfy some of required constraints.",
|
||||
"cameraConstraintFailedError": "Your camera does not satisfy some of the required constraints.",
|
||||
"micUnknownError": "Cannot use microphone for a unknown reason.",
|
||||
"micPermissionDeniedError": "You have not granted permission to use your microphone. You can still join the conference but others won't hear you. Use the camera button in the address bar to fix this.",
|
||||
"micNotFoundError": "Microphone was not found.",
|
||||
"micConstraintFailedError": "Yor microphone does not satisfy some of required constraints.",
|
||||
"micConstraintFailedError": "Your microphone does not satisfy some of the required constraints.",
|
||||
"micNotSendingData": "We are unable to access your microphone. Please select another device from the settings menu or try to restart the application.",
|
||||
"cameraNotSendingData": "We are unable to access your camera. Please check if another application is using this device, select another device from the settings menu or try to restart the application.",
|
||||
"goToStore": "Go to the webstore",
|
||||
@@ -333,7 +338,11 @@
|
||||
"remoteControlDeniedMessage": "__user__ rejected your remote control request!",
|
||||
"remoteControlAllowedMessage": "__user__ accepted your remote control request!",
|
||||
"remoteControlErrorMessage": "An error occurred while trying to request remote control permissions from __user__!",
|
||||
"remoteControlStopMessage": "The remote control session ended!"
|
||||
"remoteControlStopMessage": "The remote control session ended!",
|
||||
"close": "Close",
|
||||
"shareYourScreen": "Share your screen",
|
||||
"yourEntireScreen": "Your entire screen",
|
||||
"applicationWindow": "Application window"
|
||||
},
|
||||
"email":
|
||||
{
|
||||
@@ -378,7 +387,9 @@
|
||||
"FETCH_SESSION_ID": "Obtaining session-id...",
|
||||
"GOT_SESSION_ID": "Obtaining session-id... Done",
|
||||
"GET_SESSION_ID_ERROR": "Get session-id error: __code__",
|
||||
"USER_CONNECTION_INTERRUPTED": "__displayName__ is having connectivity issues..."
|
||||
"USER_CONNECTION_INTERRUPTED": "__displayName__ is having connectivity issues...",
|
||||
"LOW_BANDWIDTH": "Video for __displayName__ has been turned off to save bandwidth"
|
||||
|
||||
},
|
||||
"recording":
|
||||
{
|
||||
@@ -402,5 +413,21 @@
|
||||
"streamIdHelp": "Where do I find this?",
|
||||
"error": "Live streaming failed. Please try again.",
|
||||
"busy": "All recorders are currently busy. Please try again later."
|
||||
},
|
||||
"speakerStats":
|
||||
{
|
||||
"hours": "__count__h",
|
||||
"minutes": "__count__m",
|
||||
"name": "Name",
|
||||
"seconds": "__count__s",
|
||||
"speakerStats": "Speaker Stats",
|
||||
"speakerTime": "Speaker Time"
|
||||
},
|
||||
"deviceSelection": {
|
||||
"currentlyVideoMuted": "Video is currently muted",
|
||||
"deviceSettings": "Device settings",
|
||||
"noOtherDevices": "No other devices available",
|
||||
"selectADevice": "Select a device",
|
||||
"testAudio": "Test sound"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,6 @@ var loggingConfig = { // eslint-disable-line no-unused-vars
|
||||
// Option to disable LogCollector (which stores the logs on CallStats)
|
||||
//disableLogCollector: true,
|
||||
// Logging level adjustments for verbose modules:
|
||||
'modules/xmpp/strophe.util.js': 'log'
|
||||
'modules/xmpp/strophe.util.js': 'log',
|
||||
'modules/statistics/CallStats.js': 'info'
|
||||
};
|
||||
3
modules/API/.eslintrc.js
Normal file
3
modules/API/.eslintrc.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
'extends': '../../react/.eslintrc.js'
|
||||
};
|
||||
@@ -1,239 +1,308 @@
|
||||
/* global APP, getConfigParamsFromUrl */
|
||||
|
||||
/**
|
||||
* Implements API class that communicates with external api class
|
||||
* and provides interface to access Jitsi Meet features by external
|
||||
* applications that embed Jitsi Meet
|
||||
*/
|
||||
|
||||
import postisInit from 'postis';
|
||||
|
||||
import * as JitsiMeetConferenceEvents from '../../ConferenceEvents';
|
||||
|
||||
/**
|
||||
* List of the available commands.
|
||||
* @type {{
|
||||
* displayName: inputDisplayNameHandler,
|
||||
* toggleAudio: toggleAudio,
|
||||
* toggleVideo: toggleVideo,
|
||||
* toggleFilmStrip: toggleFilmStrip,
|
||||
* toggleChat: toggleChat,
|
||||
* toggleContactList: toggleContactList
|
||||
* }}
|
||||
*/
|
||||
let commands = {};
|
||||
|
||||
let hashParams = getConfigParamsFromUrl();
|
||||
/**
|
||||
* The state of screen sharing(started/stopped) before the screen sharing is
|
||||
* enabled and initialized.
|
||||
* NOTE: This flag help us to cache the state and use it if toggle-share-screen
|
||||
* was received before the initialization.
|
||||
*/
|
||||
let initialScreenSharingState = false;
|
||||
|
||||
/**
|
||||
* JitsiMeetExternalAPI id - unique for a webpage.
|
||||
*/
|
||||
let jitsi_meet_external_api_id = hashParams.jitsi_meet_external_api_id;
|
||||
const jitsiMeetExternalApiId
|
||||
= getConfigParamsFromUrl().jitsi_meet_external_api_id;
|
||||
|
||||
/**
|
||||
* Object that will execute sendMessage
|
||||
*/
|
||||
let target = window.opener ? window.opener : window.parent;
|
||||
|
||||
/**
|
||||
* Postis instance. Used to communicate with the external application.
|
||||
* Postis instance. Used to communicate with the external application. If
|
||||
* undefined, then API is disabled.
|
||||
*/
|
||||
let postis;
|
||||
|
||||
/**
|
||||
* Current status (enabled/disabled) of API.
|
||||
* Object that will execute sendMessage.
|
||||
*/
|
||||
let enabled = false;
|
||||
const target = window.opener || window.parent;
|
||||
|
||||
/**
|
||||
* Initializes supported commands.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function initCommands() {
|
||||
commands = {
|
||||
"display-name": APP.UI.inputDisplayNameHandler,
|
||||
"toggle-audio": APP.conference.toggleAudioMuted.bind(APP.conference),
|
||||
"toggle-video": APP.conference.toggleVideoMuted.bind(APP.conference),
|
||||
"toggle-film-strip": APP.UI.toggleFilmStrip,
|
||||
"toggle-chat": APP.UI.toggleChat,
|
||||
"toggle-contact-list": APP.UI.toggleContactList,
|
||||
"toggle-share-screen":
|
||||
APP.conference.toggleScreenSharing.bind(APP.conference),
|
||||
"video-hangup": () => APP.conference.hangup(),
|
||||
"email": APP.conference.changeLocalEmail,
|
||||
"avatar-url": APP.conference.changeLocalAvatarUrl,
|
||||
"remote-control-event": event =>
|
||||
APP.remoteControl.onRemoteControlAPIEvent(event)
|
||||
'display-name':
|
||||
APP.conference.changeLocalDisplayName.bind(APP.conference),
|
||||
'toggle-audio': () => APP.conference.toggleAudioMuted(true),
|
||||
'toggle-video': () => APP.conference.toggleVideoMuted(true),
|
||||
'toggle-film-strip': APP.UI.toggleFilmstrip,
|
||||
'toggle-chat': APP.UI.toggleChat,
|
||||
'toggle-contact-list': APP.UI.toggleContactList,
|
||||
'toggle-share-screen': toggleScreenSharing,
|
||||
'video-hangup': () => APP.conference.hangup(),
|
||||
'email': APP.conference.changeLocalEmail,
|
||||
'avatar-url': APP.conference.changeLocalAvatarUrl,
|
||||
'remote-control-event':
|
||||
event => APP.remoteControl.onRemoteControlAPIEvent(event)
|
||||
};
|
||||
Object.keys(commands).forEach(function (key) {
|
||||
postis.listen(key, args => commands[key](...args));
|
||||
});
|
||||
Object.keys(commands).forEach(
|
||||
key => postis.listen(key, args => commands[key](...args)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens for desktop/screen sharing enabled events and toggles the screen
|
||||
* sharing if needed.
|
||||
*
|
||||
* @param {boolean} enabled - Current screen sharing enabled status.
|
||||
* @returns {void}
|
||||
*/
|
||||
function onDesktopSharingEnabledChanged(enabled = false) {
|
||||
if (enabled && initialScreenSharingState) {
|
||||
toggleScreenSharing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends message to the external application.
|
||||
* @param message {object}
|
||||
* @param method {string}
|
||||
* @param params {object} the object that will be sent as JSON string
|
||||
*
|
||||
* @param {Object} message - The message to be sent.
|
||||
* @returns {void}
|
||||
*/
|
||||
function sendMessage(message) {
|
||||
if(enabled) {
|
||||
if (postis) {
|
||||
postis.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the API should be enabled or not.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function shouldBeEnabled () {
|
||||
return (typeof jitsi_meet_external_api_id === "number");
|
||||
function shouldBeEnabled() {
|
||||
return typeof jitsiMeetExternalApiId === 'number';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends event object to the external application that has been subscribed
|
||||
* for that event.
|
||||
* @param name the name event
|
||||
* @param object data associated with the event
|
||||
* Executes on toggle-share-screen command.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function triggerEvent (name, object) {
|
||||
if(enabled) {
|
||||
sendMessage({method: name, params: object});
|
||||
function toggleScreenSharing() {
|
||||
if (APP.conference.isDesktopSharingEnabled) {
|
||||
APP.conference.toggleScreenSharing();
|
||||
} else {
|
||||
initialScreenSharingState = !initialScreenSharingState;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends event object to the external application that has been subscribed for
|
||||
* that event.
|
||||
*
|
||||
* @param {string} name - The name event.
|
||||
* @param {Object} object - Data associated with the event.
|
||||
* @returns {void}
|
||||
*/
|
||||
function triggerEvent(name, object) {
|
||||
sendMessage({
|
||||
method: name,
|
||||
params: object
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements API class that communicates with external API class and provides
|
||||
* interface to access Jitsi Meet features by external applications that embed
|
||||
* Jitsi Meet.
|
||||
*/
|
||||
class API {
|
||||
/**
|
||||
* Constructs new instance
|
||||
* @constructor
|
||||
* Initializes the API. Setups message event listeners that will receive
|
||||
* information from external applications that embed Jitsi Meet. It also
|
||||
* sends a message to the external application that API is initialized.
|
||||
*
|
||||
* @param {Object} options - Optional parameters.
|
||||
* @param {boolean} options.forceEnable - True to forcefully enable the
|
||||
* module.
|
||||
* @returns {void}
|
||||
*/
|
||||
constructor() { }
|
||||
|
||||
/**
|
||||
* Initializes the APIConnector. Setups message event listeners that will
|
||||
* receive information from external applications that embed Jitsi Meet.
|
||||
* It also sends a message to the external application that APIConnector
|
||||
* is initialized.
|
||||
* @param options {object}
|
||||
* @param forceEnable {boolean} if true the module will be enabled.
|
||||
*/
|
||||
init (options = {}) {
|
||||
if(!shouldBeEnabled() && !options.forceEnable)
|
||||
init(options = {}) {
|
||||
if (!shouldBeEnabled() && !options.forceEnable) {
|
||||
return;
|
||||
}
|
||||
|
||||
enabled = true;
|
||||
|
||||
if(!postis) {
|
||||
if (!postis) {
|
||||
APP.conference.addListener(
|
||||
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
|
||||
onDesktopSharingEnabledChanged);
|
||||
this._initPostis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* initializes postis library.
|
||||
* Initializes postis library.
|
||||
*
|
||||
* @returns {void}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_initPostis() {
|
||||
let postisOptions = {
|
||||
const postisOptions = {
|
||||
window: target
|
||||
};
|
||||
if(typeof jitsi_meet_external_api_id === "number")
|
||||
|
||||
if (typeof jitsiMeetExternalApiId === 'number') {
|
||||
postisOptions.scope
|
||||
= "jitsi_meet_external_api_" + jitsi_meet_external_api_id;
|
||||
= `jitsi_meet_external_api_${jitsiMeetExternalApiId}`;
|
||||
}
|
||||
postis = postisInit(postisOptions);
|
||||
initCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that message was sent.
|
||||
* @param {string} body message body
|
||||
*
|
||||
* @param {string} body - Message body.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifySendingChatMessage (body) {
|
||||
triggerEvent("outgoing-message", {"message": body});
|
||||
notifySendingChatMessage(body) {
|
||||
triggerEvent('outgoing-message', { 'message': body });
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that
|
||||
* message was received.
|
||||
* @param {string} id user id
|
||||
* @param {string} nick user nickname
|
||||
* @param {string} body message body
|
||||
* @param {number} ts message creation timestamp
|
||||
* Notify external application (if API is enabled) that message was
|
||||
* received.
|
||||
*
|
||||
* @param {Object} options - Object with the message properties.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyReceivedChatMessage (id, nick, body, ts) {
|
||||
notifyReceivedChatMessage(options = {}) {
|
||||
const { id, nick, body, ts } = options;
|
||||
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerEvent(
|
||||
"incoming-message",
|
||||
{"from": id, "nick": nick, "message": body, "stamp": ts}
|
||||
);
|
||||
'incoming-message',
|
||||
{
|
||||
'from': id,
|
||||
'message': body,
|
||||
'nick': nick,
|
||||
'stamp': ts
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that
|
||||
* user joined the conference.
|
||||
* @param {string} id user id
|
||||
* Notify external application (if API is enabled) that user joined the
|
||||
* conference.
|
||||
*
|
||||
* @param {string} id - User id.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyUserJoined (id) {
|
||||
triggerEvent("participant-joined", {id});
|
||||
notifyUserJoined(id) {
|
||||
triggerEvent('participant-joined', { id });
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that
|
||||
* user left the conference.
|
||||
* @param {string} id user id
|
||||
* Notify external application (if API is enabled) that user left the
|
||||
* conference.
|
||||
*
|
||||
* @param {string} id - User id.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyUserLeft (id) {
|
||||
triggerEvent("participant-left", {id});
|
||||
notifyUserLeft(id) {
|
||||
triggerEvent('participant-left', { id });
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that
|
||||
* user changed their nickname.
|
||||
* @param {string} id user id
|
||||
* @param {string} displayName user nickname
|
||||
* Notify external application (if API is enabled) that user changed their
|
||||
* nickname.
|
||||
*
|
||||
* @param {string} id - User id.
|
||||
* @param {string} displayName - User nickname.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyDisplayNameChanged (id, displayName) {
|
||||
triggerEvent("display-name-change", {id, displayname: displayName});
|
||||
notifyDisplayNameChanged(id, displayName) {
|
||||
triggerEvent(
|
||||
'display-name-change',
|
||||
{
|
||||
displayname: displayName,
|
||||
id
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that
|
||||
* user changed their nickname.
|
||||
* @param {string} id user id
|
||||
* @param {string} displayName user nickname
|
||||
* Notify external application (if API is enabled) that the conference has
|
||||
* been joined.
|
||||
*
|
||||
* @param {string} room - The room name.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyConferenceJoined (room) {
|
||||
triggerEvent("video-conference-joined", {roomName: room});
|
||||
notifyConferenceJoined(room) {
|
||||
triggerEvent('video-conference-joined', { roomName: room });
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that
|
||||
* user changed their nickname.
|
||||
* @param {string} id user id
|
||||
* @param {string} displayName user nickname
|
||||
* Notify external application (if API is enabled) that user changed their
|
||||
* nickname.
|
||||
*
|
||||
* @param {string} room - User id.
|
||||
* @param {string} displayName - User nickname.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyConferenceLeft (room) {
|
||||
triggerEvent("video-conference-left", {roomName: room});
|
||||
notifyConferenceLeft(room) {
|
||||
triggerEvent('video-conference-left', { roomName: room });
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that
|
||||
* we are ready to be closed.
|
||||
* Notify external application (if API is enabled) that we are ready to be
|
||||
* closed.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyReadyToClose () {
|
||||
triggerEvent("video-ready-to-close", {});
|
||||
notifyReadyToClose() {
|
||||
triggerEvent('video-ready-to-close', {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends remote control event.
|
||||
* @param {RemoteControlEvent} event the remote control event.
|
||||
*
|
||||
* @param {RemoteControlEvent} event - The remote control event.
|
||||
* @returns {void}
|
||||
*/
|
||||
sendRemoteControlEvent(event) {
|
||||
sendMessage({method: "remote-control-event", params: event});
|
||||
sendMessage({
|
||||
method: 'remote-control-event',
|
||||
params: event
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listeners.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
dispose () {
|
||||
if(enabled)
|
||||
dispose() {
|
||||
if (postis) {
|
||||
postis.destroy();
|
||||
postis = undefined;
|
||||
|
||||
APP.conference.removeListener(
|
||||
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
|
||||
onDesktopSharingEnabledChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
700
modules/API/external/external_api.js
vendored
700
modules/API/external/external_api.js
vendored
@@ -1,386 +1,418 @@
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
import EventEmitter from 'events';
|
||||
import postisInit from 'postis';
|
||||
|
||||
/**
|
||||
* Implements API class that embeds Jitsi Meet in external applications.
|
||||
*/
|
||||
|
||||
var postisInit = require("postis");
|
||||
|
||||
/**
|
||||
* The minimum width for the Jitsi Meet frame
|
||||
* @type {number}
|
||||
*/
|
||||
var MIN_WIDTH = 790;
|
||||
|
||||
/**
|
||||
* The minimum height for the Jitsi Meet frame
|
||||
* @type {number}
|
||||
*/
|
||||
var MIN_HEIGHT = 300;
|
||||
|
||||
/**
|
||||
* Last id of api object
|
||||
* @type {number}
|
||||
*/
|
||||
var id = 0;
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Maps the names of the commands expected by the API with the name of the
|
||||
* commands expected by jitsi-meet
|
||||
*/
|
||||
var commands = {
|
||||
"displayName": "display-name",
|
||||
"toggleAudio": "toggle-audio",
|
||||
"toggleVideo": "toggle-video",
|
||||
"toggleFilmStrip": "toggle-film-strip",
|
||||
"toggleChat": "toggle-chat",
|
||||
"toggleContactList": "toggle-contact-list",
|
||||
"toggleShareScreen": "toggle-share-screen",
|
||||
"hangup": "video-hangup",
|
||||
"email": "email",
|
||||
"avatarUrl": "avatar-url"
|
||||
const commands = {
|
||||
avatarUrl: 'avatar-url',
|
||||
displayName: 'display-name',
|
||||
email: 'email',
|
||||
hangup: 'video-hangup',
|
||||
toggleAudio: 'toggle-audio',
|
||||
toggleChat: 'toggle-chat',
|
||||
toggleContactList: 'toggle-contact-list',
|
||||
toggleFilmStrip: 'toggle-film-strip',
|
||||
toggleShareScreen: 'toggle-share-screen',
|
||||
toggleVideo: 'toggle-video'
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps the names of the events expected by the API with the name of the
|
||||
* events expected by jitsi-meet
|
||||
*/
|
||||
var events = {
|
||||
"incomingMessage": "incoming-message",
|
||||
"outgoingMessage": "outgoing-message",
|
||||
"displayNameChange": "display-name-change",
|
||||
"participantJoined": "participant-joined",
|
||||
"participantLeft": "participant-left",
|
||||
"videoConferenceJoined": "video-conference-joined",
|
||||
"videoConferenceLeft": "video-conference-left",
|
||||
"readyToClose": "video-ready-to-close"
|
||||
const events = {
|
||||
displayNameChange: 'display-name-change',
|
||||
incomingMessage: 'incoming-message',
|
||||
outgoingMessage: 'outgoing-message',
|
||||
participantJoined: 'participant-joined',
|
||||
participantLeft: 'participant-left',
|
||||
readyToClose: 'video-ready-to-close',
|
||||
videoConferenceJoined: 'video-conference-joined',
|
||||
videoConferenceLeft: 'video-conference-left'
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends the passed object to Jitsi Meet
|
||||
* @param postis {Postis object} the postis instance that is going to be used
|
||||
* to send the message
|
||||
* @param object the object to be sent
|
||||
* - method {sting}
|
||||
* - params {object}
|
||||
* Last id of api object
|
||||
* @type {number}
|
||||
*/
|
||||
function sendMessage(postis, object) {
|
||||
postis.send(object);
|
||||
}
|
||||
let id = 0;
|
||||
|
||||
/**
|
||||
* The minimum height for the Jitsi Meet frame
|
||||
* @type {number}
|
||||
*/
|
||||
const MIN_HEIGHT = 300;
|
||||
|
||||
/**
|
||||
* The minimum width for the Jitsi Meet frame
|
||||
* @type {number}
|
||||
*/
|
||||
const MIN_WIDTH = 790;
|
||||
|
||||
/**
|
||||
* Adds given number to the numberOfParticipants property of given APIInstance.
|
||||
* @param {JitsiMeetExternalAPI} APIInstance the instance of the
|
||||
* JitsiMeetExternalAPI
|
||||
* @param {int} number - the number of participants to be added to
|
||||
*
|
||||
* @param {JitsiMeetExternalAPI} APIInstance - The instance of the API.
|
||||
* @param {int} number - The number of participants to be added to
|
||||
* numberOfParticipants property (this parameter can be negative number if the
|
||||
* numberOfParticipants should be decreased).
|
||||
* @returns {void}
|
||||
*/
|
||||
function changeParticipantNumber(APIInstance, number) {
|
||||
APIInstance.numberOfParticipants += number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new API instance. Creates iframe element that loads
|
||||
* Jitsi Meet.
|
||||
* @param domain the domain name of the server that hosts the conference
|
||||
* @param room_name the name of the room to join
|
||||
* @param width width of the iframe
|
||||
* @param height height of the iframe
|
||||
* @param parent_node the node that will contain the iframe
|
||||
* @param configOverwrite object containing configuration options defined in
|
||||
* config.js to be overridden.
|
||||
* @param interfaceConfigOverwrite object containing configuration options
|
||||
* defined in interface_config.js to be overridden.
|
||||
* @param noSsl if the value is true https won't be used
|
||||
* @constructor
|
||||
* Generates array with URL params based on the passed config object that will
|
||||
* be used for the Jitsi Meet URL generation.
|
||||
*
|
||||
* @param {Object} config - The config object.
|
||||
* @returns {Array<string>} The array with URL param strings.
|
||||
*/
|
||||
function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
|
||||
configOverwrite, interfaceConfigOverwrite, noSsl) {
|
||||
if (!width || width < MIN_WIDTH)
|
||||
width = MIN_WIDTH;
|
||||
if (!height || height < MIN_HEIGHT)
|
||||
height = MIN_HEIGHT;
|
||||
function configToURLParamsArray(config = {}) {
|
||||
const params = [];
|
||||
|
||||
this.parentNode = null;
|
||||
if (parentNode) {
|
||||
this.parentNode = parentNode;
|
||||
} else {
|
||||
var scriptTag = document.scripts[document.scripts.length - 1];
|
||||
this.parentNode = scriptTag.parentNode;
|
||||
}
|
||||
|
||||
this.iframeHolder =
|
||||
this.parentNode.appendChild(document.createElement("div"));
|
||||
this.iframeHolder.id = "jitsiConference" + id;
|
||||
if(width)
|
||||
this.iframeHolder.style.width = width + "px";
|
||||
if(height)
|
||||
this.iframeHolder.style.height = height + "px";
|
||||
this.frameName = "jitsiConferenceFrame" + id;
|
||||
this.url = (noSsl) ? "http" : "https" +"://" + domain + "/";
|
||||
if(room_name)
|
||||
this.url += room_name;
|
||||
this.url += "#jitsi_meet_external_api_id=" + id;
|
||||
|
||||
var key;
|
||||
if (configOverwrite) {
|
||||
for (key in configOverwrite) {
|
||||
if (!configOverwrite.hasOwnProperty(key) ||
|
||||
typeof key !== 'string')
|
||||
continue;
|
||||
this.url += "&config." + key + "=" + configOverwrite[key];
|
||||
for (const key in config) { // eslint-disable-line guard-for-in
|
||||
try {
|
||||
params.push(`${key}=${
|
||||
encodeURIComponent(JSON.stringify(config[key]))}`);
|
||||
} catch (e) {
|
||||
console.warn(`Error encoding ${key}: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (interfaceConfigOverwrite) {
|
||||
for (key in interfaceConfigOverwrite) {
|
||||
if (!interfaceConfigOverwrite.hasOwnProperty(key) ||
|
||||
typeof key !== 'string')
|
||||
continue;
|
||||
this.url += "&interfaceConfig." + key + "=" +
|
||||
interfaceConfigOverwrite[key];
|
||||
}
|
||||
}
|
||||
|
||||
this.frame = document.createElement("iframe");
|
||||
this.frame.src = this.url;
|
||||
this.frame.name = this.frameName;
|
||||
this.frame.id = this.frameName;
|
||||
this.frame.width = "100%";
|
||||
this.frame.height = "100%";
|
||||
this.frame.setAttribute("allowFullScreen","true");
|
||||
this.frame = this.iframeHolder.appendChild(this.frame);
|
||||
this.postis = postisInit({
|
||||
window: this.frame.contentWindow,
|
||||
scope: "jitsi_meet_external_api_" + id
|
||||
});
|
||||
|
||||
this.eventHandlers = {};
|
||||
|
||||
// Map<{string} event_name, {boolean} postis_listener_added>
|
||||
this.postisListeners = {};
|
||||
|
||||
this.numberOfParticipants = 1;
|
||||
this._setupListeners();
|
||||
|
||||
id++;
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes command. The available commands are:
|
||||
* displayName - sets the display name of the local participant to the value
|
||||
* passed in the arguments array.
|
||||
* 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.
|
||||
* @param name the name of the command
|
||||
* @param arguments array of arguments
|
||||
* Generates the URL for the iframe.
|
||||
*
|
||||
* @param {string} domain - The domain name of the server that hosts the
|
||||
* conference.
|
||||
* @param {string} [options] - Another optional parameters.
|
||||
* @param {Object} [options.configOverwrite] - Object containing configuration
|
||||
* options defined in config.js to be overridden.
|
||||
* @param {Object} [options.interfaceConfigOverwrite] - Object containing
|
||||
* configuration options defined in interface_config.js to be overridden.
|
||||
* @param {string} [options.jwt] - The JWT token if needed by jitsi-meet for
|
||||
* authentication.
|
||||
* @param {boolean} [options.noSsl] - If the value is true https won't be used.
|
||||
* @param {string} [options.roomName] - The name of the room to join.
|
||||
* @returns {string} The URL.
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.executeCommand
|
||||
= function(name, ...argumentsList) {
|
||||
if(!(name in commands)) {
|
||||
logger.error("Not supported command name.");
|
||||
return;
|
||||
function generateURL(domain, options = {}) {
|
||||
const {
|
||||
configOverwrite,
|
||||
interfaceConfigOverwrite,
|
||||
jwt,
|
||||
noSSL,
|
||||
roomName
|
||||
} = options;
|
||||
|
||||
let url = `${noSSL ? 'http' : 'https'}://${domain}/${roomName || ''}`;
|
||||
|
||||
if (jwt) {
|
||||
url += `?jwt=${jwt}`;
|
||||
}
|
||||
sendMessage(this.postis, {method: commands[name], params: argumentsList});
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes commands. The available commands are:
|
||||
* displayName - sets the display name of the local participant to the value
|
||||
* passed in the arguments array.
|
||||
* toggleAudio - mutes / unmutes audio. no arguments
|
||||
* toggleVideo - mutes / unmutes video. no arguments
|
||||
* filmStrip - hides / shows the film strip. no arguments
|
||||
* toggleChat - hides / shows chat. no arguments.
|
||||
* toggleContactList - hides / shows contact list. no arguments.
|
||||
* toggleShareScreen - starts / stops screen sharing. 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
|
||||
* arguments for the command.
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.executeCommands = function(object) {
|
||||
for(var key in object)
|
||||
this.executeCommand(key, object[key]);
|
||||
};
|
||||
url += `#jitsi_meet_external_api_id=${id}`;
|
||||
|
||||
/**
|
||||
* Adds event listeners to Meet Jitsi. The object key should be the name of
|
||||
* the event and value - the listener.
|
||||
* Currently we support the following
|
||||
* events:
|
||||
* incomingMessage - receives event notifications about incoming
|
||||
* messages. The listener will receive object with the following structure:
|
||||
* {{
|
||||
* "from": from,//JID of the user that sent the message
|
||||
* "nick": nick,//the nickname of the user that sent the message
|
||||
* "message": txt//the text of the message
|
||||
* }}
|
||||
* outgoingMessage - receives event notifications about outgoing
|
||||
* messages. The listener will receive object with the following structure:
|
||||
* {{
|
||||
* "message": txt//the text of the message
|
||||
* }}
|
||||
* displayNameChanged - receives event notifications about display name
|
||||
* change. The listener will receive object with the following structure:
|
||||
* {{
|
||||
* jid: jid,//the JID of the participant that changed his display name
|
||||
* displayname: displayName //the new display name
|
||||
* }}
|
||||
* participantJoined - receives event notifications about new participant.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* jid: jid //the jid of the participant
|
||||
* }}
|
||||
* participantLeft - receives event notifications about the participant that
|
||||
* left the room.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* jid: jid //the jid of the participant
|
||||
* }}
|
||||
* video-conference-joined - receives event notifications about the local user
|
||||
* has successfully joined the video conference.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* roomName: room //the room name of the conference
|
||||
* }}
|
||||
* video-conference-left - receives event notifications about the local user
|
||||
* has left the video conference.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* roomName: room //the room name of the conference
|
||||
* }}
|
||||
* readyToClose - all hangup operations are completed and Jitsi Meet is ready
|
||||
* to be disposed.
|
||||
* @param object
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.addEventListeners = function(object) {
|
||||
for(var i in object)
|
||||
this.addEventListener(i, object[i]);
|
||||
};
|
||||
const configURLParams = configToURLParamsArray(configOverwrite);
|
||||
|
||||
/**
|
||||
* Adds event listeners to Meet Jitsi. Currently we support the following
|
||||
* events:
|
||||
* incomingMessage - receives event notifications about incoming
|
||||
* messages. The listener will receive object with the following structure:
|
||||
* {{
|
||||
* "from": from,//JID of the user that sent the message
|
||||
* "nick": nick,//the nickname of the user that sent the message
|
||||
* "message": txt//the text of the message
|
||||
* }}
|
||||
* outgoingMessage - receives event notifications about outgoing
|
||||
* messages. The listener will receive object with the following structure:
|
||||
* {{
|
||||
* "message": txt//the text of the message
|
||||
* }}
|
||||
* displayNameChanged - receives event notifications about display name
|
||||
* change. The listener will receive object with the following structure:
|
||||
* {{
|
||||
* jid: jid,//the JID of the participant that changed his display name
|
||||
* displayname: displayName //the new display name
|
||||
* }}
|
||||
* participantJoined - receives event notifications about new participant.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* jid: jid //the jid of the participant
|
||||
* }}
|
||||
* participantLeft - receives event notifications about participant the that
|
||||
* left the room.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* jid: jid //the jid of the participant
|
||||
* }}
|
||||
* video-conference-joined - receives event notifications fired when the local
|
||||
* user has joined the video conference.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* roomName: room //the room name of the conference
|
||||
* }}
|
||||
* video-conference-left - receives event notifications fired when the local
|
||||
* user has joined the video conference.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* roomName: room //the room name of the conference
|
||||
* }}
|
||||
* @param event the name of the event
|
||||
* @param listener the listener
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.addEventListener = function(event, listener) {
|
||||
if(!(event in events)) {
|
||||
logger.error("Not supported event name.");
|
||||
return;
|
||||
if (configURLParams.length) {
|
||||
url += `&config.${configURLParams.join('&config.')}`;
|
||||
}
|
||||
// We cannot remove listeners from postis that's why we are handling the
|
||||
// callback that way.
|
||||
if(!this.postisListeners[event]) {
|
||||
this.postis.listen(events[event], function(data) {
|
||||
if((event in this.eventHandlers) &&
|
||||
typeof this.eventHandlers[event] === "function")
|
||||
this.eventHandlers[event].call(null, data);
|
||||
}.bind(this));
|
||||
this.postisListeners[event] = true;
|
||||
|
||||
const interfaceConfigURLParams
|
||||
= configToURLParamsArray(interfaceConfigOverwrite);
|
||||
|
||||
if (interfaceConfigURLParams.length) {
|
||||
url += `&interfaceConfig.${
|
||||
interfaceConfigURLParams.join('&interfaceConfig.')}`;
|
||||
}
|
||||
this.eventHandlers[event] = listener;
|
||||
};
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes event listener.
|
||||
* @param event the name of the event.
|
||||
* The IFrame API interface class.
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.removeEventListener = function(event) {
|
||||
if(!(event in this.eventHandlers))
|
||||
{
|
||||
logger.error("The event " + event + " is not registered.");
|
||||
return;
|
||||
class JitsiMeetExternalAPI extends EventEmitter {
|
||||
/**
|
||||
* Constructs new API instance. Creates iframe and loads Jitsi Meet in it.
|
||||
*
|
||||
* @param {string} domain - The domain name of the server that hosts the
|
||||
* conference.
|
||||
* @param {string} [roomName] - The name of the room to join.
|
||||
* @param {number} [width] - Width of the iframe.
|
||||
* @param {number} [height] - Height of the iframe.
|
||||
* @param {DOMElement} [parentNode] - The node that will contain the
|
||||
* iframe.
|
||||
* @param {Object} [configOverwrite] - Object containing configuration
|
||||
* options defined in config.js to be overridden.
|
||||
* @param {Object} [interfaceConfigOverwrite] - Object containing
|
||||
* configuration options defined in interface_config.js to be overridden.
|
||||
* @param {boolean} [noSSL] - If the value is true https won't be used.
|
||||
* @param {string} [jwt] - The JWT token if needed by jitsi-meet for
|
||||
* authentication.
|
||||
*/
|
||||
constructor(domain, // eslint-disable-line max-params
|
||||
roomName = '',
|
||||
width = MIN_WIDTH,
|
||||
height = MIN_HEIGHT,
|
||||
parentNode = document.body,
|
||||
configOverwrite = {},
|
||||
interfaceConfigOverwrite = {},
|
||||
noSSL = false,
|
||||
jwt = undefined) {
|
||||
super();
|
||||
this.parentNode = parentNode;
|
||||
this.url = generateURL(domain, {
|
||||
configOverwrite,
|
||||
interfaceConfigOverwrite,
|
||||
jwt,
|
||||
noSSL,
|
||||
roomName
|
||||
});
|
||||
this._createIFrame(Math.max(height, MIN_HEIGHT),
|
||||
Math.max(width, MIN_WIDTH));
|
||||
this.postis = postisInit({
|
||||
scope: `jitsi_meet_external_api_${id}`,
|
||||
window: this.frame.contentWindow
|
||||
});
|
||||
this.numberOfParticipants = 1;
|
||||
this._setupListeners();
|
||||
id++;
|
||||
}
|
||||
delete this.eventHandlers[event];
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes event listeners.
|
||||
* @param events array with the names of the events.
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.removeEventListeners = function(events) {
|
||||
for(var i = 0; i < events.length; i++)
|
||||
this.removeEventListener(events[i]);
|
||||
};
|
||||
/**
|
||||
* Creates the iframe element.
|
||||
*
|
||||
* @param {number} height - The height of the iframe.
|
||||
* @param {number} width - The with of the iframe.
|
||||
* @returns {void}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_createIFrame(height, width) {
|
||||
this.iframeHolder
|
||||
= this.parentNode.appendChild(document.createElement('div'));
|
||||
this.iframeHolder.id = `jitsiConference${id}`;
|
||||
this.iframeHolder.style.width = `${width}px`;
|
||||
this.iframeHolder.style.height = `${height}px`;
|
||||
|
||||
/**
|
||||
* Returns the number of participants in the conference.
|
||||
* NOTE: the local participant is included.
|
||||
* @returns {int} the number of participants in the conference.
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.getNumberOfParticipants = function() {
|
||||
return this.numberOfParticipants;
|
||||
};
|
||||
this.frameName = `jitsiConferenceFrame${id}`;
|
||||
|
||||
/**
|
||||
* Setups listeners that are used internally for JitsiMeetExternalAPI.
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype._setupListeners = function() {
|
||||
this.postis.listen("participant-joined",
|
||||
changeParticipantNumber.bind(null, this, 1));
|
||||
this.postis.listen("participant-left",
|
||||
changeParticipantNumber.bind(null, this, -1));
|
||||
};
|
||||
this.frame = document.createElement('iframe');
|
||||
this.frame.src = this.url;
|
||||
this.frame.name = this.frameName;
|
||||
this.frame.id = this.frameName;
|
||||
this.frame.width = '100%';
|
||||
this.frame.height = '100%';
|
||||
this.frame.setAttribute('allowFullScreen', 'true');
|
||||
this.frame = this.iframeHolder.appendChild(this.frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listeners and removes the Jitsi Meet frame.
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.dispose = function() {
|
||||
this.postis.destroy();
|
||||
var frame = document.getElementById(this.frameName);
|
||||
if(frame)
|
||||
frame.src = 'about:blank';
|
||||
var self = this;
|
||||
window.setTimeout(function () {
|
||||
self.iframeHolder.removeChild(self.frame);
|
||||
self.iframeHolder.parentNode.removeChild(self.iframeHolder);
|
||||
}, 10);
|
||||
};
|
||||
/**
|
||||
* Setups listeners that are used internally for JitsiMeetExternalAPI.
|
||||
*
|
||||
* @returns {void}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_setupListeners() {
|
||||
this.postis.listen('participant-joined',
|
||||
changeParticipantNumber.bind(null, this, 1));
|
||||
this.postis.listen('participant-left',
|
||||
changeParticipantNumber.bind(null, this, -1));
|
||||
|
||||
for (const eventName in events) { // eslint-disable-line guard-for-in
|
||||
const postisMethod = events[eventName];
|
||||
|
||||
this.postis.listen(postisMethod,
|
||||
(...args) => this.emit(eventName, ...args));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds event listener to Meet Jitsi.
|
||||
*
|
||||
* @param {string} event - The name of the event.
|
||||
* @param {Function} listener - The listener.
|
||||
* @returns {void}
|
||||
*
|
||||
* @deprecated
|
||||
* NOTE: This method is not removed for backward comatability purposes.
|
||||
*/
|
||||
addEventListener(event, listener) {
|
||||
this.on(event, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds event listeners to Meet Jitsi.
|
||||
*
|
||||
* @param {Object} listeners - The object key should be the name of
|
||||
* the event and value - the listener.
|
||||
* Currently we support the following
|
||||
* events:
|
||||
* incomingMessage - receives event notifications about incoming
|
||||
* messages. The listener will receive object with the following structure:
|
||||
* {{
|
||||
* 'from': from,//JID of the user that sent the message
|
||||
* 'nick': nick,//the nickname of the user that sent the message
|
||||
* 'message': txt//the text of the message
|
||||
* }}
|
||||
* outgoingMessage - receives event notifications about outgoing
|
||||
* messages. The listener will receive object with the following structure:
|
||||
* {{
|
||||
* 'message': txt//the text of the message
|
||||
* }}
|
||||
* displayNameChanged - receives event notifications about display name
|
||||
* change. The listener will receive object with the following structure:
|
||||
* {{
|
||||
* jid: jid,//the JID of the participant that changed his display name
|
||||
* displayname: displayName //the new display name
|
||||
* }}
|
||||
* participantJoined - receives event notifications about new participant.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* jid: jid //the jid of the participant
|
||||
* }}
|
||||
* participantLeft - receives event notifications about the participant that
|
||||
* left the room.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* jid: jid //the jid of the participant
|
||||
* }}
|
||||
* video-conference-joined - receives event notifications about the local
|
||||
* user has successfully joined the video conference.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* roomName: room //the room name of the conference
|
||||
* }}
|
||||
* video-conference-left - receives event notifications about the local user
|
||||
* has left the video conference.
|
||||
* The listener will receive object with the following structure:
|
||||
* {{
|
||||
* roomName: room //the room name of the conference
|
||||
* }}
|
||||
* readyToClose - all hangup operations are completed and Jitsi Meet is
|
||||
* ready to be disposed.
|
||||
* @returns {void}
|
||||
*
|
||||
* @deprecated
|
||||
* NOTE: This method is not removed for backward comatability purposes.
|
||||
*/
|
||||
addEventListeners(listeners) {
|
||||
for (const event in listeners) { // eslint-disable-line guard-for-in
|
||||
this.addEventListener(event, listeners[event]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listeners and removes the Jitsi Meet frame.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
dispose() {
|
||||
const frame = document.getElementById(this.frameName);
|
||||
|
||||
this.postis.destroy();
|
||||
if (frame) {
|
||||
frame.src = 'about:blank';
|
||||
}
|
||||
window.setTimeout(() => {
|
||||
this.iframeHolder.removeChild(this.frame);
|
||||
this.iframeHolder.parentNode.removeChild(this.iframeHolder);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes command. The available commands are:
|
||||
* displayName - sets the display name of the local participant to the value
|
||||
* passed in the arguments array.
|
||||
* toggleAudio - mutes / unmutes audio with no arguments.
|
||||
* toggleVideo - mutes / unmutes video with no arguments.
|
||||
* toggleFilmStrip - hides / shows the filmstrip with no arguments.
|
||||
* If the command doesn't require any arguments the parameter should be set
|
||||
* to empty array or it may be omitted.
|
||||
*
|
||||
* @param {string} name - The name of the command.
|
||||
* @returns {void}
|
||||
*/
|
||||
executeCommand(name, ...args) {
|
||||
if (!(name in commands)) {
|
||||
logger.error('Not supported command name.');
|
||||
|
||||
return;
|
||||
}
|
||||
this.postis.send({
|
||||
method: commands[name],
|
||||
params: args
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes commands. The available commands are:
|
||||
* displayName - sets the display name of the local participant to the value
|
||||
* passed in the arguments array.
|
||||
* toggleAudio - mutes / unmutes audio. no arguments
|
||||
* toggleVideo - mutes / unmutes video. no arguments
|
||||
* toggleFilmStrip - hides / shows the filmstrip. no arguments
|
||||
* toggleChat - hides / shows chat. no arguments.
|
||||
* toggleContactList - hides / shows contact list. no arguments.
|
||||
* toggleShareScreen - starts / stops screen sharing. no arguments.
|
||||
*
|
||||
* @param {Object} commandList - The object with commands to be executed.
|
||||
* The keys of the object are the commands that will be executed and the
|
||||
* values are the arguments for the command.
|
||||
* @returns {void}
|
||||
*/
|
||||
executeCommands(commandList) {
|
||||
for (const key in commandList) { // eslint-disable-line guard-for-in
|
||||
this.executeCommand(key, commandList[key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of participants in the conference. The local
|
||||
* participant is included.
|
||||
*
|
||||
* @returns {int} The number of participants in the conference.
|
||||
*/
|
||||
getNumberOfParticipants() {
|
||||
return this.numberOfParticipants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes event listener.
|
||||
*
|
||||
* @param {string} event - The name of the event.
|
||||
* @returns {void}
|
||||
*
|
||||
* @deprecated
|
||||
* NOTE: This method is not removed for backward comatability purposes.
|
||||
*/
|
||||
removeEventListener(event) {
|
||||
this.removeListeners(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes event listeners.
|
||||
*
|
||||
* @param {Array<string>} eventList - Array with the names of the events.
|
||||
* @returns {void}
|
||||
*
|
||||
* @deprecated
|
||||
* NOTE: This method is not removed for backward comatability purposes.
|
||||
*/
|
||||
removeEventListeners(eventList) {
|
||||
eventList.forEach(event => this.removeEventListener(event));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JitsiMeetExternalAPI;
|
||||
|
||||
@@ -54,13 +54,13 @@ class State {
|
||||
this._propertyChangeCallback = propertyChangeCallback;
|
||||
}
|
||||
|
||||
get filmStripVisible () { return this._filmStripVisible; }
|
||||
get filmstripVisible () { return this._filmstripVisible; }
|
||||
|
||||
set filmStripVisible (b) {
|
||||
var oldValue = this._filmStripVisible;
|
||||
set filmstripVisible (b) {
|
||||
var oldValue = this._filmstripVisible;
|
||||
if (oldValue !== b) {
|
||||
this._filmStripVisible = b;
|
||||
this._firePropertyChange('filmStripVisible', oldValue, b);
|
||||
this._filmstripVisible = b;
|
||||
this._firePropertyChange('filmstripVisible', oldValue, b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class State {
|
||||
|
||||
/**
|
||||
* Represents the "Follow Me" feature which enables a moderator to
|
||||
* (partially) control the user experience/interface (e.g. film strip
|
||||
* (partially) control the user experience/interface (e.g. filmstrip
|
||||
* visibility) of (other) non-moderator particiapnts.
|
||||
*
|
||||
* @author Lyubomir Marinov
|
||||
@@ -143,7 +143,7 @@ class FollowMe {
|
||||
* @private
|
||||
*/
|
||||
_setFollowMeInitialState() {
|
||||
this._filmStripToggled.bind(this, this._UI.isFilmStripVisible());
|
||||
this._filmstripToggled.bind(this, this._UI.isFilmstripVisible());
|
||||
|
||||
var pinnedId = VideoLayout.getPinnedId();
|
||||
var isPinned = false;
|
||||
@@ -169,9 +169,9 @@ class FollowMe {
|
||||
* @private
|
||||
*/
|
||||
_addFollowMeListeners () {
|
||||
this.filmStripEventHandler = this._filmStripToggled.bind(this);
|
||||
this._UI.addListener(UIEvents.TOGGLED_FILM_STRIP,
|
||||
this.filmStripEventHandler);
|
||||
this.filmstripEventHandler = this._filmstripToggled.bind(this);
|
||||
this._UI.addListener(UIEvents.TOGGLED_FILMSTRIP,
|
||||
this.filmstripEventHandler);
|
||||
|
||||
var self = this;
|
||||
this.pinnedEndpointEventHandler = function (smallVideo, isPinned) {
|
||||
@@ -190,8 +190,8 @@ class FollowMe {
|
||||
* @private
|
||||
*/
|
||||
_removeFollowMeListeners () {
|
||||
this._UI.removeListener(UIEvents.TOGGLED_FILM_STRIP,
|
||||
this.filmStripEventHandler);
|
||||
this._UI.removeListener(UIEvents.TOGGLED_FILMSTRIP,
|
||||
this.filmstripEventHandler);
|
||||
this._UI.removeListener(UIEvents.TOGGLED_SHARED_DOCUMENT,
|
||||
this.sharedDocEventHandler);
|
||||
this._UI.removeListener(UIEvents.PINNED_ENDPOINT,
|
||||
@@ -214,14 +214,14 @@ class FollowMe {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this instance that the (visibility of the) film strip was
|
||||
* Notifies this instance that the (visibility of the) filmstrip was
|
||||
* toggled (in the user interface of the local participant).
|
||||
*
|
||||
* @param filmStripVisible {Boolean} {true} if the film strip was shown (as
|
||||
* a result of the toggle) or {false} if the film strip was hidden
|
||||
* @param filmstripVisible {Boolean} {true} if the filmstrip was shown (as a
|
||||
* result of the toggle) or {false} if the filmstrip was hidden
|
||||
*/
|
||||
_filmStripToggled (filmStripVisible) {
|
||||
this._local.filmStripVisible = filmStripVisible;
|
||||
_filmstripToggled (filmstripVisible) {
|
||||
this._local.filmstripVisible = filmstripVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,7 +279,7 @@ class FollowMe {
|
||||
_COMMAND,
|
||||
{
|
||||
attributes: {
|
||||
filmStripVisible: local.filmStripVisible,
|
||||
filmstripVisible: local.filmstripVisible,
|
||||
nextOnStage: local.nextOnStage,
|
||||
sharedDocumentVisible: local.sharedDocumentVisible
|
||||
}
|
||||
@@ -316,32 +316,32 @@ class FollowMe {
|
||||
|
||||
// Applies the received/remote command to the user experience/interface
|
||||
// of the local participant.
|
||||
this._onFilmStripVisible(attributes.filmStripVisible);
|
||||
this._onFilmstripVisible(attributes.filmstripVisible);
|
||||
this._onNextOnStage(attributes.nextOnStage);
|
||||
this._onSharedDocumentVisible(attributes.sharedDocumentVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a film strip open / close event received from FOLLOW-ME
|
||||
* Process a filmstrip open / close event received from FOLLOW-ME
|
||||
* command.
|
||||
* @param filmStripVisible indicates if the film strip has been shown or
|
||||
* @param filmstripVisible indicates if the filmstrip has been shown or
|
||||
* hidden
|
||||
* @private
|
||||
*/
|
||||
_onFilmStripVisible(filmStripVisible) {
|
||||
if (typeof filmStripVisible !== 'undefined') {
|
||||
_onFilmstripVisible(filmstripVisible) {
|
||||
if (typeof filmstripVisible !== 'undefined') {
|
||||
// XXX The Command(s) API doesn't preserve the types (of
|
||||
// attributes, at least) at the time of this writing so take into
|
||||
// account that what originated as a Boolean may be a String on
|
||||
// receipt.
|
||||
filmStripVisible = (filmStripVisible == 'true');
|
||||
filmstripVisible = (filmstripVisible == 'true');
|
||||
|
||||
// FIXME The UI (module) very likely doesn't (want to) expose its
|
||||
// eventEmitter as a public field. I'm not sure at the time of this
|
||||
// writing whether calling UI.toggleFilmStrip() is acceptable (from
|
||||
// writing whether calling UI.toggleFilmstrip() is acceptable (from
|
||||
// a design standpoint) either.
|
||||
if (filmStripVisible !== this._UI.isFilmStripVisible())
|
||||
this._UI.eventEmitter.emit(UIEvents.TOGGLE_FILM_STRIP);
|
||||
if (filmstripVisible !== this._UI.isFilmstripVisible())
|
||||
this._UI.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
210
modules/UI/UI.js
210
modules/UI/UI.js
@@ -4,10 +4,12 @@ const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
var UI = {};
|
||||
|
||||
import {
|
||||
updateDeviceList
|
||||
} from '../../react/features/base/devices';
|
||||
|
||||
import Chat from "./side_pannels/chat/Chat";
|
||||
import SidePanels from "./side_pannels/SidePanels";
|
||||
import Toolbar from "./toolbars/Toolbar";
|
||||
import ToolbarToggler from "./toolbars/ToolbarToggler";
|
||||
import Avatar from "./avatar/Avatar";
|
||||
import SideContainerToggler from "./side_pannels/SideContainerToggler";
|
||||
import UIUtil from "./util/UIUtil";
|
||||
@@ -17,7 +19,7 @@ import SharedVideoManager from './shared_video/SharedVideo';
|
||||
import Recording from "./recording/Recording";
|
||||
|
||||
import VideoLayout from "./videolayout/VideoLayout";
|
||||
import FilmStrip from "./videolayout/FilmStrip";
|
||||
import Filmstrip from "./videolayout/Filmstrip";
|
||||
import SettingsMenu from "./side_pannels/settings/SettingsMenu";
|
||||
import Profile from "./side_pannels/profile/Profile";
|
||||
import Settings from "./../settings/Settings";
|
||||
@@ -25,6 +27,26 @@ import RingOverlay from "./ring_overlay/RingOverlay";
|
||||
import UIErrors from './UIErrors';
|
||||
import { debounce } from "../util/helpers";
|
||||
|
||||
import {
|
||||
setAudioMuted,
|
||||
setVideoMuted
|
||||
} from '../../react/features/base/media';
|
||||
import {
|
||||
openDeviceSelectionDialog
|
||||
} from '../../react/features/device-selection';
|
||||
import {
|
||||
checkAutoEnableDesktopSharing,
|
||||
dockToolbox,
|
||||
setAudioIconEnabled,
|
||||
setToolbarButton,
|
||||
setVideoIconEnabled,
|
||||
showDialPadButton,
|
||||
showEtherpadButton,
|
||||
showSharedVideoButton,
|
||||
showSIPCallButton,
|
||||
showToolbox
|
||||
} from '../../react/features/toolbox';
|
||||
|
||||
var EventEmitter = require("events");
|
||||
UI.messageHandler = require("./util/MessageHandler");
|
||||
var messageHandler = UI.messageHandler;
|
||||
@@ -83,16 +105,6 @@ JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.CONSTRAINT_FAILED]
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.NO_DATA_FROM_SOURCE]
|
||||
= "dialog.micNotSendingData";
|
||||
|
||||
/**
|
||||
* Initialize toolbars with side panels.
|
||||
*/
|
||||
function setupToolbars() {
|
||||
// Initialize toolbar buttons
|
||||
Toolbar.init(eventEmitter);
|
||||
// Initialize side panels
|
||||
SidePanels.init(eventEmitter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the application in and out of full screen mode
|
||||
* (a.k.a. presentation mode in Chrome).
|
||||
@@ -196,11 +208,11 @@ UI.setRaisedHandStatus = (participant, raisedHandStatus) => {
|
||||
/**
|
||||
* Sets the local "raised hand" status.
|
||||
*/
|
||||
UI.setLocalRaisedHandStatus = (raisedHandStatus) => {
|
||||
VideoLayout.setRaisedHandStatus(
|
||||
UI.setLocalRaisedHandStatus
|
||||
= raisedHandStatus =>
|
||||
VideoLayout.setRaisedHandStatus(
|
||||
APP.conference.getMyUserId(),
|
||||
raisedHandStatus);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize conference UI.
|
||||
@@ -231,7 +243,7 @@ UI.initConference = function () {
|
||||
UI.setUserAvatarID(id, Settings.getAvatarId());
|
||||
}
|
||||
|
||||
Toolbar.checkAutoEnableDesktopSharing();
|
||||
APP.store.dispatch(checkAutoEnableDesktopSharing());
|
||||
|
||||
if(!interfaceConfig.filmStripOnly) {
|
||||
Feedback.init(eventEmitter);
|
||||
@@ -253,7 +265,7 @@ UI.mucJoined = function () {
|
||||
/***
|
||||
* Handler for toggling filmstrip
|
||||
*/
|
||||
UI.handleToggleFilmStrip = () => UI.toggleFilmStrip();
|
||||
UI.handleToggleFilmstrip = () => UI.toggleFilmstrip();
|
||||
|
||||
/**
|
||||
* Sets tooltip defaults.
|
||||
@@ -294,9 +306,8 @@ UI.start = function () {
|
||||
// Set the defaults for tooltips.
|
||||
_setTooltipDefaults();
|
||||
|
||||
ToolbarToggler.init();
|
||||
SideContainerToggler.init(eventEmitter);
|
||||
FilmStrip.init(eventEmitter);
|
||||
Filmstrip.init(eventEmitter);
|
||||
|
||||
VideoLayout.init(eventEmitter);
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
@@ -306,25 +317,24 @@ UI.start = function () {
|
||||
|
||||
sharedVideoManager = new SharedVideoManager(eventEmitter);
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
let debouncedShowToolbar = debounce(() => {
|
||||
UI.showToolbar();
|
||||
}, 100, { leading: true, trailing: false });
|
||||
let debouncedShowToolbar
|
||||
= debounce(
|
||||
() => UI.showToolbar(),
|
||||
100,
|
||||
{ leading: true, trailing: false });
|
||||
|
||||
$("#videoconference_page").mousemove(debouncedShowToolbar);
|
||||
setupToolbars();
|
||||
|
||||
// Initialise the recording module.
|
||||
if (config.enableRecording)
|
||||
if (config.enableRecording) {
|
||||
Recording.init(eventEmitter, config.recordingType);
|
||||
|
||||
// Display notice message at the top of the toolbar
|
||||
if (config.noticeMessage) {
|
||||
$('#noticeText').text(config.noticeMessage);
|
||||
UIUtil.setVisible('notice', true);
|
||||
}
|
||||
// Initialize side panels
|
||||
SidePanels.init(eventEmitter);
|
||||
} else {
|
||||
$("body").addClass("filmstrip-only");
|
||||
UIUtil.setVisible('mainToolbarContainer', false);
|
||||
FilmStrip.setupFilmStripOnly();
|
||||
UI.showToolbar();
|
||||
Filmstrip.setFilmstripOnly();
|
||||
messageHandler.enableNotifications(false);
|
||||
JitsiPopover.enabled = false;
|
||||
}
|
||||
@@ -449,7 +459,8 @@ UI.initEtherpad = name => {
|
||||
logger.log('Etherpad is enabled');
|
||||
etherpadManager
|
||||
= new EtherpadManager(config.etherpad_base, name, eventEmitter);
|
||||
Toolbar.showEtherpadButton();
|
||||
|
||||
APP.store.dispatch(showEtherpadButton());
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -524,8 +535,9 @@ UI.onPeerVideoTypeChanged
|
||||
UI.updateLocalRole = isModerator => {
|
||||
VideoLayout.showModeratorIndicator();
|
||||
|
||||
Toolbar.showSipCallButton(isModerator);
|
||||
Toolbar.showSharedVideoButton(isModerator);
|
||||
APP.store.dispatch(showSIPCallButton(isModerator));
|
||||
APP.store.dispatch(showSharedVideoButton());
|
||||
|
||||
Recording.showRecordingButton(isModerator);
|
||||
SettingsMenu.showStartMutedOptions(isModerator);
|
||||
SettingsMenu.showFollowMeOptions(isModerator);
|
||||
@@ -564,8 +576,7 @@ UI.updateUserRole = user => {
|
||||
} else {
|
||||
messageHandler.notify(
|
||||
'', 'notify.somebody',
|
||||
'connected', 'notify.grantedToUnknown', {}
|
||||
);
|
||||
'connected', 'notify.grantedToUnknown');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -575,19 +586,19 @@ UI.updateUserRole = user => {
|
||||
UI.toggleSmileys = () => Chat.toggleSmileys();
|
||||
|
||||
/**
|
||||
* Toggles film strip.
|
||||
* Toggles filmstrip.
|
||||
*/
|
||||
UI.toggleFilmStrip = function () {
|
||||
var self = FilmStrip;
|
||||
self.toggleFilmStrip.apply(self, arguments);
|
||||
UI.toggleFilmstrip = function () {
|
||||
var self = Filmstrip;
|
||||
self.toggleFilmstrip.apply(self, arguments);
|
||||
VideoLayout.resizeVideoArea(true, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates if the film strip is currently visible or not.
|
||||
* @returns {true} if the film strip is currently visible, otherwise
|
||||
* Indicates if the filmstrip is currently visible or not.
|
||||
* @returns {true} if the filmstrip is currently visible, otherwise
|
||||
*/
|
||||
UI.isFilmStripVisible = () => FilmStrip.isFilmStripVisible();
|
||||
UI.isFilmstripVisible = () => Filmstrip.isFilmstripVisible();
|
||||
|
||||
/**
|
||||
* Toggles chat panel.
|
||||
@@ -680,7 +691,10 @@ UI.askForNickname = function () {
|
||||
UI.setAudioMuted = function (id, muted) {
|
||||
VideoLayout.onAudioMute(id, muted);
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
Toolbar.toggleAudioIcon(muted);
|
||||
APP.store.dispatch(setAudioMuted(muted));
|
||||
APP.store.dispatch(setToolbarButton('microphone', {
|
||||
toggled: muted
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -690,7 +704,10 @@ UI.setAudioMuted = function (id, muted) {
|
||||
UI.setVideoMuted = function (id, muted) {
|
||||
VideoLayout.onVideoMute(id, muted);
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
Toolbar.toggleVideoIcon(muted);
|
||||
APP.store.dispatch(setVideoMuted(muted));
|
||||
APP.store.dispatch(setToolbarButton('camera', {
|
||||
toggled: muted
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -720,9 +737,7 @@ UI.removeListener = function (type, listener) {
|
||||
* @param type the type of the event we're emitting
|
||||
* @param options the parameters for the event
|
||||
*/
|
||||
UI.emitEvent = function (type, options) {
|
||||
eventEmitter.emit(type, options);
|
||||
};
|
||||
UI.emitEvent = (type, ...options) => eventEmitter.emit(type, ...options);
|
||||
|
||||
UI.clickOnVideo = function (videoNumber) {
|
||||
let videos = $("#remoteVideos .videocontainer:not(#mixedstream)");
|
||||
@@ -735,15 +750,11 @@ UI.clickOnVideo = function (videoNumber) {
|
||||
videos[videoIndex].click();
|
||||
};
|
||||
|
||||
//Used by torture
|
||||
UI.showToolbar = function (timeout) {
|
||||
return ToolbarToggler.showToolbar(timeout);
|
||||
};
|
||||
// Used by torture.
|
||||
UI.showToolbar = timeout => APP.store.dispatch(showToolbox(timeout));
|
||||
|
||||
//Used by torture
|
||||
UI.dockToolbar = function (isDock) {
|
||||
ToolbarToggler.dockToolbar(isDock);
|
||||
};
|
||||
// Used by torture.
|
||||
UI.dockToolbar = dock => APP.store.dispatch(dockToolbox(dock));
|
||||
|
||||
/**
|
||||
* Updates the avatar for participant.
|
||||
@@ -769,6 +780,9 @@ UI.setUserEmail = function (id, email) {
|
||||
Avatar.setUserEmail(id, email);
|
||||
|
||||
changeAvatar(id, Avatar.getAvatarUrl(id));
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
Profile.changeEmail(email);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -844,19 +858,17 @@ UI.markDominantSpeaker = function (id) {
|
||||
VideoLayout.onDominantSpeakerChanged(id);
|
||||
};
|
||||
|
||||
UI.handleLastNEndpoints = function (ids, enteringIds) {
|
||||
VideoLayout.onLastNEndpointsChanged(ids, enteringIds);
|
||||
UI.handleLastNEndpoints = function (leavingIds, enteringIds) {
|
||||
VideoLayout.onLastNEndpointsChanged(leavingIds, enteringIds);
|
||||
};
|
||||
|
||||
/**
|
||||
* Will handle notification about participant's connectivity status change.
|
||||
*
|
||||
* @param {string} id the id of remote participant(MUC jid)
|
||||
* @param {boolean} isActive true if the connection is ok or false if the user
|
||||
* is having connectivity issues.
|
||||
*/
|
||||
UI.participantConnectionStatusChanged = function (id, isActive) {
|
||||
VideoLayout.onParticipantConnectionStatusChanged(id, isActive);
|
||||
UI.participantConnectionStatusChanged = function (id) {
|
||||
VideoLayout.onParticipantConnectionStatusChanged(id);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -917,16 +929,18 @@ UI.promptDisplayName = () => {
|
||||
* @param {string} id user id
|
||||
* @param {number} lvl audio level
|
||||
*/
|
||||
UI.setAudioLevel = function (id, lvl) {
|
||||
VideoLayout.setAudioLevel(id, lvl);
|
||||
};
|
||||
UI.setAudioLevel = (id, lvl) => VideoLayout.setAudioLevel(id, lvl);
|
||||
|
||||
/**
|
||||
* Update state of desktop sharing buttons.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.updateDesktopSharingButtons = function () {
|
||||
Toolbar.updateDesktopSharingButtonState();
|
||||
};
|
||||
UI.updateDesktopSharingButtons
|
||||
= () =>
|
||||
APP.store.dispatch(setToolbarButton('desktop', {
|
||||
toggled: APP.conference.isSharingScreen
|
||||
}));
|
||||
|
||||
/**
|
||||
* Hide connection quality statistics from UI.
|
||||
@@ -977,11 +991,8 @@ UI.addMessage = function (from, displayName, message, stamp) {
|
||||
Chat.updateChatConversation(from, displayName, message, stamp);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
UI.updateDTMFSupport = function (isDTMFSupported) {
|
||||
//TODO: enable when the UI is ready
|
||||
//Toolbar.showDialPadButton(isDTMFSupported);
|
||||
};
|
||||
UI.updateDTMFSupport
|
||||
= isDTMFSupported => APP.store.dispatch(showDialPadButton(isDTMFSupported));
|
||||
|
||||
/**
|
||||
* Show user feedback dialog if its required and enabled after pressing the
|
||||
@@ -1080,29 +1091,7 @@ UI.onLocalRaiseHandChanged = function (isRaisedHand) {
|
||||
* @param {object[]} devices new list of available devices
|
||||
*/
|
||||
UI.onAvailableDevicesChanged = function (devices) {
|
||||
SettingsMenu.changeDevicesList(devices);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets microphone's <select> element to select microphone ID from settings.
|
||||
*/
|
||||
UI.setSelectedMicFromSettings = function () {
|
||||
SettingsMenu.setSelectedMicFromSettings();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets camera's <select> element to select camera ID from settings.
|
||||
*/
|
||||
UI.setSelectedCameraFromSettings = function () {
|
||||
SettingsMenu.setSelectedCameraFromSettings();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets audio outputs's <select> element to select audio output ID from
|
||||
* settings.
|
||||
*/
|
||||
UI.setSelectedAudioOutputFromSettings = function () {
|
||||
SettingsMenu.setSelectedAudioOutputFromSettings();
|
||||
APP.store.dispatch(updateDeviceList(devices));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1322,7 +1311,8 @@ UI.onSharedVideoStop = function (id, attributes) {
|
||||
* @param {boolean} enabled indicates if the camera button should be enabled
|
||||
* or disabled
|
||||
*/
|
||||
UI.setCameraButtonEnabled = enabled => Toolbar.setVideoIconEnabled(enabled);
|
||||
UI.setCameraButtonEnabled
|
||||
= enabled => APP.store.dispatch(setVideoIconEnabled(enabled));
|
||||
|
||||
/**
|
||||
* Enables / disables microphone toolbar button.
|
||||
@@ -1330,17 +1320,18 @@ UI.setCameraButtonEnabled = enabled => Toolbar.setVideoIconEnabled(enabled);
|
||||
* @param {boolean} enabled indicates if the microphone button should be
|
||||
* enabled or disabled
|
||||
*/
|
||||
UI.setMicrophoneButtonEnabled = enabled => Toolbar.setAudioIconEnabled(enabled);
|
||||
UI.setMicrophoneButtonEnabled
|
||||
= enabled => APP.store.dispatch(setAudioIconEnabled(enabled));
|
||||
|
||||
UI.showRingOverlay = function () {
|
||||
RingOverlay.show(APP.tokenData.callee, interfaceConfig.DISABLE_RINGING);
|
||||
FilmStrip.toggleFilmStrip(false, false);
|
||||
Filmstrip.toggleFilmstrip(false, false);
|
||||
};
|
||||
|
||||
UI.hideRingOverLay = function () {
|
||||
if (!RingOverlay.hide())
|
||||
return;
|
||||
FilmStrip.toggleFilmStrip(true, false);
|
||||
Filmstrip.toggleFilmstrip(true, false);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1381,7 +1372,18 @@ const UIListeners = new Map([
|
||||
UI.toggleChat
|
||||
], [
|
||||
UIEvents.TOGGLE_SETTINGS,
|
||||
() => UI.toggleSidePanel("settings_container")
|
||||
() => {
|
||||
// Opening of device selection is special-cased as it is a dialog
|
||||
// opened through a button in settings and not directly displayed in
|
||||
// settings itself. As it is not useful to only have a settings menu
|
||||
// with a button to open a dialog, open the dialog directly instead.
|
||||
if (interfaceConfig.SETTINGS_SECTIONS.length === 1
|
||||
&& UIUtil.isSettingEnabled('devices')) {
|
||||
APP.store.dispatch(openDeviceSelectionDialog());
|
||||
} else {
|
||||
UI.toggleSidePanel("settings_container");
|
||||
}
|
||||
}
|
||||
], [
|
||||
UIEvents.TOGGLE_CONTACT_LIST,
|
||||
UI.toggleContactList
|
||||
@@ -1389,8 +1391,8 @@ const UIListeners = new Map([
|
||||
UIEvents.TOGGLE_PROFILE,
|
||||
() => APP.tokenData.isGuest && UI.toggleSidePanel("profile_container")
|
||||
], [
|
||||
UIEvents.TOGGLE_FILM_STRIP,
|
||||
UI.handleToggleFilmStrip
|
||||
UIEvents.TOGGLE_FILMSTRIP,
|
||||
UI.handleToggleFilmstrip
|
||||
], [
|
||||
UIEvents.FOLLOW_ME_ENABLED,
|
||||
enabled => (followMeHandler && followMeHandler.enableFollowMe(enabled))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* global $, APP, config */
|
||||
/* global $, APP, config, JitsiMeetJS */
|
||||
const ConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
|
||||
/**
|
||||
* Build html for "password required" dialog.
|
||||
@@ -127,14 +128,30 @@ function LoginDialog(successCallback, cancelCallback) {
|
||||
/**
|
||||
* Displays error message in 'finished' state which allows either to cancel
|
||||
* or retry.
|
||||
* @param messageKey the key to the message to be displayed.
|
||||
* @param error the key to the error to be displayed.
|
||||
* @param options the options to the error message (optional)
|
||||
*/
|
||||
this.displayError = function (messageKey, options) {
|
||||
this.displayError = function (error, options) {
|
||||
|
||||
let finishedState = connDialog.getState('finished');
|
||||
|
||||
let errorMessageElem = finishedState.find('#errorMessage');
|
||||
|
||||
let messageKey;
|
||||
if (error === ConnectionErrors.PASSWORD_REQUIRED) {
|
||||
// this is a password required error, as login window was already
|
||||
// open once, this means username or password is wrong
|
||||
messageKey = 'dialog.incorrectPassword';
|
||||
}
|
||||
else {
|
||||
messageKey = 'dialog.connectErrorWithMsg';
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
options.msg = error;
|
||||
}
|
||||
|
||||
errorMessageElem.attr("data-i18n", messageKey);
|
||||
|
||||
APP.translation.translateElement($(errorMessageElem), options);
|
||||
|
||||
@@ -30,7 +30,7 @@ let users = {};
|
||||
export default {
|
||||
/**
|
||||
* Sets prop in users object.
|
||||
* @param id {string} user id
|
||||
* @param id {string} user id or undefined for the local user.
|
||||
* @param prop {string} name of the prop
|
||||
* @param val {string} value to be set
|
||||
*/
|
||||
@@ -38,7 +38,7 @@ export default {
|
||||
// FIXME: Fixes the issue with not be able to return avatar for the
|
||||
// local user when the conference has been left. Maybe there is beter
|
||||
// way to solve it.
|
||||
if(APP.conference.isLocalId(id)) {
|
||||
if(!id || APP.conference.isLocalId(id)) {
|
||||
id = "local";
|
||||
}
|
||||
if(!val || (users[id] && users[id][prop] === val))
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import VideoLayout from "../videolayout/VideoLayout";
|
||||
import LargeContainer from '../videolayout/LargeContainer';
|
||||
import UIEvents from "../../../service/UI/UIEvents";
|
||||
import FilmStrip from '../videolayout/FilmStrip';
|
||||
import Filmstrip from '../videolayout/Filmstrip';
|
||||
|
||||
/**
|
||||
* Etherpad options.
|
||||
@@ -103,7 +103,7 @@ class Etherpad extends LargeContainer {
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
resize (containerWidth, containerHeight, animate) {
|
||||
let height = containerHeight - FilmStrip.getFilmStripHeight();
|
||||
let height = containerHeight - Filmstrip.getFilmstripHeight();
|
||||
let width = containerWidth;
|
||||
|
||||
$(this.iframe).width(width).height(height);
|
||||
|
||||
@@ -47,31 +47,8 @@ class Invite {
|
||||
}
|
||||
});
|
||||
|
||||
this.conference.on(ConferenceEvents.CONFERENCE_JOINED, () => {
|
||||
let roomLocker = this.getRoomLocker();
|
||||
roomLocker.hideRequirePasswordDialog();
|
||||
});
|
||||
|
||||
APP.UI.addListener( UIEvents.INVITE_CLICKED,
|
||||
() => { this.openLinkDialog(); });
|
||||
|
||||
APP.UI.addListener( UIEvents.PASSWORD_REQUIRED,
|
||||
() => {
|
||||
let roomLocker = this.getRoomLocker();
|
||||
this.setLockedFromElsewhere(true);
|
||||
roomLocker.requirePassword().then(() => {
|
||||
let pass = roomLocker.password;
|
||||
// we received that password is required, but user is trying
|
||||
// anyway to login without a password, mark room as not
|
||||
// locked in case he succeeds (maybe someone removed the
|
||||
// password meanwhile), if it is still locked another
|
||||
// password required will be received and the room again
|
||||
// will be marked as locked.
|
||||
if (!pass)
|
||||
this.setLockedFromElsewhere(false);
|
||||
this.conference.join(pass);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
/* global APP */
|
||||
|
||||
import UIUtil from '../util/UIUtil';
|
||||
|
||||
/**
|
||||
* Show dialog which asks for required conference password.
|
||||
* @returns {Promise<string>} password or nothing if user canceled
|
||||
*/
|
||||
export default class RequirePasswordDialog {
|
||||
constructor() {
|
||||
this.titleKey = 'dialog.passwordRequired';
|
||||
this.labelKey = 'dialog.passwordLabel';
|
||||
this.errorKey = 'dialog.incorrectPassword';
|
||||
this.errorId = 'passwordRequiredError';
|
||||
this.inputId = 'passwordRequiredInput';
|
||||
this.inputErrorClass = 'error';
|
||||
this.isOpened = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registering dialog listeners
|
||||
* @private
|
||||
*/
|
||||
_registerListeners() {
|
||||
let el = document.getElementById(this.inputId);
|
||||
el.addEventListener('keypress', this._hideError.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method returning dialog body
|
||||
* @returns {string}
|
||||
* @private
|
||||
*/
|
||||
_getBodyMessage() {
|
||||
return (
|
||||
`<div class="form-control">
|
||||
<label class="input-control__label"
|
||||
data-i18n="${this.labelKey}"></label>
|
||||
<input class="input-control__input input-control"
|
||||
name="lockKey" type="text"
|
||||
data-i18n="[placeholder]dialog.password"
|
||||
autofocus id="${this.inputId}">
|
||||
<p class="form-control__hint form-control__hint_error hide"
|
||||
id="${this.errorId}"
|
||||
data-i18n="${this.errorKey}"></p>
|
||||
</div>`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asking for a password
|
||||
* @returns {Promise}
|
||||
*/
|
||||
askForPassword() {
|
||||
if (!this.isOpened) {
|
||||
return this.open();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
this._showError();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the dialog
|
||||
* @returns {Promise}
|
||||
*/
|
||||
open() {
|
||||
let { titleKey } = this;
|
||||
let msgString = this._getBodyMessage();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
let submitFunction = this._submitFunction.bind(this);
|
||||
let closeFunction = this._closeFunction.bind(this);
|
||||
|
||||
this._dialog = APP.UI.messageHandler.openTwoButtonDialog({
|
||||
titleKey,
|
||||
msgString,
|
||||
leftButtonKey: "dialog.Ok",
|
||||
submitFunction,
|
||||
closeFunction,
|
||||
focus: ':input:first'
|
||||
});
|
||||
|
||||
this._registerListeners();
|
||||
this.isOpened = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit dialog callback
|
||||
* @param e - event
|
||||
* @param v - value
|
||||
* @param m - message
|
||||
* @param f - form
|
||||
* @private
|
||||
*/
|
||||
_submitFunction(e, v, m, f) {
|
||||
e.preventDefault();
|
||||
this._processInput(v, f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processing input in dialog
|
||||
* @param v - value
|
||||
* @param f - form
|
||||
* @private
|
||||
*/
|
||||
_processInput(v, f) {
|
||||
if (v && f.lockKey) {
|
||||
this.resolve(UIUtil.escapeHtml(f.lockKey));
|
||||
} else {
|
||||
this.reject(APP.UI.messageHandler.CANCEL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close dialog callback
|
||||
* @private
|
||||
*/
|
||||
_closeFunction(e, v, m, f) {
|
||||
this._processInput(v, f);
|
||||
this._hideError();
|
||||
this.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method showing error hint
|
||||
* @private
|
||||
*/
|
||||
_showError() {
|
||||
let className = this.inputErrorClass;
|
||||
let input = document.getElementById(this.inputId);
|
||||
document.getElementById(this.errorId).classList.remove('hide');
|
||||
input.classList.add(className);
|
||||
input.select();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method hiding error hint
|
||||
* @private
|
||||
*/
|
||||
_hideError() {
|
||||
let className = this.inputErrorClass;
|
||||
document.getElementById(this.errorId).classList.add('hide');
|
||||
document.getElementById(this.inputId).classList.remove(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the dialog
|
||||
*/
|
||||
close() {
|
||||
if (this._dialog) {
|
||||
this._dialog.close();
|
||||
}
|
||||
this.isOpened = false;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
/* global APP, JitsiMeetJS */
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
import RequirePasswordDialog from './RequirePasswordDialog';
|
||||
|
||||
/**
|
||||
* Show notification that user cannot set password for the conference
|
||||
* because server doesn't support that.
|
||||
@@ -33,7 +31,6 @@ const ConferenceErrors = JitsiMeetJS.errors.conference;
|
||||
*/
|
||||
export default function createRoomLocker (room) {
|
||||
let password;
|
||||
let requirePasswordDialog = new RequirePasswordDialog();
|
||||
/**
|
||||
* If the room was locked from someone other than us, we indicate it with
|
||||
* this property in order to have correct roomLocker state of isLocked.
|
||||
@@ -102,31 +99,5 @@ export default function createRoomLocker (room) {
|
||||
password = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Asks user for required conference password.
|
||||
*/
|
||||
requirePassword () {
|
||||
return requirePasswordDialog.askForPassword().then(
|
||||
newPass => { password = newPass; }
|
||||
).catch(
|
||||
reason => {
|
||||
// user canceled, no pass was entered.
|
||||
// clear, as if we use the same instance several times
|
||||
// pass stays between attempts
|
||||
password = null;
|
||||
if (reason !== APP.UI.messageHandler.CANCEL)
|
||||
logger.error(reason);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides require password dialog
|
||||
*/
|
||||
hideRequirePasswordDialog() {
|
||||
if (requirePasswordDialog.isOpened) {
|
||||
requirePasswordDialog.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ import UIEvents from "../../../service/UI/UIEvents";
|
||||
import UIUtil from '../util/UIUtil';
|
||||
import VideoLayout from '../videolayout/VideoLayout';
|
||||
import Feedback from '../feedback/Feedback.js';
|
||||
import Toolbar from '../toolbars/Toolbar';
|
||||
|
||||
import { setToolboxEnabled } from '../../../react/features/toolbox';
|
||||
|
||||
/**
|
||||
* The dialog for user input.
|
||||
@@ -34,8 +35,10 @@ let dialog = null;
|
||||
* @private
|
||||
*/
|
||||
function _isRecordingButtonEnabled() {
|
||||
return interfaceConfig.TOOLBAR_BUTTONS.indexOf("recording") !== -1
|
||||
&& config.enableRecording && APP.conference.isRecordingSupported();
|
||||
return (
|
||||
interfaceConfig.TOOLBAR_BUTTONS.indexOf("recording") !== -1
|
||||
&& config.enableRecording
|
||||
&& APP.conference.isRecordingSupported());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +67,7 @@ function _requestLiveStreamId() {
|
||||
name="streamId" type="text"
|
||||
data-i18n="[placeholder]dialog.streamKey"
|
||||
autofocus><div style="text-align: right">
|
||||
<a class="helper-link" target="_new"
|
||||
<a class="helper-link" target="_new"
|
||||
href="${interfaceConfig.LIVE_STREAMING_HELP_LINK}">`
|
||||
+ streamIdHelp
|
||||
+ `</a></div>`,
|
||||
@@ -128,7 +131,7 @@ function _requestLiveStreamId() {
|
||||
* Request recording token from the user.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function _requestRecordingToken () {
|
||||
function _requestRecordingToken() {
|
||||
let titleKey = "dialog.recordingToken";
|
||||
let messageString = (
|
||||
`<input name="recordingToken" type="text"
|
||||
@@ -163,7 +166,7 @@ function _requestRecordingToken () {
|
||||
* @returns {Promise}
|
||||
* @private
|
||||
*/
|
||||
function _showStopRecordingPrompt (recordingType) {
|
||||
function _showStopRecordingPrompt(recordingType) {
|
||||
var title;
|
||||
var message;
|
||||
var buttonKey;
|
||||
@@ -178,19 +181,13 @@ function _showStopRecordingPrompt (recordingType) {
|
||||
buttonKey = "dialog.stopRecording";
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
return new Promise((resolve, reject) => {
|
||||
dialog = APP.UI.messageHandler.openTwoButtonDialog({
|
||||
titleKey: title,
|
||||
msgKey: message,
|
||||
leftButtonKey: buttonKey,
|
||||
submitFunction: function(e,v) {
|
||||
if (v) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
},
|
||||
closeFunction: function () {
|
||||
submitFunction: (e, v) => (v ? resolve : reject)(),
|
||||
closeFunction: () => {
|
||||
dialog = null;
|
||||
}
|
||||
});
|
||||
@@ -249,35 +246,12 @@ var Recording = {
|
||||
/**
|
||||
* Initializes the recording UI.
|
||||
*/
|
||||
init (emitter, recordingType) {
|
||||
this.eventEmitter = emitter;
|
||||
init(eventEmitter, recordingType) {
|
||||
this.eventEmitter = eventEmitter;
|
||||
this.recordingType = recordingType;
|
||||
|
||||
this.updateRecordingState(APP.conference.getRecordingState());
|
||||
|
||||
this.initRecordingButton(recordingType);
|
||||
|
||||
// If I am a recorder then I publish my recorder custom role to notify
|
||||
// everyone.
|
||||
if (config.iAmRecorder) {
|
||||
VideoLayout.enableDeviceAvailabilityIcons(
|
||||
APP.conference.getMyUserId(), false);
|
||||
VideoLayout.setLocalVideoVisible(false);
|
||||
Feedback.enableFeedback(false);
|
||||
Toolbar.enable(false);
|
||||
APP.UI.messageHandler.enableNotifications(false);
|
||||
APP.UI.messageHandler.enablePopups(false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialise the recording button.
|
||||
*/
|
||||
initRecordingButton(recordingType) {
|
||||
let selector = $('#toolbar_button_record');
|
||||
|
||||
let button = selector.get(0);
|
||||
UIUtil.setTooltip(button, 'liveStreaming.buttonTooltip', 'right');
|
||||
|
||||
if (recordingType === 'jibri') {
|
||||
this.baseClass = "fa fa-play-circle";
|
||||
this.recordingTitle = "dialog.liveStreaming";
|
||||
@@ -303,101 +277,44 @@ var Recording = {
|
||||
this.recordingBusy = "liveStreaming.busy";
|
||||
}
|
||||
|
||||
// XXX Due to the React-ification of Toolbox, the HTMLElement with id
|
||||
// toolbar_button_record may not exist yet.
|
||||
$(document).on(
|
||||
'click',
|
||||
'#toolbar_button_record',
|
||||
ev => this._onToolbarButtonClick(ev));
|
||||
|
||||
// If I am a recorder then I publish my recorder custom role to notify
|
||||
// everyone.
|
||||
if (config.iAmRecorder) {
|
||||
VideoLayout.enableDeviceAvailabilityIcons(
|
||||
APP.conference.getMyUserId(), false);
|
||||
VideoLayout.setLocalVideoVisible(false);
|
||||
Feedback.enableFeedback(false);
|
||||
APP.store.dispatch(setToolboxEnabled(false));
|
||||
APP.UI.messageHandler.enableNotifications(false);
|
||||
APP.UI.messageHandler.enablePopups(false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialise the recording button.
|
||||
*/
|
||||
initRecordingButton() {
|
||||
const selector = $('#toolbar_button_record');
|
||||
|
||||
UIUtil.setTooltip(selector, 'liveStreaming.buttonTooltip', 'right');
|
||||
|
||||
selector.addClass(this.baseClass);
|
||||
selector.attr("data-i18n", "[content]" + this.recordingButtonTooltip);
|
||||
APP.translation.translateElement(selector);
|
||||
|
||||
var self = this;
|
||||
selector.click(function () {
|
||||
if (dialog)
|
||||
return;
|
||||
JitsiMeetJS.analytics.sendEvent('recording.clicked');
|
||||
switch (self.currentState) {
|
||||
case Status.ON:
|
||||
case Status.RETRYING:
|
||||
case Status.PENDING: {
|
||||
_showStopRecordingPrompt(recordingType).then(
|
||||
() => {
|
||||
self.eventEmitter.emit(UIEvents.RECORDING_TOGGLED);
|
||||
JitsiMeetJS.analytics.sendEvent(
|
||||
'recording.stopped');
|
||||
},
|
||||
() => {});
|
||||
break;
|
||||
}
|
||||
case Status.AVAILABLE:
|
||||
case Status.OFF: {
|
||||
if (recordingType === 'jibri')
|
||||
_requestLiveStreamId().then((streamId) => {
|
||||
self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
|
||||
{streamId: streamId});
|
||||
JitsiMeetJS.analytics.sendEvent(
|
||||
'recording.started');
|
||||
}).catch(
|
||||
reason => {
|
||||
if (reason !== APP.UI.messageHandler.CANCEL)
|
||||
logger.error(reason);
|
||||
else
|
||||
JitsiMeetJS.analytics.sendEvent(
|
||||
'recording.canceled');
|
||||
}
|
||||
);
|
||||
else {
|
||||
if (self.predefinedToken) {
|
||||
self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
|
||||
{token: self.predefinedToken});
|
||||
JitsiMeetJS.analytics.sendEvent(
|
||||
'recording.started');
|
||||
return;
|
||||
}
|
||||
|
||||
_requestRecordingToken().then((token) => {
|
||||
self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
|
||||
{token: token});
|
||||
JitsiMeetJS.analytics.sendEvent(
|
||||
'recording.started');
|
||||
}).catch(
|
||||
reason => {
|
||||
if (reason !== APP.UI.messageHandler.CANCEL)
|
||||
logger.error(reason);
|
||||
else
|
||||
JitsiMeetJS.analytics.sendEvent(
|
||||
'recording.canceled');
|
||||
}
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Status.BUSY: {
|
||||
dialog = APP.UI.messageHandler.openMessageDialog(
|
||||
self.recordingTitle,
|
||||
self.recordingBusy,
|
||||
null,
|
||||
function () {
|
||||
dialog = null;
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
dialog = APP.UI.messageHandler.openMessageDialog(
|
||||
self.recordingTitle,
|
||||
self.recordingUnavailable,
|
||||
null,
|
||||
function () {
|
||||
dialog = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows or hides the 'recording' button.
|
||||
* @param show {true} to show the recording button, {false} to hide it
|
||||
*/
|
||||
showRecordingButton (show) {
|
||||
showRecordingButton(show) {
|
||||
let shouldShow = show && _isRecordingButtonEnabled();
|
||||
let id = 'toolbar_button_record';
|
||||
|
||||
@@ -424,7 +341,7 @@ var Recording = {
|
||||
* Sets the state of the recording button.
|
||||
* @param recordingState gives us the current recording state
|
||||
*/
|
||||
updateRecordingUI (recordingState) {
|
||||
updateRecordingUI(recordingState) {
|
||||
|
||||
let oldState = this.currentState;
|
||||
this.currentState = recordingState;
|
||||
@@ -490,7 +407,7 @@ var Recording = {
|
||||
},
|
||||
// checks whether recording is enabled and whether we have params
|
||||
// to start automatically recording
|
||||
checkAutoRecord () {
|
||||
checkAutoRecord() {
|
||||
if (_isRecordingButtonEnabled && config.autoRecord) {
|
||||
this.predefinedToken = UIUtil.escapeHtml(config.autoRecordToken);
|
||||
this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED,
|
||||
@@ -513,6 +430,90 @@ var Recording = {
|
||||
APP.translation.translateElement(labelSelector);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles {@code click} on {@code toolbar_button_record}.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToolbarButtonClick() {
|
||||
if (dialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
JitsiMeetJS.analytics.sendEvent('recording.clicked');
|
||||
switch (this.currentState) {
|
||||
case Status.ON:
|
||||
case Status.RETRYING:
|
||||
case Status.PENDING: {
|
||||
_showStopRecordingPrompt(this.recordingType).then(
|
||||
() => {
|
||||
this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED);
|
||||
JitsiMeetJS.analytics.sendEvent('recording.stopped');
|
||||
},
|
||||
() => {});
|
||||
break;
|
||||
}
|
||||
case Status.AVAILABLE:
|
||||
case Status.OFF: {
|
||||
if (this.recordingType === 'jibri')
|
||||
_requestLiveStreamId().then(streamId => {
|
||||
this.eventEmitter.emit(
|
||||
UIEvents.RECORDING_TOGGLED,
|
||||
{ streamId });
|
||||
JitsiMeetJS.analytics.sendEvent('recording.started');
|
||||
}).catch(reason => {
|
||||
if (reason !== APP.UI.messageHandler.CANCEL)
|
||||
logger.error(reason);
|
||||
else
|
||||
JitsiMeetJS.analytics.sendEvent('recording.canceled');
|
||||
});
|
||||
else {
|
||||
if (this.predefinedToken) {
|
||||
this.eventEmitter.emit(
|
||||
UIEvents.RECORDING_TOGGLED,
|
||||
{ token: this.predefinedToken });
|
||||
JitsiMeetJS.analytics.sendEvent('recording.started');
|
||||
return;
|
||||
}
|
||||
|
||||
_requestRecordingToken().then((token) => {
|
||||
this.eventEmitter.emit(
|
||||
UIEvents.RECORDING_TOGGLED,
|
||||
{ token });
|
||||
JitsiMeetJS.analytics.sendEvent('recording.started');
|
||||
}).catch(reason => {
|
||||
if (reason !== APP.UI.messageHandler.CANCEL)
|
||||
logger.error(reason);
|
||||
else
|
||||
JitsiMeetJS.analytics.sendEvent('recording.canceled');
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Status.BUSY: {
|
||||
dialog = APP.UI.messageHandler.openMessageDialog(
|
||||
this.recordingTitle,
|
||||
this.recordingBusy,
|
||||
null,
|
||||
() => {
|
||||
dialog = null;
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
dialog = APP.UI.messageHandler.openMessageDialog(
|
||||
this.recordingTitle,
|
||||
this.recordingUnavailable,
|
||||
null,
|
||||
() => {
|
||||
dialog = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the toggled state of the recording toolbar button.
|
||||
*
|
||||
|
||||
@@ -8,8 +8,9 @@ import UIEvents from '../../../service/UI/UIEvents';
|
||||
import VideoLayout from "../videolayout/VideoLayout";
|
||||
import LargeContainer from '../videolayout/LargeContainer';
|
||||
import SmallVideo from '../videolayout/SmallVideo';
|
||||
import FilmStrip from '../videolayout/FilmStrip';
|
||||
import ToolbarToggler from "../toolbars/ToolbarToggler";
|
||||
import Filmstrip from '../videolayout/Filmstrip';
|
||||
|
||||
import { dockToolbox, showToolbox } from '../../../react/features/toolbox';
|
||||
|
||||
export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo";
|
||||
|
||||
@@ -578,7 +579,7 @@ class SharedVideoContainer extends LargeContainer {
|
||||
self.bodyBackground = document.body.style.background;
|
||||
document.body.style.background = 'black';
|
||||
this.$iframe.css({opacity: 1});
|
||||
ToolbarToggler.dockToolbar(true);
|
||||
APP.store.dispatch(dockToolbox(true));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
@@ -586,7 +587,7 @@ class SharedVideoContainer extends LargeContainer {
|
||||
|
||||
hide () {
|
||||
let self = this;
|
||||
ToolbarToggler.dockToolbar(false);
|
||||
APP.store.dispatch(dockToolbox(false));
|
||||
return new Promise(resolve => {
|
||||
this.$iframe.fadeOut(300, () => {
|
||||
document.body.style.background = self.bodyBackground;
|
||||
@@ -597,7 +598,7 @@ class SharedVideoContainer extends LargeContainer {
|
||||
}
|
||||
|
||||
onHoverIn () {
|
||||
ToolbarToggler.showToolbar();
|
||||
APP.store.dispatch(showToolbox());
|
||||
}
|
||||
|
||||
get id () {
|
||||
@@ -605,7 +606,7 @@ class SharedVideoContainer extends LargeContainer {
|
||||
}
|
||||
|
||||
resize (containerWidth, containerHeight) {
|
||||
let height = containerHeight - FilmStrip.getFilmStripHeight();
|
||||
let height = containerHeight - Filmstrip.getFilmstripHeight();
|
||||
|
||||
let width = containerWidth;
|
||||
|
||||
|
||||
@@ -16,17 +16,27 @@ const SideContainerToggler = {
|
||||
init(eventEmitter) {
|
||||
this.eventEmitter = eventEmitter;
|
||||
|
||||
// Adds a listener for the animation end event that would take care
|
||||
// of hiding all internal containers when the extendedToolbarPanel is
|
||||
// We may not have a side toolbar container, for example, in
|
||||
// filmstrip-only mode.
|
||||
const sideToolbarContainer
|
||||
= document.getElementById("sideToolbarContainer");
|
||||
|
||||
if (!sideToolbarContainer)
|
||||
return;
|
||||
|
||||
// Adds a listener for the animationend event that would take care of
|
||||
// hiding all internal containers when the extendedToolbarPanel is
|
||||
// closed.
|
||||
document.getElementById("sideToolbarContainer")
|
||||
.addEventListener("animationend", function(e) {
|
||||
if(e.animationName === "slideOutExt")
|
||||
sideToolbarContainer.addEventListener(
|
||||
"animationend",
|
||||
function(e) {
|
||||
if (e.animationName === "slideOutExt")
|
||||
$("#sideToolbarContainer").children().each(function() {
|
||||
if ($(this).hasClass("show"))
|
||||
SideContainerToggler.hideInnerContainer($(this));
|
||||
});
|
||||
}, false);
|
||||
},
|
||||
false);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -134,4 +144,4 @@ const SideContainerToggler = {
|
||||
}
|
||||
};
|
||||
|
||||
export default SideContainerToggler;
|
||||
export default SideContainerToggler;
|
||||
|
||||
@@ -6,19 +6,26 @@ import UIUtil from '../util/UIUtil';
|
||||
|
||||
const SidePanels = {
|
||||
init (eventEmitter) {
|
||||
//Initialize chat
|
||||
if (UIUtil.isButtonEnabled('chat'))
|
||||
// Initialize chat
|
||||
if (UIUtil.isButtonEnabled('chat')) {
|
||||
Chat.init(eventEmitter);
|
||||
//Initialize settings
|
||||
if (UIUtil.isButtonEnabled('settings'))
|
||||
}
|
||||
|
||||
// Initialize settings
|
||||
if (UIUtil.isButtonEnabled('settings')) {
|
||||
SettingsMenu.init(eventEmitter);
|
||||
//Initialize profile
|
||||
if (UIUtil.isButtonEnabled('profile'))
|
||||
}
|
||||
|
||||
// Initialize profile
|
||||
if (UIUtil.isButtonEnabled('profile')) {
|
||||
Profile.init(eventEmitter);
|
||||
//Initialize contact list view
|
||||
if (UIUtil.isButtonEnabled('contacts'))
|
||||
}
|
||||
|
||||
// Initialize contact list view
|
||||
if (UIUtil.isButtonEnabled('contacts')) {
|
||||
ContactListView.init();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default SidePanels;
|
||||
export default SidePanels;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import {processReplacements, linkify} from './Replacement';
|
||||
import CommandsProcessor from './Commands';
|
||||
import ToolbarToggler from '../../toolbars/ToolbarToggler';
|
||||
import VideoLayout from "../../videolayout/VideoLayout";
|
||||
|
||||
import UIUtil from '../../util/UIUtil';
|
||||
@@ -10,6 +9,8 @@ import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
import { smileys } from './smileys';
|
||||
|
||||
import { dockToolbox, setSubject } from '../../../../react/features/toolbox';
|
||||
|
||||
let unreadMessages = 0;
|
||||
const sidePanelsContainerId = 'sideToolbarContainer';
|
||||
const htmlStr = `
|
||||
@@ -24,9 +25,9 @@ const htmlStr = `
|
||||
</div>
|
||||
|
||||
<div id="chatconversation"></div>
|
||||
<audio id="chatNotification" src="sounds/incomingMessage.wav"
|
||||
<audio id="chatNotification" src="sounds/incomingMessage.wav"
|
||||
preload="auto"></audio>
|
||||
<textarea id="usermsg" autofocus
|
||||
<textarea id="usermsg" autofocus
|
||||
data-i18n="[placeholder]chat.messagebox"></textarea>
|
||||
<div id="smileysarea">
|
||||
<div id="smileys" id="toggle_smileys">
|
||||
@@ -49,30 +50,41 @@ var CHAT_CONTAINER_ID = "chat_container";
|
||||
* Updates visual notification, indicating that a message has arrived.
|
||||
*/
|
||||
function updateVisualNotification() {
|
||||
var unreadMsgElement = document.getElementById('unreadMessages');
|
||||
// XXX The rewrite of the toolbar in React delayed the availability of the
|
||||
// element unreadMessages. In order to work around the delay, I introduced
|
||||
// and utilized unreadMsgSelector in addition to unreadMsgElement.
|
||||
const unreadMsgSelector = $('#unreadMessages');
|
||||
const unreadMsgElement
|
||||
= unreadMsgSelector.length > 0 ? unreadMsgSelector[0] : undefined;
|
||||
|
||||
if (unreadMessages) {
|
||||
unreadMsgElement.innerHTML = unreadMessages.toString();
|
||||
|
||||
ToolbarToggler.dockToolbar(true);
|
||||
APP.store.dispatch(dockToolbox(true));
|
||||
|
||||
var chatButtonElement
|
||||
const chatButtonElement
|
||||
= document.getElementById('toolbar_button_chat');
|
||||
var leftIndent = (UIUtil.getTextWidth(chatButtonElement) -
|
||||
UIUtil.getTextWidth(unreadMsgElement)) / 2;
|
||||
var topIndent = (UIUtil.getTextHeight(chatButtonElement) -
|
||||
UIUtil.getTextHeight(unreadMsgElement)) / 2 - 5;
|
||||
const leftIndent
|
||||
= (UIUtil.getTextWidth(chatButtonElement)
|
||||
- UIUtil.getTextWidth(unreadMsgElement))
|
||||
/ 2;
|
||||
const topIndent
|
||||
= (UIUtil.getTextHeight(chatButtonElement)
|
||||
- UIUtil.getTextHeight(unreadMsgElement))
|
||||
/ 2
|
||||
- 5;
|
||||
|
||||
unreadMsgElement.setAttribute(
|
||||
'style',
|
||||
'top:' + topIndent +
|
||||
'; left:' + leftIndent + ';');
|
||||
'style',
|
||||
'top:' + topIndent + '; left:' + leftIndent + ';');
|
||||
}
|
||||
else {
|
||||
unreadMsgElement.innerHTML = '';
|
||||
unreadMsgSelector.html('');
|
||||
}
|
||||
|
||||
$(unreadMsgElement).parent()[unreadMessages > 0 ? 'show' : 'hide']();
|
||||
if (unreadMsgElement) {
|
||||
unreadMsgSelector.parent()[unreadMessages > 0 ? 'show' : 'hide']();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -227,7 +239,7 @@ var Chat = {
|
||||
// Undock the toolbar when the chat is shown and if we're in a
|
||||
// video mode.
|
||||
if (VideoLayout.isLargeVideoVisible()) {
|
||||
ToolbarToggler.dockToolbar(false);
|
||||
APP.store.dispatch(dockToolbox(false));
|
||||
}
|
||||
|
||||
// if we are in conversation mode focus on the text input
|
||||
@@ -308,10 +320,9 @@ var Chat = {
|
||||
subject = subject.trim();
|
||||
}
|
||||
|
||||
let subjectId = 'subject';
|
||||
let html = linkify(UIUtil.escapeHtml(subject));
|
||||
$(`#${subjectId}`).html(html);
|
||||
UIUtil.setVisible(subjectId, subject && subject.length > 0);
|
||||
const html = linkify(UIUtil.escapeHtml(subject));
|
||||
|
||||
APP.store.dispatch(setSubject(html));
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,10 +15,10 @@ const htmlStr = `
|
||||
</div>
|
||||
<div class="sideToolbarBlock">
|
||||
<label data-i18n="profile.setEmailLabel"></label>
|
||||
<input id="setEmail" type="text" class="input-control"
|
||||
<input id="setEmail" type="text" class="input-control"
|
||||
data-i18n="[placeholder]profile.setEmailInput">
|
||||
</div>
|
||||
<div id="profile_auth_container"
|
||||
<div id="profile_auth_container"
|
||||
class="sideToolbarBlock auth_container">
|
||||
<p data-i18n="toolbar.authenticate"></p>
|
||||
<ul>
|
||||
@@ -36,6 +36,9 @@ const htmlStr = `
|
||||
function initHTML() {
|
||||
$(`#${sidePanelsContainerId}`)
|
||||
.append(htmlStr);
|
||||
// make sure we translate the panel, as adding it can be after i18n
|
||||
// library had initialized and translated already present html
|
||||
APP.translation.translateElement($(`#${sidePanelsContainerId}`));
|
||||
}
|
||||
|
||||
export default {
|
||||
@@ -122,6 +125,14 @@ export default {
|
||||
$('#avatar').attr('src', avatarUrl);
|
||||
},
|
||||
|
||||
/**
|
||||
* Change the value of the field for the user email.
|
||||
* @param {string} email the new value that will be displayed in the field.
|
||||
*/
|
||||
changeEmail (email) {
|
||||
$('#setEmail').val(email);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows or hides authentication related buttons
|
||||
* @param {boolean} show <tt>true</tt> to show or <tt>false</tt> to hide
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
/* global $, APP, AJS, interfaceConfig, JitsiMeetJS */
|
||||
|
||||
/* global $, APP, AJS, interfaceConfig */
|
||||
import { LANGUAGES } from "../../../../react/features/base/i18n";
|
||||
import { openDeviceSelectionDialog }
|
||||
from '../../../../react/features/device-selection';
|
||||
|
||||
import UIUtil from "../../util/UIUtil";
|
||||
import UIEvents from "../../../../service/UI/UIEvents";
|
||||
import Settings from '../../../settings/Settings';
|
||||
|
||||
const sidePanelsContainerId = 'sideToolbarContainer';
|
||||
const deviceSelectionButtonClasses
|
||||
= 'button-control button-control_primary button-control_full-width';
|
||||
const htmlStr = `
|
||||
<div id="settings_container" class="sideToolbarContainer__inner">
|
||||
<div class="title" data-i18n="settings.title"></div>
|
||||
@@ -19,17 +21,11 @@ const htmlStr = `
|
||||
<div id="deviceOptionsTitle" class="subTitle hide"
|
||||
data-i18n="settings.audioVideo"></div>
|
||||
<div class="sideToolbarBlock first">
|
||||
<label class="first" data-i18n="settings.selectCamera">
|
||||
</label>
|
||||
<select id="selectCamera"></select>
|
||||
</div>
|
||||
<div class="sideToolbarBlock">
|
||||
<label data-i18n="settings.selectMic"></label>
|
||||
<select id="selectMic"></select>
|
||||
</div>
|
||||
<div class="sideToolbarBlock">
|
||||
<label data-i18n="settings.selectAudioOutput"></label>
|
||||
<select id="selectAudioOutput"></select>
|
||||
<button
|
||||
class="${deviceSelectionButtonClasses}"
|
||||
data-i18n="deviceSelection.deviceSettings"
|
||||
id="deviceSelection"
|
||||
type="button"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="moderatorOptionsWrapper" class="hide">
|
||||
@@ -61,6 +57,9 @@ const htmlStr = `
|
||||
function initHTML() {
|
||||
$(`#${sidePanelsContainerId}`)
|
||||
.append(htmlStr);
|
||||
// make sure we translate the panel, as adding it can be after i18n
|
||||
// library had initialized and translated already present html
|
||||
APP.translation.translateElement($(`#${sidePanelsContainerId}`));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,40 +85,6 @@ function generateLanguagesOptions(items, currentLang) {
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate html select options for available physical devices.
|
||||
*
|
||||
* @param {{ deviceId, label }[]} items available devices
|
||||
* @param {string} [selectedId] id of selected device
|
||||
* @param {boolean} permissionGranted if permission to use selected device type
|
||||
* is granted
|
||||
* @returns {string}
|
||||
*/
|
||||
function generateDevicesOptions(items, selectedId, permissionGranted) {
|
||||
if (!permissionGranted && items.length) {
|
||||
return '<option data-i18n="settings.noPermission"></option>';
|
||||
}
|
||||
|
||||
var options = items.map(function (item) {
|
||||
let attrs = {
|
||||
value: item.deviceId
|
||||
};
|
||||
|
||||
if (item.deviceId === selectedId) {
|
||||
attrs.selected = 'selected';
|
||||
}
|
||||
|
||||
let attrsStr = UIUtil.attrsToString(attrs);
|
||||
return `<option ${attrsStr}>${item.label}</option>`;
|
||||
});
|
||||
|
||||
if (!items.length) {
|
||||
options.unshift('<option data-i18n="settings.noDevice"></option>');
|
||||
}
|
||||
|
||||
return options.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace html select element to select2 custom dropdown
|
||||
*
|
||||
@@ -162,6 +127,9 @@ export default {
|
||||
selectInput[0].dataset.i18n =
|
||||
`languages:${APP.translation.getCurrentLanguage()}`;
|
||||
|
||||
// translate selectInput, which is the currently selected language
|
||||
// otherwise there will be no selected option
|
||||
APP.translation.translateElement(selectInput);
|
||||
APP.translation.translateElement(selectEl);
|
||||
|
||||
APP.translation.addLanguageChangedListener(
|
||||
@@ -173,13 +141,9 @@ export default {
|
||||
if (UIUtil.isSettingEnabled('devices')) {
|
||||
const wrapperId = 'deviceOptionsWrapper';
|
||||
|
||||
JitsiMeetJS.mediaDevices.isDeviceListAvailable()
|
||||
.then((isDeviceListAvailable) => {
|
||||
if (isDeviceListAvailable &&
|
||||
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
|
||||
this._initializeDeviceSelectionSettings(emitter);
|
||||
}
|
||||
});
|
||||
$('#deviceSelection').on('click', () =>
|
||||
APP.store.dispatch(openDeviceSelectionDialog()));
|
||||
|
||||
// Only show the subtitle if this isn't the only setting section.
|
||||
if (interfaceConfig.SETTINGS_SECTIONS.length > 1)
|
||||
UIUtil.setVisible("deviceOptionsTitle", true);
|
||||
@@ -213,30 +177,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
_initializeDeviceSelectionSettings(emitter) {
|
||||
this.changeDevicesList([]);
|
||||
|
||||
$('#selectCamera').change(function () {
|
||||
let cameraDeviceId = $(this).val();
|
||||
if (cameraDeviceId !== Settings.getCameraDeviceId()) {
|
||||
emitter.emit(UIEvents.VIDEO_DEVICE_CHANGED, cameraDeviceId);
|
||||
}
|
||||
});
|
||||
$('#selectMic').change(function () {
|
||||
let micDeviceId = $(this).val();
|
||||
if (micDeviceId !== Settings.getMicDeviceId()) {
|
||||
emitter.emit(UIEvents.AUDIO_DEVICE_CHANGED, micDeviceId);
|
||||
}
|
||||
});
|
||||
$('#selectAudioOutput').change(function () {
|
||||
let audioOutputDeviceId = $(this).val();
|
||||
if (audioOutputDeviceId !== Settings.getAudioOutputDeviceId()) {
|
||||
emitter.emit(
|
||||
UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED, audioOutputDeviceId);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* If start audio muted/start video muted options should be visible or not.
|
||||
* @param {boolean} show
|
||||
@@ -280,91 +220,5 @@ export default {
|
||||
*/
|
||||
isVisible () {
|
||||
return UIUtil.isVisible(document.getElementById("settings_container"));
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets microphone's <select> element to select microphone ID from settings.
|
||||
*/
|
||||
setSelectedMicFromSettings () {
|
||||
$('#selectMic').val(Settings.getMicDeviceId());
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets camera's <select> element to select camera ID from settings.
|
||||
*/
|
||||
setSelectedCameraFromSettings () {
|
||||
$('#selectCamera').val(Settings.getCameraDeviceId());
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets audio outputs's <select> element to select audio output ID from
|
||||
* settings.
|
||||
*/
|
||||
setSelectedAudioOutputFromSettings () {
|
||||
$('#selectAudioOutput').val(Settings.getAudioOutputDeviceId());
|
||||
},
|
||||
|
||||
/**
|
||||
* Change available cameras/microphones or hide selects completely if
|
||||
* no devices available.
|
||||
* @param {{ deviceId, label, kind }[]} devices list of available devices
|
||||
*/
|
||||
changeDevicesList (devices) {
|
||||
let $selectCamera= AJS.$('#selectCamera'),
|
||||
$selectMic = AJS.$('#selectMic'),
|
||||
$selectAudioOutput = AJS.$('#selectAudioOutput'),
|
||||
$selectAudioOutputParent = $selectAudioOutput.parent();
|
||||
|
||||
let audio = devices.filter(device => device.kind === 'audioinput'),
|
||||
video = devices.filter(device => device.kind === 'videoinput'),
|
||||
audioOutput = devices
|
||||
.filter(device => device.kind === 'audiooutput'),
|
||||
selectedAudioDevice = audio.find(
|
||||
d => d.deviceId === Settings.getMicDeviceId()) || audio[0],
|
||||
selectedVideoDevice = video.find(
|
||||
d => d.deviceId === Settings.getCameraDeviceId()) || video[0],
|
||||
selectedAudioOutputDevice = audioOutput.find(
|
||||
d => d.deviceId === Settings.getAudioOutputDeviceId()),
|
||||
videoPermissionGranted =
|
||||
JitsiMeetJS.mediaDevices.isDevicePermissionGranted('video'),
|
||||
audioPermissionGranted =
|
||||
JitsiMeetJS.mediaDevices.isDevicePermissionGranted('audio');
|
||||
|
||||
$selectCamera
|
||||
.html(generateDevicesOptions(
|
||||
video,
|
||||
selectedVideoDevice ? selectedVideoDevice.deviceId : '',
|
||||
videoPermissionGranted))
|
||||
.prop('disabled', !video.length || !videoPermissionGranted);
|
||||
|
||||
initSelect2($selectCamera);
|
||||
|
||||
$selectMic
|
||||
.html(generateDevicesOptions(
|
||||
audio,
|
||||
selectedAudioDevice ? selectedAudioDevice.deviceId : '',
|
||||
audioPermissionGranted))
|
||||
.prop('disabled', !audio.length || !audioPermissionGranted);
|
||||
|
||||
initSelect2($selectMic);
|
||||
|
||||
if (JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
|
||||
$selectAudioOutput
|
||||
.html(generateDevicesOptions(
|
||||
audioOutput,
|
||||
selectedAudioOutputDevice
|
||||
? selectedAudioOutputDevice.deviceId
|
||||
: 'default',
|
||||
videoPermissionGranted || audioPermissionGranted))
|
||||
.prop('disabled', !audioOutput.length ||
|
||||
(!videoPermissionGranted && !audioPermissionGranted));
|
||||
initSelect2($selectAudioOutput);
|
||||
|
||||
$selectAudioOutputParent.show();
|
||||
} else {
|
||||
$selectAudioOutputParent.hide();
|
||||
}
|
||||
|
||||
APP.translation.translateElement($('#settings_container option'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,889 +0,0 @@
|
||||
/* global AJS, APP, $, config, interfaceConfig, JitsiMeetJS */
|
||||
import UIUtil from '../util/UIUtil';
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
import SideContainerToggler from "../side_pannels/SideContainerToggler";
|
||||
|
||||
let emitter = null;
|
||||
let Toolbar;
|
||||
|
||||
/**
|
||||
* Handlers for toolbar buttons.
|
||||
*
|
||||
* buttonId {string}: handler {function}
|
||||
*/
|
||||
const buttonHandlers = {
|
||||
"toolbar_button_profile": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.profile.toggled');
|
||||
emitter.emit(UIEvents.TOGGLE_PROFILE);
|
||||
},
|
||||
"toolbar_button_mute": function () {
|
||||
let sharedVideoManager = APP.UI.getSharedVideoManager();
|
||||
|
||||
if (APP.conference.audioMuted) {
|
||||
// If there's a shared video with the volume "on" and we aren't
|
||||
// the video owner, we warn the user
|
||||
// that currently it's not possible to unmute.
|
||||
if (sharedVideoManager
|
||||
&& sharedVideoManager.isSharedVideoVolumeOn()
|
||||
&& !sharedVideoManager.isSharedVideoOwner()) {
|
||||
APP.UI.showCustomToolbarPopup(
|
||||
'#unableToUnmutePopup', true, 5000);
|
||||
}
|
||||
else {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.audio.unmuted');
|
||||
emitter.emit(UIEvents.AUDIO_MUTED, false, true);
|
||||
}
|
||||
} else {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.audio.muted');
|
||||
emitter.emit(UIEvents.AUDIO_MUTED, true, true);
|
||||
}
|
||||
},
|
||||
"toolbar_button_camera": function () {
|
||||
if (APP.conference.videoMuted) {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.video.enabled');
|
||||
emitter.emit(UIEvents.VIDEO_MUTED, false);
|
||||
} else {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.video.disabled');
|
||||
emitter.emit(UIEvents.VIDEO_MUTED, true);
|
||||
}
|
||||
},
|
||||
"toolbar_button_link": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.invite.clicked');
|
||||
emitter.emit(UIEvents.INVITE_CLICKED);
|
||||
},
|
||||
"toolbar_button_chat": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.chat.toggled');
|
||||
emitter.emit(UIEvents.TOGGLE_CHAT);
|
||||
},
|
||||
"toolbar_contact_list": function () {
|
||||
JitsiMeetJS.analytics.sendEvent(
|
||||
'toolbar.contacts.toggled');
|
||||
emitter.emit(UIEvents.TOGGLE_CONTACT_LIST);
|
||||
},
|
||||
"toolbar_button_etherpad": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.etherpad.clicked');
|
||||
emitter.emit(UIEvents.ETHERPAD_CLICKED);
|
||||
},
|
||||
"toolbar_button_sharedvideo": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.sharedvideo.clicked');
|
||||
emitter.emit(UIEvents.SHARED_VIDEO_CLICKED);
|
||||
},
|
||||
"toolbar_button_desktopsharing": function () {
|
||||
if (APP.conference.isSharingScreen) {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.screen.disabled');
|
||||
} else {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.screen.enabled');
|
||||
}
|
||||
emitter.emit(UIEvents.TOGGLE_SCREENSHARING);
|
||||
},
|
||||
"toolbar_button_fullScreen": function() {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.fullscreen.enabled');
|
||||
|
||||
emitter.emit(UIEvents.TOGGLE_FULLSCREEN);
|
||||
},
|
||||
"toolbar_button_sip": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.sip.clicked');
|
||||
showSipNumberInput();
|
||||
},
|
||||
"toolbar_button_dialpad": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.sip.dialpad.clicked');
|
||||
dialpadButtonClicked();
|
||||
},
|
||||
"toolbar_button_settings": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.settings.toggled');
|
||||
emitter.emit(UIEvents.TOGGLE_SETTINGS);
|
||||
},
|
||||
"toolbar_button_hangup": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.hangup');
|
||||
emitter.emit(UIEvents.HANGUP);
|
||||
},
|
||||
"toolbar_button_raisehand": function () {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.raiseHand.clicked');
|
||||
APP.conference.maybeToggleRaisedHand();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* All toolbars buttons description
|
||||
*/
|
||||
const defaultToolbarButtons = {
|
||||
'microphone': {
|
||||
id: 'toolbar_button_mute',
|
||||
tooltipKey: 'toolbar.mute',
|
||||
className: "button icon-microphone",
|
||||
shortcut: 'M',
|
||||
shortcutAttr: 'mutePopover',
|
||||
shortcutFunc: function() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.audiomute.toggled');
|
||||
APP.conference.toggleAudioMuted();
|
||||
},
|
||||
shortcutDescription: "keyboardShortcuts.mute",
|
||||
popups: [
|
||||
{
|
||||
id: 'micMutedPopup',
|
||||
className: 'loginmenu',
|
||||
dataAttr: '[title]toolbar.micMutedPopup'
|
||||
},
|
||||
{
|
||||
id: 'unableToUnmutePopup',
|
||||
className: 'loginmenu',
|
||||
dataAttr: '[title]toolbar.unableToUnmutePopup'
|
||||
},
|
||||
{
|
||||
id: 'talkWhileMutedPopup',
|
||||
className: 'loginmenu',
|
||||
dataAttr: '[title]toolbar.talkWhileMutedPopup'
|
||||
}
|
||||
],
|
||||
content: "Mute / Unmute",
|
||||
i18n: "[content]toolbar.mute"
|
||||
},
|
||||
'camera': {
|
||||
id: 'toolbar_button_camera',
|
||||
tooltipKey: 'toolbar.videomute',
|
||||
className: "button icon-camera",
|
||||
shortcut: 'V',
|
||||
shortcutAttr: 'toggleVideoPopover',
|
||||
shortcutFunc: function() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.videomute.toggled');
|
||||
APP.conference.toggleVideoMuted();
|
||||
},
|
||||
shortcutDescription: "keyboardShortcuts.videoMute",
|
||||
content: "Start / stop camera",
|
||||
i18n: "[content]toolbar.videomute"
|
||||
},
|
||||
'desktop': {
|
||||
id: 'toolbar_button_desktopsharing',
|
||||
tooltipKey: 'toolbar.sharescreen',
|
||||
className: 'button icon-share-desktop',
|
||||
shortcut: 'D',
|
||||
shortcutAttr: 'toggleDesktopSharingPopover',
|
||||
shortcutFunc: function() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.screen.toggled');
|
||||
APP.conference.toggleScreenSharing();
|
||||
},
|
||||
shortcutDescription: 'keyboardShortcuts.toggleScreensharing',
|
||||
content: 'Share screen',
|
||||
i18n: '[content]toolbar.sharescreen'
|
||||
},
|
||||
'invite': {
|
||||
id: 'toolbar_button_link',
|
||||
tooltipKey: 'toolbar.invite',
|
||||
className: 'button icon-link',
|
||||
content: 'Invite others',
|
||||
i18n: '[content]toolbar.invite'
|
||||
},
|
||||
'chat': {
|
||||
id: 'toolbar_button_chat',
|
||||
tooltipKey: 'toolbar.chat',
|
||||
className: 'button icon-chat',
|
||||
shortcut: 'C',
|
||||
shortcutAttr: 'toggleChatPopover',
|
||||
shortcutFunc: function() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.chat.toggled');
|
||||
APP.UI.toggleChat();
|
||||
},
|
||||
shortcutDescription: 'keyboardShortcuts.toggleChat',
|
||||
sideContainerId: 'chat_container',
|
||||
html: `<span class="badge-round">
|
||||
<span id="unreadMessages"></span>
|
||||
</span>`
|
||||
},
|
||||
'contacts': {
|
||||
id: 'toolbar_contact_list',
|
||||
tooltipKey: 'bottomtoolbar.contactlist',
|
||||
className: 'button icon-contactList',
|
||||
sideContainerId: 'contacts_container',
|
||||
html: `<span class="badge-round">
|
||||
<span id="numberOfParticipants"></span>
|
||||
</span>`
|
||||
},
|
||||
'profile': {
|
||||
id: 'toolbar_button_profile',
|
||||
tooltipKey: 'profile.setDisplayNameLabel',
|
||||
className: 'button',
|
||||
sideContainerId: 'profile_container',
|
||||
html: `<img id="avatar" src="images/avatar2.png"/>`
|
||||
},
|
||||
'etherpad': {
|
||||
id: 'toolbar_button_etherpad',
|
||||
tooltipKey: 'toolbar.etherpad',
|
||||
className: 'button icon-share-doc'
|
||||
},
|
||||
'fullscreen': {
|
||||
id: 'toolbar_button_fullScreen',
|
||||
tooltipKey: 'toolbar.fullscreen',
|
||||
className: "button icon-full-screen",
|
||||
shortcut: 'S',
|
||||
shortcutAttr: 'toggleFullscreenPopover',
|
||||
shortcutFunc: function() {
|
||||
JitsiMeetJS.analytics.sendEvent('shortcut.fullscreen.toggled');
|
||||
APP.UI.toggleFullScreen();
|
||||
},
|
||||
shortcutDescription: "keyboardShortcuts.fullScreen",
|
||||
content: "Enter / Exit Full Screen",
|
||||
i18n: "[content]toolbar.fullscreen"
|
||||
},
|
||||
'settings': {
|
||||
id: 'toolbar_button_settings',
|
||||
tooltipKey: 'toolbar.Settings',
|
||||
className: 'button icon-settings',
|
||||
sideContainerId: "settings_container"
|
||||
},
|
||||
'hangup': {
|
||||
id: 'toolbar_button_hangup',
|
||||
tooltipKey: 'toolbar.hangup',
|
||||
className: "button icon-hangup",
|
||||
content: "Hang Up",
|
||||
i18n: "[content]toolbar.hangup"
|
||||
},
|
||||
'raisehand': {
|
||||
id: "toolbar_button_raisehand",
|
||||
tooltipKey: 'toolbar.raiseHand',
|
||||
className: "button icon-raised-hand",
|
||||
shortcut: "R",
|
||||
shortcutAttr: "raiseHandPopover",
|
||||
shortcutFunc: function() {
|
||||
JitsiMeetJS.analytics.sendEvent("shortcut.raisehand.clicked");
|
||||
APP.conference.maybeToggleRaisedHand();
|
||||
},
|
||||
shortcutDescription: "keyboardShortcuts.raiseHand",
|
||||
content: "Raise Hand",
|
||||
i18n: "[content]toolbar.raiseHand"
|
||||
},
|
||||
//init and btn handler: Recording.initRecordingButton (Recording.js)
|
||||
'recording': {
|
||||
id: 'toolbar_button_record',
|
||||
tooltipKey: 'liveStreaming.buttonTooltip',
|
||||
className: 'button',
|
||||
hidden: true // will be displayed once
|
||||
// the recording functionality is detected
|
||||
},
|
||||
'sharedvideo': {
|
||||
id: 'toolbar_button_sharedvideo',
|
||||
tooltipKey: 'toolbar.sharedvideo',
|
||||
className: 'button icon-shared-video',
|
||||
popups: [
|
||||
{
|
||||
id: 'sharedVideoMutedPopup',
|
||||
className: 'loginmenu extendedToolbarPopup',
|
||||
dataAttr: '[title]toolbar.sharedVideoMutedPopup',
|
||||
dataAttrPosition: 'w'
|
||||
}
|
||||
]
|
||||
},
|
||||
'sip': {
|
||||
id: 'toolbar_button_sip',
|
||||
tooltipKey: 'toolbar.sip',
|
||||
className: 'button icon-telephone',
|
||||
hidden: true // will be displayed once
|
||||
// the SIP calls functionality is detected
|
||||
},
|
||||
'dialpad': {
|
||||
id: 'toolbar_button_dialpad',
|
||||
tooltipKey: 'toolbar.dialpad',
|
||||
className: 'button icon-dialpad',
|
||||
//TODO: remove it after UI.updateDTMFSupport fix
|
||||
hidden: true
|
||||
}
|
||||
};
|
||||
|
||||
function dialpadButtonClicked() {
|
||||
//TODO show the dialpad box
|
||||
}
|
||||
|
||||
function showSipNumberInput () {
|
||||
let defaultNumber = config.defaultSipNumber
|
||||
? config.defaultSipNumber
|
||||
: '';
|
||||
let titleKey = "dialog.sipMsg";
|
||||
let msgString = (`
|
||||
<input class="input-control"
|
||||
name="sipNumber" type="text"
|
||||
value="${defaultNumber}" autofocus>`);
|
||||
|
||||
APP.UI.messageHandler.openTwoButtonDialog({
|
||||
titleKey,
|
||||
msgString,
|
||||
leftButtonKey: "dialog.Dial",
|
||||
submitFunction: function (e, v, m, f) {
|
||||
if (v && f.sipNumber) {
|
||||
emitter.emit(UIEvents.SIP_DIAL, f.sipNumber);
|
||||
}
|
||||
},
|
||||
focus: ':input:first'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get place for toolbar button.
|
||||
* Now it can be in main toolbar or in extended (left) toolbar
|
||||
*
|
||||
* @param btn {string}
|
||||
* @returns {string}
|
||||
*/
|
||||
function getToolbarButtonPlace (btn) {
|
||||
return interfaceConfig.MAIN_TOOLBAR_BUTTONS.includes(btn) ?
|
||||
'main' :
|
||||
'extended';
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for side toolbar container toggled event.
|
||||
*
|
||||
* @param {string} containerId - ID of the container.
|
||||
* @param {boolean} isVisible - Flag showing whether container
|
||||
* is visible.
|
||||
* @returns {void}
|
||||
*/
|
||||
function onSideToolbarContainerToggled(containerId, isVisible) {
|
||||
Toolbar._handleSideToolbarContainerToggled(containerId, isVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for local raise hand changed event.
|
||||
*
|
||||
* @param {boolean} isRaisedHand - Flag showing whether hand is raised.
|
||||
* @returns {void}
|
||||
*/
|
||||
function onLocalRaiseHandChanged(isRaisedHand) {
|
||||
Toolbar._setToggledState("toolbar_button_raisehand", isRaisedHand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for full screen toggled event.
|
||||
*
|
||||
* @param {boolean} isFullScreen - Flag showing whether app in full
|
||||
* screen mode.
|
||||
* @returns {void}
|
||||
*/
|
||||
function onFullScreenToggled(isFullScreen) {
|
||||
Toolbar._handleFullScreenToggled(isFullScreen);
|
||||
}
|
||||
|
||||
Toolbar = {
|
||||
init (eventEmitter) {
|
||||
emitter = eventEmitter;
|
||||
// The toolbar is enabled by default.
|
||||
this.enabled = true;
|
||||
this.toolbarSelector = $("#mainToolbarContainer");
|
||||
this.extendedToolbarSelector = $("#extendedToolbar");
|
||||
|
||||
// Unregister listeners in case of reinitialization.
|
||||
this.unregisterListeners();
|
||||
|
||||
// Initialise the toolbar buttons.
|
||||
// The main toolbar will only take into account
|
||||
// it's own configuration from interface_config.
|
||||
this._initToolbarButtons();
|
||||
|
||||
this._setShortcutsAndTooltips();
|
||||
|
||||
this._setButtonHandlers();
|
||||
|
||||
this.registerListeners();
|
||||
|
||||
APP.UI.addListener(UIEvents.SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP,
|
||||
(popupID, show, timeout) => {
|
||||
Toolbar._showCustomToolbarPopup(popupID, show, timeout);
|
||||
});
|
||||
|
||||
if(!APP.tokenData.isGuest) {
|
||||
$("#toolbar_button_profile").addClass("unclickable");
|
||||
UIUtil.removeTooltip(
|
||||
document.getElementById('toolbar_button_profile'));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Register listeners for UI events of toolbar component.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
registerListeners() {
|
||||
APP.UI.addListener(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
|
||||
onSideToolbarContainerToggled);
|
||||
|
||||
APP.UI.addListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
|
||||
onLocalRaiseHandChanged);
|
||||
|
||||
APP.UI.addListener(UIEvents.FULLSCREEN_TOGGLED, onFullScreenToggled);
|
||||
},
|
||||
/**
|
||||
* Unregisters handlers for UI events of Toolbar component.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
unregisterListeners() {
|
||||
APP.UI.removeListener(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
|
||||
onSideToolbarContainerToggled);
|
||||
|
||||
APP.UI.removeListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
|
||||
onLocalRaiseHandChanged);
|
||||
|
||||
APP.UI.removeListener(UIEvents.FULLSCREEN_TOGGLED,
|
||||
onFullScreenToggled);
|
||||
},
|
||||
/**
|
||||
* Enables / disables the toolbar.
|
||||
* @param {e} set to {true} to enable the toolbar or {false}
|
||||
* to disable it
|
||||
*/
|
||||
enable (e) {
|
||||
this.enabled = e;
|
||||
if (!e && this.isVisible())
|
||||
this.hide(false);
|
||||
},
|
||||
/**
|
||||
* Indicates if the bottom toolbar is currently enabled.
|
||||
* @return {this.enabled}
|
||||
*/
|
||||
isEnabled() {
|
||||
return this.enabled;
|
||||
},
|
||||
|
||||
showEtherpadButton () {
|
||||
if (!$('#toolbar_button_etherpad').is(":visible")) {
|
||||
$('#toolbar_button_etherpad').css({display: 'inline-block'});
|
||||
}
|
||||
},
|
||||
|
||||
// Shows or hides the 'shared video' button.
|
||||
showSharedVideoButton () {
|
||||
let id = 'toolbar_button_sharedvideo';
|
||||
let shouldShow = UIUtil.isButtonEnabled('sharedvideo')
|
||||
&& !config.disableThirdPartyRequests;
|
||||
|
||||
if (shouldShow) {
|
||||
let el = document.getElementById(id);
|
||||
UIUtil.setTooltip(el, 'toolbar.sharedvideo', 'right');
|
||||
}
|
||||
UIUtil.setVisible(id, shouldShow);
|
||||
},
|
||||
|
||||
// checks whether desktop sharing is enabled and whether
|
||||
// we have params to start automatically sharing
|
||||
checkAutoEnableDesktopSharing () {
|
||||
if (UIUtil.isButtonEnabled('desktop')
|
||||
&& config.autoEnableDesktopSharing) {
|
||||
emitter.emit(UIEvents.TOGGLE_SCREENSHARING);
|
||||
}
|
||||
},
|
||||
|
||||
// Shows or hides SIP calls button
|
||||
showSipCallButton (show) {
|
||||
let shouldShow = APP.conference.sipGatewayEnabled()
|
||||
&& UIUtil.isButtonEnabled('sip') && show;
|
||||
let id = 'toolbar_button_sip';
|
||||
|
||||
UIUtil.setVisible(id, shouldShow);
|
||||
},
|
||||
|
||||
// Shows or hides the dialpad button
|
||||
showDialPadButton (show) {
|
||||
let shouldShow = UIUtil.isButtonEnabled('dialpad') && show;
|
||||
let id = 'toolbar_button_dialpad';
|
||||
|
||||
UIUtil.setVisible(id, shouldShow);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the state of the button. The button has blue glow if desktop
|
||||
* streaming is active.
|
||||
*/
|
||||
updateDesktopSharingButtonState () {
|
||||
this._setToggledState( "toolbar_button_desktopsharing",
|
||||
APP.conference.isSharingScreen);
|
||||
},
|
||||
|
||||
/**
|
||||
* Marks video icon as muted or not.
|
||||
*
|
||||
* @param {boolean} muted if icon should look like muted or not
|
||||
*/
|
||||
toggleVideoIcon (muted) {
|
||||
$('#toolbar_button_camera').toggleClass("icon-camera-disabled", muted);
|
||||
|
||||
this._setToggledState("toolbar_button_camera", muted);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables / disables audio toolbar button.
|
||||
*
|
||||
* @param {boolean} enabled indicates if the button should be enabled
|
||||
* or disabled
|
||||
*/
|
||||
setVideoIconEnabled (enabled) {
|
||||
this._setMediaIconEnabled(
|
||||
'#toolbar_button_camera',
|
||||
enabled,
|
||||
/* data-i18n attribute value */
|
||||
`[content]toolbar.${enabled ? 'videomute' : 'cameraDisabled'}`,
|
||||
/* shortcut attribute value */
|
||||
'toggleVideoPopover');
|
||||
|
||||
enabled || this.toggleVideoIcon(!enabled);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables/disables the toolbar button associated with a specific media such
|
||||
* as audio or video.
|
||||
*
|
||||
* @param {string} btn - The jQuery selector <tt>string</tt> which
|
||||
* identifies the toolbar button to be enabled/disabled.
|
||||
* @param {boolean} enabled - <tt>true</tt> to enable the specified
|
||||
* <tt>btn</tt> or <tt>false</tt> to disable it.
|
||||
* @param {string} dataI18n - The value to assign to the <tt>data-i18n</tt>
|
||||
* attribute of the specified <tt>btn</tt>.
|
||||
* @param {string} shortcut - The value, if any, to assign to the
|
||||
* <tt>shortcut</tt> attribute of the specified <tt>btn</tt> if the toolbar
|
||||
* button is enabled.
|
||||
*/
|
||||
_setMediaIconEnabled(btn, enabled, dataI18n, shortcut) {
|
||||
const $btn = $(btn);
|
||||
|
||||
$btn
|
||||
.prop('disabled', !enabled)
|
||||
.attr('data-i18n', dataI18n)
|
||||
.attr('shortcut', enabled && shortcut ? shortcut : '');
|
||||
|
||||
enabled
|
||||
? $btn.removeAttr('disabled')
|
||||
: $btn.attr('disabled', 'disabled');
|
||||
|
||||
APP.translation.translateElement($btn);
|
||||
},
|
||||
|
||||
/**
|
||||
* Marks audio icon as muted or not.
|
||||
*
|
||||
* @param {boolean} muted if icon should look like muted or not
|
||||
*/
|
||||
toggleAudioIcon(muted) {
|
||||
$('#toolbar_button_mute')
|
||||
.toggleClass("icon-microphone", !muted)
|
||||
.toggleClass("icon-mic-disabled", muted);
|
||||
|
||||
this._setToggledState("toolbar_button_mute", muted);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables / disables audio toolbar button.
|
||||
*
|
||||
* @param {boolean} enabled indicates if the button should be enabled
|
||||
* or disabled
|
||||
*/
|
||||
setAudioIconEnabled (enabled) {
|
||||
this._setMediaIconEnabled(
|
||||
'#toolbar_button_mute',
|
||||
enabled,
|
||||
/* data-i18n attribute value */
|
||||
`[content]toolbar.${enabled ? 'mute' : 'micDisabled'}`,
|
||||
/* shortcut attribute value */
|
||||
'mutePopover');
|
||||
|
||||
enabled || this.toggleAudioIcon(!enabled);
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates if the toolbar is currently hovered.
|
||||
* @return {boolean} true if the toolbar is currently hovered,
|
||||
* false otherwise
|
||||
*/
|
||||
isHovered() {
|
||||
var hovered = false;
|
||||
this.toolbarSelector.find('*').each(function () {
|
||||
let id = $(this).attr('id');
|
||||
if ($(`#${id}:hover`).length > 0) {
|
||||
hovered = true;
|
||||
// break each
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (hovered)
|
||||
return true;
|
||||
if ($("#bottomToolbar:hover").length > 0
|
||||
|| $("#extendedToolbar:hover").length > 0
|
||||
|| SideContainerToggler.isHovered()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if this toolbar is currently visible, or false otherwise.
|
||||
* @return <tt>true</tt> if currently visible, <tt>false</tt> - otherwise
|
||||
*/
|
||||
isVisible() {
|
||||
return this.toolbarSelector.hasClass("fadeIn");
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides the toolbar with animation or not depending on the animate
|
||||
* parameter.
|
||||
*/
|
||||
hide() {
|
||||
this.toolbarSelector
|
||||
.removeClass("fadeIn")
|
||||
.addClass("fadeOut");
|
||||
|
||||
let slideInAnimation = (SideContainerToggler.isVisible)
|
||||
? "slideInExtX"
|
||||
: "slideInX";
|
||||
let slideOutAnimation = (SideContainerToggler.isVisible)
|
||||
? "slideOutExtX"
|
||||
: "slideOutX";
|
||||
|
||||
this.extendedToolbarSelector.toggleClass(slideInAnimation)
|
||||
.toggleClass(slideOutAnimation);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows the toolbar with animation or not depending on the animate
|
||||
* parameter.
|
||||
*/
|
||||
show() {
|
||||
if (this.toolbarSelector.hasClass("fadeOut")) {
|
||||
this.toolbarSelector.removeClass("fadeOut");
|
||||
}
|
||||
|
||||
let slideInAnimation = (SideContainerToggler.isVisible)
|
||||
? "slideInExtX"
|
||||
: "slideInX";
|
||||
let slideOutAnimation = (SideContainerToggler.isVisible)
|
||||
? "slideOutExtX"
|
||||
: "slideOutX";
|
||||
|
||||
if (this.extendedToolbarSelector.hasClass(slideOutAnimation)) {
|
||||
this.extendedToolbarSelector.toggleClass(slideOutAnimation);
|
||||
}
|
||||
|
||||
this.toolbarSelector.addClass("fadeIn");
|
||||
this.extendedToolbarSelector.toggleClass(slideInAnimation);
|
||||
},
|
||||
|
||||
registerClickListeners(listener) {
|
||||
$('#mainToolbarContainer').click(listener);
|
||||
|
||||
$("#extendedToolbar").click(listener);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the side toolbar toggle.
|
||||
*
|
||||
* @param {string} containerId the identifier of the container element
|
||||
*/
|
||||
_handleSideToolbarContainerToggled(containerId) {
|
||||
Object.keys(defaultToolbarButtons).forEach(
|
||||
id => {
|
||||
if (!UIUtil.isButtonEnabled(id))
|
||||
return;
|
||||
|
||||
var button = defaultToolbarButtons[id];
|
||||
|
||||
if (button.sideContainerId
|
||||
&& button.sideContainerId === containerId) {
|
||||
UIUtil.buttonClick(button.id, "selected");
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles full screen toggled.
|
||||
*
|
||||
* @param {boolean} isFullScreen indicates if we're currently in full
|
||||
* screen mode
|
||||
*/
|
||||
_handleFullScreenToggled(isFullScreen) {
|
||||
let element
|
||||
= document.getElementById("toolbar_button_fullScreen");
|
||||
|
||||
element.className = isFullScreen
|
||||
? element.className
|
||||
.replace("icon-full-screen", "icon-exit-full-screen")
|
||||
: element.className
|
||||
.replace("icon-exit-full-screen", "icon-full-screen");
|
||||
|
||||
Toolbar._setToggledState("toolbar_button_fullScreen", isFullScreen);
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialise toolbar buttons.
|
||||
*/
|
||||
_initToolbarButtons() {
|
||||
interfaceConfig.TOOLBAR_BUTTONS.forEach((value, index) => {
|
||||
let place = getToolbarButtonPlace(value);
|
||||
|
||||
if (value && value in defaultToolbarButtons) {
|
||||
let button = defaultToolbarButtons[value];
|
||||
this._addToolbarButton(
|
||||
button,
|
||||
place,
|
||||
(interfaceConfig.MAIN_TOOLBAR_SPLITTER_INDEX !== undefined
|
||||
&& index
|
||||
=== interfaceConfig.MAIN_TOOLBAR_SPLITTER_INDEX));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the given button to the main (top) or extended (left) toolbar.
|
||||
*
|
||||
* @param {Object} the button to add.
|
||||
* @param {boolean} isFirst indicates if this is the first button in the
|
||||
* toolbar
|
||||
* @param {boolean} isLast indicates if this is the last button in the
|
||||
* toolbar
|
||||
* @param {boolean} isSplitter if this button is a splitter button for
|
||||
* the dialog, which means that a special splitter style will be applied
|
||||
*/
|
||||
_addToolbarButton(button, place, isSplitter) {
|
||||
const places = {
|
||||
main: 'mainToolbar',
|
||||
extended: 'extendedToolbarButtons'
|
||||
};
|
||||
let id = places[place];
|
||||
let buttonElement = document.createElement("a");
|
||||
if (button.className) {
|
||||
buttonElement.className = button.className;
|
||||
}
|
||||
|
||||
if (isSplitter) {
|
||||
let splitter = document.createElement('span');
|
||||
splitter.className = 'toolbar__splitter';
|
||||
document.getElementById(id).appendChild(splitter);
|
||||
}
|
||||
|
||||
buttonElement.id = button.id;
|
||||
|
||||
if (button.html)
|
||||
buttonElement.innerHTML = button.html;
|
||||
|
||||
//TODO: remove it after UI.updateDTMFSupport fix
|
||||
if (button.hidden)
|
||||
buttonElement.style.display = 'none';
|
||||
|
||||
if (button.shortcutAttr)
|
||||
buttonElement.setAttribute("shortcut", button.shortcutAttr);
|
||||
|
||||
if (button.content)
|
||||
buttonElement.setAttribute("content", button.content);
|
||||
|
||||
if (button.i18n)
|
||||
buttonElement.setAttribute("data-i18n", button.i18n);
|
||||
|
||||
buttonElement.setAttribute("data-container", "body");
|
||||
buttonElement.setAttribute("data-placement", "bottom");
|
||||
this._addPopups(buttonElement, button.popups);
|
||||
|
||||
document.getElementById(id)
|
||||
.appendChild(buttonElement);
|
||||
},
|
||||
|
||||
_addPopups(buttonElement, popups = []) {
|
||||
popups.forEach((popup) => {
|
||||
const popupElement = document.createElement('div');
|
||||
popupElement.id = popup.id;
|
||||
popupElement.className = popup.className;
|
||||
popupElement.setAttribute('data-i18n', popup.dataAttr);
|
||||
|
||||
let gravity = 'n';
|
||||
if (popup.dataAttrPosition)
|
||||
gravity = popup.dataAttrPosition;
|
||||
// use custom attribute to save gravity option
|
||||
// we use 'data-tooltip' in UIUtil to activate all tooltips
|
||||
// but we want these to be manually triggered
|
||||
popupElement.setAttribute('tooltip-gravity', gravity);
|
||||
|
||||
APP.translation.translateElement($(popupElement));
|
||||
|
||||
buttonElement.appendChild(popupElement);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show custom popup/tooltip for a specified button.
|
||||
* @param popupSelectorID the selector id of the popup to show
|
||||
* @param show true or false/show or hide the popup
|
||||
* @param timeout the time to show the popup
|
||||
*/
|
||||
_showCustomToolbarPopup(popupSelectorID, show, timeout) {
|
||||
|
||||
const gravity = $(popupSelectorID).attr('tooltip-gravity');
|
||||
AJS.$(popupSelectorID)
|
||||
.tooltip({
|
||||
trigger: 'manual',
|
||||
html: true,
|
||||
gravity: gravity,
|
||||
title: 'title'});
|
||||
if (show) {
|
||||
AJS.$(popupSelectorID).tooltip('show');
|
||||
setTimeout(function () {
|
||||
// hide the tooltip
|
||||
AJS.$(popupSelectorID).tooltip('hide');
|
||||
}, timeout);
|
||||
} else {
|
||||
AJS.$(popupSelectorID).tooltip('hide');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the toggled state of the given element depending on the isToggled
|
||||
* parameter.
|
||||
*
|
||||
* @param elementId the element identifier
|
||||
* @param isToggled indicates if the element should be toggled or untoggled
|
||||
*/
|
||||
_setToggledState(elementId, isToggled) {
|
||||
$("#" + elementId).toggleClass("toggled", isToggled);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets Shortcuts and Tooltips for all toolbar buttons
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_setShortcutsAndTooltips() {
|
||||
Object.keys(defaultToolbarButtons).forEach(
|
||||
id => {
|
||||
if (UIUtil.isButtonEnabled(id)) {
|
||||
let button = defaultToolbarButtons[id];
|
||||
let buttonElement = document.getElementById(button.id);
|
||||
if (!buttonElement) return false;
|
||||
let tooltipPosition
|
||||
= (interfaceConfig.MAIN_TOOLBAR_BUTTONS
|
||||
.indexOf(id) > -1)
|
||||
? "bottom" : "right";
|
||||
|
||||
UIUtil.setTooltip( buttonElement,
|
||||
button.tooltipKey,
|
||||
tooltipPosition);
|
||||
|
||||
if (button.shortcut)
|
||||
APP.keyboardshortcut.registerShortcut(
|
||||
button.shortcut,
|
||||
button.shortcutAttr,
|
||||
button.shortcutFunc,
|
||||
button.shortcutDescription
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets Handlers for all toolbar buttons
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_setButtonHandlers() {
|
||||
Object.keys(buttonHandlers).forEach(
|
||||
buttonId => $(`#${buttonId}`).click(function(event) {
|
||||
!$(this).prop('disabled') && buttonHandlers[buttonId](event);
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Toolbar;
|
||||
@@ -1,149 +0,0 @@
|
||||
/* global APP, config, $, interfaceConfig */
|
||||
|
||||
import UIUtil from '../util/UIUtil';
|
||||
import Toolbar from './Toolbar';
|
||||
import SideContainerToggler from "../side_pannels/SideContainerToggler";
|
||||
|
||||
let toolbarTimeoutObject;
|
||||
let toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
|
||||
/**
|
||||
* If true the toolbar will be always displayed
|
||||
*/
|
||||
let alwaysVisibleToolbar = false;
|
||||
|
||||
function showDesktopSharingButton() {
|
||||
if (APP.conference.isDesktopSharingEnabled &&
|
||||
UIUtil.isButtonEnabled('desktop')) {
|
||||
$('#toolbar_button_desktopsharing').css({display: "inline-block"});
|
||||
} else {
|
||||
$('#toolbar_button_desktopsharing').css({display: "none"});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the toolbar.
|
||||
*
|
||||
* @param force {true} to force the hiding of the toolbar without caring about
|
||||
* the extended toolbar side panels.
|
||||
*/
|
||||
function hideToolbar(force) { // eslint-disable-line no-unused-vars
|
||||
if (alwaysVisibleToolbar) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(toolbarTimeoutObject);
|
||||
toolbarTimeoutObject = null;
|
||||
|
||||
if (force !== true &&
|
||||
(Toolbar.isHovered()
|
||||
|| APP.UI.isRingOverlayVisible()
|
||||
|| SideContainerToggler.isVisible())) {
|
||||
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
|
||||
} else {
|
||||
Toolbar.hide();
|
||||
$('#subject').animate({top: "-=40"}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
const ToolbarToggler = {
|
||||
/**
|
||||
* Initializes the ToolbarToggler
|
||||
*/
|
||||
init() {
|
||||
alwaysVisibleToolbar = (config.alwaysVisibleToolbar === true);
|
||||
|
||||
// disabled
|
||||
//this._registerWindowClickListeners();
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers click listeners handling the show and hode of toolbars when
|
||||
* user clicks outside of toolbar area.
|
||||
*/
|
||||
_registerWindowClickListeners() {
|
||||
$(window).click(function() {
|
||||
(Toolbar.isEnabled() && Toolbar.isVisible())
|
||||
? hideToolbar(true)
|
||||
: this.showToolbar();
|
||||
}.bind(this));
|
||||
|
||||
Toolbar.registerClickListeners(function(event){
|
||||
event.stopPropagation();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the value of alwaysVisibleToolbar variable.
|
||||
* @param value {boolean} the new value of alwaysVisibleToolbar variable
|
||||
*/
|
||||
setAlwaysVisibleToolbar(value) {
|
||||
alwaysVisibleToolbar = value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets the value of alwaysVisibleToolbar variable to the default one.
|
||||
*/
|
||||
resetAlwaysVisibleToolbar() {
|
||||
alwaysVisibleToolbar = (config.alwaysVisibleToolbar === true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows the main toolbar.
|
||||
* @param timeout (optional) to specify custom timeout value
|
||||
*/
|
||||
showToolbar (timeout) {
|
||||
if (interfaceConfig.filmStripOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
var updateTimeout = false;
|
||||
if (Toolbar.isEnabled() && !Toolbar.isVisible()) {
|
||||
Toolbar.show();
|
||||
$('#subject').animate({top: "+=40"}, 300);
|
||||
updateTimeout = true;
|
||||
}
|
||||
|
||||
if (updateTimeout) {
|
||||
if (toolbarTimeoutObject) {
|
||||
clearTimeout(toolbarTimeoutObject);
|
||||
toolbarTimeoutObject = null;
|
||||
}
|
||||
toolbarTimeoutObject
|
||||
= setTimeout(hideToolbar, timeout || toolbarTimeout);
|
||||
toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
|
||||
}
|
||||
|
||||
// Show/hide desktop sharing button
|
||||
showDesktopSharingButton();
|
||||
},
|
||||
|
||||
/**
|
||||
* Docks/undocks the toolbar.
|
||||
*
|
||||
* @param isDock indicates what operation to perform
|
||||
*/
|
||||
dockToolbar (isDock) {
|
||||
if (interfaceConfig.filmStripOnly || !Toolbar.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDock) {
|
||||
// First make sure the toolbar is shown.
|
||||
if (!Toolbar.isVisible()) {
|
||||
this.showToolbar();
|
||||
}
|
||||
|
||||
// Then clear the time out, to dock the toolbar.
|
||||
clearTimeout(toolbarTimeoutObject);
|
||||
toolbarTimeoutObject = null;
|
||||
} else {
|
||||
if (Toolbar.isVisible()) {
|
||||
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
|
||||
} else {
|
||||
this.showToolbar();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ToolbarToggler;
|
||||
@@ -157,11 +157,13 @@ const IndicatorFontSizes = {
|
||||
* @param position the position of the tooltip in relation to the element
|
||||
*/
|
||||
setTooltip(element, key, position) {
|
||||
if (element !== null) {
|
||||
element.setAttribute('data-tooltip', TOOLTIP_POSITIONS[position]);
|
||||
element.setAttribute('data-i18n', '[content]' + key);
|
||||
if (element) {
|
||||
const selector = element.jquery ? element : $(element);
|
||||
|
||||
APP.translation.translateElement($(element));
|
||||
selector.attr('data-tooltip', TOOLTIP_POSITIONS[position]);
|
||||
selector.attr('data-i18n', `[content]${key}`);
|
||||
|
||||
APP.translation.translateElement(selector);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ function ConnectionIndicator(videoContainer, videoId) {
|
||||
this.resolution = null;
|
||||
this.isResolutionHD = null;
|
||||
this.transport = [];
|
||||
this.framerate = null;
|
||||
this.popover = null;
|
||||
this.id = videoId;
|
||||
this.create();
|
||||
@@ -88,12 +89,17 @@ ConnectionIndicator.prototype.generateText = function () {
|
||||
}
|
||||
|
||||
// GENERATE RESOLUTIONS STRING
|
||||
let resolutions = this.resolution || {};
|
||||
let resolutionStr = Object.keys(resolutions).map(function (ssrc) {
|
||||
const resolutions = this.resolution || {};
|
||||
const resolutionStr = Object.keys(resolutions).map(ssrc => {
|
||||
let {width, height} = resolutions[ssrc];
|
||||
return `${width}x${height}`;
|
||||
}).join(', ') || 'N/A';
|
||||
|
||||
const framerates = this.framerate || {};
|
||||
const frameRateStr = Object.keys(framerates).map(ssrc =>
|
||||
framerates[ssrc]
|
||||
).join(', ') || 'N/A';
|
||||
|
||||
let result = (
|
||||
`<table class="connection-info__container" style='width:100%'>
|
||||
<tr>
|
||||
@@ -119,6 +125,14 @@ ConnectionIndicator.prototype.generateText = function () {
|
||||
${resolutionStr}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span data-i18n='connectionindicator.framerate'></span>
|
||||
</td>
|
||||
<td>
|
||||
${frameRateStr}
|
||||
</td>
|
||||
</tr>
|
||||
</table>`);
|
||||
|
||||
if(this.videoContainer.videoSpanId == "localVideoContainer") {
|
||||
@@ -150,7 +164,12 @@ ConnectionIndicator.prototype.generateText = function () {
|
||||
"data-i18n='connectionindicator.address'></span></td>" +
|
||||
"<td> N/A</td></tr>";
|
||||
} else {
|
||||
var data = {remoteIP: [], localIP:[], remotePort:[], localPort:[]};
|
||||
var data = {
|
||||
remoteIP: [],
|
||||
localIP:[],
|
||||
remotePort:[],
|
||||
localPort:[],
|
||||
transportType:[]};
|
||||
for(i = 0; i < this.transport.length; i++) {
|
||||
var ip = ConnectionIndicator.getIP(this.transport[i].ip);
|
||||
var port = ConnectionIndicator.getPort(this.transport[i].ip);
|
||||
@@ -173,8 +192,15 @@ ConnectionIndicator.prototype.generateText = function () {
|
||||
if(data.localPort.indexOf(localPort) == -1) {
|
||||
data.localPort.push(localPort);
|
||||
}
|
||||
|
||||
if(data.transportType.indexOf(this.transport[i].type) == -1) {
|
||||
data.transportType.push(this.transport[i].type);
|
||||
}
|
||||
}
|
||||
|
||||
// All of the transports should be either P2P or JVB
|
||||
const isP2P = this.transport.length ? this.transport[0].p2p : false;
|
||||
|
||||
var local_address_key = "connectionindicator.localaddress";
|
||||
var remote_address_key = "connectionindicator.remoteaddress";
|
||||
var localTransport =
|
||||
@@ -189,8 +215,14 @@ ConnectionIndicator.prototype.generateText = function () {
|
||||
remote_address_key + "' data-i18n-options='" +
|
||||
JSON.stringify({count: data.remoteIP.length})
|
||||
+ "'></span></td><td> " +
|
||||
ConnectionIndicator.getStringFromArray(data.remoteIP) +
|
||||
"</td></tr>";
|
||||
ConnectionIndicator.getStringFromArray(data.remoteIP);
|
||||
|
||||
// Append (p2p) to indicate the P2P type of transport
|
||||
if (isP2P) {
|
||||
transport
|
||||
+= "<span data-i18n='connectionindicator.peer_to_peer'>";
|
||||
}
|
||||
transport += "</td></tr>";
|
||||
|
||||
var key_remote = "connectionindicator.remoteport",
|
||||
key_local = "connectionindicator.localport";
|
||||
@@ -215,9 +247,13 @@ ConnectionIndicator.prototype.generateText = function () {
|
||||
transport += "</td></tr>";
|
||||
transport += localTransport + "</td></tr>";
|
||||
transport +="<tr>" +
|
||||
"<td><span data-i18n='connectionindicator.transport'>" +
|
||||
"</span></td>" +
|
||||
"<td>" + this.transport[0].type + "</td></tr>";
|
||||
"<td><span data-i18n='connectionindicator.transport' "
|
||||
+ " data-i18n-options='" +
|
||||
JSON.stringify({count: data.transportType.length})
|
||||
+ "'></span></td>" +
|
||||
"<td>"
|
||||
+ ConnectionIndicator.getStringFromArray(data.transportType);
|
||||
+ "</td></tr>";
|
||||
|
||||
}
|
||||
|
||||
@@ -321,17 +357,17 @@ ConnectionIndicator.prototype.remove = function() {
|
||||
* the user is having connectivity issues.
|
||||
*/
|
||||
ConnectionIndicator.prototype.updateConnectionStatusIndicator
|
||||
= function (isActive) {
|
||||
this.isConnectionActive = isActive;
|
||||
if (this.isConnectionActive) {
|
||||
$(this.interruptedIndicator).hide();
|
||||
$(this.emptyIcon).show();
|
||||
$(this.fullIcon).show();
|
||||
} else {
|
||||
$(this.interruptedIndicator).show();
|
||||
$(this.emptyIcon).hide();
|
||||
$(this.fullIcon).hide();
|
||||
}
|
||||
= function (isActive) {
|
||||
this.isConnectionActive = isActive;
|
||||
if (this.isConnectionActive) {
|
||||
$(this.interruptedIndicator).hide();
|
||||
$(this.emptyIcon).show();
|
||||
$(this.fullIcon).show();
|
||||
} else {
|
||||
$(this.interruptedIndicator).show();
|
||||
$(this.emptyIcon).hide();
|
||||
$(this.fullIcon).hide();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -358,6 +394,8 @@ ConnectionIndicator.prototype.updateConnectionQuality =
|
||||
if (object.resolution) {
|
||||
this.resolution = object.resolution;
|
||||
}
|
||||
if (object.framerate)
|
||||
this.framerate = object.framerate;
|
||||
}
|
||||
|
||||
let width = qualityToWidth.find(x => percent >= x.percent);
|
||||
@@ -380,6 +418,15 @@ ConnectionIndicator.prototype.updateResolution = function (resolution) {
|
||||
this.updatePopoverData();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the framerate
|
||||
* @param framerate the new resolution
|
||||
*/
|
||||
ConnectionIndicator.prototype.updateFramerate = function (framerate) {
|
||||
this.framerate = framerate;
|
||||
this.updatePopoverData();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the content of the popover if its visible
|
||||
* @param force to work even if popover is not visible
|
||||
|
||||
@@ -3,53 +3,55 @@
|
||||
import UIEvents from "../../../service/UI/UIEvents";
|
||||
import UIUtil from "../util/UIUtil";
|
||||
|
||||
const FilmStrip = {
|
||||
const Filmstrip = {
|
||||
/**
|
||||
*
|
||||
* @param eventEmitter the {EventEmitter} through which {FilmStrip} is to
|
||||
* emit/fire {UIEvents} (such as {UIEvents.TOGGLED_FILM_STRIP}).
|
||||
* @param eventEmitter the {EventEmitter} through which {Filmstrip} is to
|
||||
* emit/fire {UIEvents} (such as {UIEvents.TOGGLED_FILMSTRIP}).
|
||||
*/
|
||||
init (eventEmitter) {
|
||||
this.iconMenuDownClassName = 'icon-menu-down';
|
||||
this.iconMenuUpClassName = 'icon-menu-up';
|
||||
this.filmStripContainerClassName = 'filmstrip';
|
||||
this.filmStrip = $('#remoteVideos');
|
||||
this.filmstripContainerClassName = 'filmstrip';
|
||||
this.filmstrip = $('#remoteVideos');
|
||||
this.eventEmitter = eventEmitter;
|
||||
this._initFilmStripToolbar();
|
||||
this._initFilmstripToolbar();
|
||||
this.registerListeners();
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes the filmstrip toolbar
|
||||
* Initializes the filmstrip toolbar.
|
||||
*/
|
||||
_initFilmStripToolbar() {
|
||||
let toolbar = this._generateFilmStripToolbar();
|
||||
let className = this.filmStripContainerClassName;
|
||||
_initFilmstripToolbar() {
|
||||
// Do not show the toggle button in filmstrip only mode.
|
||||
if (interfaceConfig.filmStripOnly)
|
||||
return;
|
||||
|
||||
let toolbarContainerHTML = this._generateToolbarHTML();
|
||||
let className = this.filmstripContainerClassName;
|
||||
let container = document.querySelector(`.${className}`);
|
||||
|
||||
UIUtil.prependChild(container, toolbar);
|
||||
UIUtil.prependChild(container, toolbarContainerHTML);
|
||||
|
||||
let iconSelector = '#hideVideoToolbar i';
|
||||
this.toggleFilmStripIcon = document.querySelector(iconSelector);
|
||||
let iconSelector = '#toggleFilmstripButton i';
|
||||
this.toggleFilmstripIcon = document.querySelector(iconSelector);
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates HTML layout for filmstrip toolbar
|
||||
* Generates HTML layout for filmstrip toggle button and wrapping container.
|
||||
* @returns {HTMLElement}
|
||||
* @private
|
||||
*/
|
||||
_generateFilmStripToolbar() {
|
||||
_generateToolbarHTML() {
|
||||
let container = document.createElement('div');
|
||||
let isVisible = this.isFilmStripVisible();
|
||||
let isVisible = this.isFilmstripVisible();
|
||||
container.className = 'filmstrip__toolbar';
|
||||
if(!interfaceConfig.filmStripOnly) {
|
||||
container.innerHTML = `
|
||||
<button id="hideVideoToolbar">
|
||||
<i class="icon-menu-${isVisible ? 'down' : 'up'}">
|
||||
</i>
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
container.innerHTML = `
|
||||
<button id="toggleFilmstripButton">
|
||||
<i class="icon-menu-${isVisible ? 'down' : 'up'}">
|
||||
</i>
|
||||
</button>
|
||||
`;
|
||||
|
||||
return container;
|
||||
},
|
||||
@@ -62,8 +64,8 @@ const FilmStrip = {
|
||||
// Firing the event instead of executing toggleFilmstrip method because
|
||||
// it's important to hide the filmstrip by UI.toggleFilmstrip in order
|
||||
// to correctly resize the video area.
|
||||
$('#hideVideoToolbar').on('click',
|
||||
() => this.eventEmitter.emit(UIEvents.TOGGLE_FILM_STRIP));
|
||||
$('#toggleFilmstripButton').on('click',
|
||||
() => this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP));
|
||||
|
||||
this._registerToggleFilmstripShortcut();
|
||||
},
|
||||
@@ -80,7 +82,7 @@ const FilmStrip = {
|
||||
// Firing the event instead of executing toggleFilmstrip method because
|
||||
// it's important to hide the filmstrip by UI.toggleFilmstrip in order
|
||||
// to correctly resize the video area.
|
||||
let handler = () => this.eventEmitter.emit(UIEvents.TOGGLE_FILM_STRIP);
|
||||
let handler = () => this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP);
|
||||
|
||||
APP.keyboardshortcut.registerShortcut(
|
||||
shortcut,
|
||||
@@ -94,7 +96,7 @@ const FilmStrip = {
|
||||
* Changes classes of icon for showing down state
|
||||
*/
|
||||
showMenuDownIcon() {
|
||||
let icon = this.toggleFilmStripIcon;
|
||||
let icon = this.toggleFilmstripIcon;
|
||||
if(icon) {
|
||||
icon.classList.add(this.iconMenuDownClassName);
|
||||
icon.classList.remove(this.iconMenuUpClassName);
|
||||
@@ -105,7 +107,7 @@ const FilmStrip = {
|
||||
* Changes classes of icon for showing up state
|
||||
*/
|
||||
showMenuUpIcon() {
|
||||
let icon = this.toggleFilmStripIcon;
|
||||
let icon = this.toggleFilmstripIcon;
|
||||
if(icon) {
|
||||
icon.classList.add(this.iconMenuUpClassName);
|
||||
icon.classList.remove(this.iconMenuDownClassName);
|
||||
@@ -113,10 +115,10 @@ const FilmStrip = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles the visibility of the film strip.
|
||||
* Toggles the visibility of the filmstrip.
|
||||
*
|
||||
* @param visible optional {Boolean} which specifies the desired visibility
|
||||
* of the film strip. If not specified, the visibility will be flipped
|
||||
* of the filmstrip. If not specified, the visibility will be flipped
|
||||
* (i.e. toggled); otherwise, the visibility will be set to the specified
|
||||
* value.
|
||||
* @param {Boolean} sendAnalytics - True to send an analytics event. The
|
||||
@@ -127,17 +129,17 @@ const FilmStrip = {
|
||||
* It's important to hide the filmstrip with UI.toggleFilmstrip in order
|
||||
* to correctly resize the video area.
|
||||
*/
|
||||
toggleFilmStrip(visible, sendAnalytics = true) {
|
||||
toggleFilmstrip(visible, sendAnalytics = true) {
|
||||
const isVisibleDefined = typeof visible === 'boolean';
|
||||
if (!isVisibleDefined) {
|
||||
visible = this.isFilmStripVisible();
|
||||
} else if (this.isFilmStripVisible() === visible) {
|
||||
visible = this.isFilmstripVisible();
|
||||
} else if (this.isFilmstripVisible() === visible) {
|
||||
return;
|
||||
}
|
||||
if (sendAnalytics) {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.filmstrip.toggled');
|
||||
}
|
||||
this.filmStrip.toggleClass("hidden");
|
||||
this.filmstrip.toggleClass("hidden");
|
||||
|
||||
if (visible) {
|
||||
this.showMenuUpIcon();
|
||||
@@ -145,12 +147,12 @@ const FilmStrip = {
|
||||
this.showMenuDownIcon();
|
||||
}
|
||||
|
||||
// Emit/fire UIEvents.TOGGLED_FILM_STRIP.
|
||||
// Emit/fire UIEvents.TOGGLED_FILMSTRIP.
|
||||
const eventEmitter = this.eventEmitter;
|
||||
if (eventEmitter) {
|
||||
eventEmitter.emit(
|
||||
UIEvents.TOGGLED_FILM_STRIP,
|
||||
this.isFilmStripVisible());
|
||||
UIEvents.TOGGLED_FILMSTRIP,
|
||||
this.isFilmstripVisible());
|
||||
}
|
||||
},
|
||||
|
||||
@@ -158,24 +160,24 @@ const FilmStrip = {
|
||||
* Shows if filmstrip is visible
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isFilmStripVisible() {
|
||||
return !this.filmStrip.hasClass('hidden');
|
||||
isFilmstripVisible() {
|
||||
return !this.filmstrip.hasClass('hidden');
|
||||
},
|
||||
|
||||
setupFilmStripOnly() {
|
||||
this.filmStrip.css({
|
||||
padding: "0px 0px 18px 0px",
|
||||
right: 0
|
||||
});
|
||||
/**
|
||||
* Adjusts styles for filmstrip-only mode.
|
||||
*/
|
||||
setFilmstripOnly() {
|
||||
this.filmstrip.addClass('filmstrip__videos-filmstripOnly');
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the height of filmstrip
|
||||
* @returns {number} height
|
||||
*/
|
||||
getFilmStripHeight() {
|
||||
if (this.isFilmStripVisible()) {
|
||||
return $(`.${this.filmStripContainerClassName}`).outerHeight();
|
||||
getFilmstripHeight() {
|
||||
if (this.isFilmstripVisible()) {
|
||||
return $(`.${this.filmstripContainerClassName}`).outerHeight();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
@@ -185,10 +187,10 @@ const FilmStrip = {
|
||||
* Returns the width of filmstip
|
||||
* @returns {number} width
|
||||
*/
|
||||
getFilmStripWidth() {
|
||||
return this.filmStrip.innerWidth()
|
||||
- parseInt(this.filmStrip.css('paddingLeft'), 10)
|
||||
- parseInt(this.filmStrip.css('paddingRight'), 10);
|
||||
getFilmstripWidth() {
|
||||
return this.filmstrip.innerWidth()
|
||||
- parseInt(this.filmstrip.css('paddingLeft'), 10)
|
||||
- parseInt(this.filmstrip.css('paddingRight'), 10);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -218,17 +220,17 @@ const FilmStrip = {
|
||||
|
||||
/**
|
||||
* If the videoAreaAvailableWidth is set we use this one to calculate
|
||||
* the filmStrip width, because we're probably in a state where the
|
||||
* film strip size hasn't been updated yet, but it will be.
|
||||
* the filmstrip width, because we're probably in a state where the
|
||||
* filmstrip size hasn't been updated yet, but it will be.
|
||||
*/
|
||||
let videoAreaAvailableWidth
|
||||
= UIUtil.getAvailableVideoWidth()
|
||||
- this._getFilmstripExtraPanelsWidth()
|
||||
- UIUtil.parseCssInt(this.filmStrip.css('right'), 10)
|
||||
- UIUtil.parseCssInt(this.filmStrip.css('paddingLeft'), 10)
|
||||
- UIUtil.parseCssInt(this.filmStrip.css('paddingRight'), 10)
|
||||
- UIUtil.parseCssInt(this.filmStrip.css('borderLeftWidth'), 10)
|
||||
- UIUtil.parseCssInt(this.filmStrip.css('borderRightWidth'), 10)
|
||||
- UIUtil.parseCssInt(this.filmstrip.css('right'), 10)
|
||||
- UIUtil.parseCssInt(this.filmstrip.css('paddingLeft'), 10)
|
||||
- UIUtil.parseCssInt(this.filmstrip.css('paddingRight'), 10)
|
||||
- UIUtil.parseCssInt(this.filmstrip.css('borderLeftWidth'), 10)
|
||||
- UIUtil.parseCssInt(this.filmstrip.css('borderRightWidth'), 10)
|
||||
- 5;
|
||||
|
||||
let availableWidth = videoAreaAvailableWidth;
|
||||
@@ -295,7 +297,7 @@ const FilmStrip = {
|
||||
* @private
|
||||
*/
|
||||
_getFilmstripExtraPanelsWidth() {
|
||||
let className = this.filmStripContainerClassName;
|
||||
let className = this.filmstripContainerClassName;
|
||||
let width = 0;
|
||||
$(`.${className}`)
|
||||
.children()
|
||||
@@ -403,7 +405,7 @@ const FilmStrip = {
|
||||
}));
|
||||
}
|
||||
promises.push(new Promise((resolve) => {
|
||||
this.filmStrip.animate({
|
||||
this.filmstrip.animate({
|
||||
// adds 2 px because of small video 1px border
|
||||
height: remote.thumbHeight + 2
|
||||
}, this._getAnimateOptions(animate, resolve));
|
||||
@@ -413,7 +415,7 @@ const FilmStrip = {
|
||||
let { localThumb } = this.getThumbs();
|
||||
let height = localThumb.height();
|
||||
let fontSize = UIUtil.getIndicatorFontSize(height);
|
||||
this.filmStrip.find('.indicator').animate({
|
||||
this.filmstrip.find('.indicator').animate({
|
||||
fontSize
|
||||
}, this._getAnimateOptions(animate, resolve));
|
||||
}));
|
||||
@@ -453,7 +455,7 @@ const FilmStrip = {
|
||||
}
|
||||
|
||||
let localThumb = $("#localVideoContainer");
|
||||
let remoteThumbs = this.filmStrip.children(selector)
|
||||
let remoteThumbs = this.filmstrip.children(selector)
|
||||
.not("#localVideoContainer");
|
||||
|
||||
// Exclude the local video container if it has been hidden.
|
||||
@@ -465,4 +467,4 @@ const FilmStrip = {
|
||||
}
|
||||
};
|
||||
|
||||
export default FilmStrip;
|
||||
export default Filmstrip;
|
||||
@@ -1,4 +1,4 @@
|
||||
/* global $, APP */
|
||||
/* global $, APP, JitsiMeetJS */
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
import Avatar from "../avatar/Avatar";
|
||||
@@ -9,6 +9,9 @@ import {VideoContainer, VIDEO_CONTAINER_TYPE} from "./VideoContainer";
|
||||
|
||||
import AudioLevels from "../audio_levels/AudioLevels";
|
||||
|
||||
const ParticipantConnectionStatus
|
||||
= JitsiMeetJS.constants.participantConnectionStatus;
|
||||
|
||||
/**
|
||||
* Manager for all Large containers.
|
||||
*/
|
||||
@@ -114,7 +117,7 @@ export default class LargeVideoManager {
|
||||
// (camera or desktop) is a completely different thing than
|
||||
// the video container type (Etherpad, SharedVideo, VideoContainer).
|
||||
// ----------------------------------------------------------------
|
||||
// If we the continer is VIDEO_CONTAINER_TYPE, we need to check
|
||||
// If the container is VIDEO_CONTAINER_TYPE, we need to check
|
||||
// its stream whether exist and is muted to set isVideoMuted
|
||||
// in rest of the cases it is false
|
||||
let showAvatar
|
||||
@@ -125,9 +128,10 @@ export default class LargeVideoManager {
|
||||
// displayed in case we have no video image cached. That is if
|
||||
// there was a user switch(image is lost on stream detach) or if
|
||||
// the video was not rendered, before the connection has failed.
|
||||
const isHavingConnectivityIssues
|
||||
= APP.conference.isParticipantConnectionActive(id) === false;
|
||||
if (isHavingConnectivityIssues
|
||||
const isConnectionActive = this._isConnectionActive(id);
|
||||
|
||||
if (videoType === VIDEO_CONTAINER_TYPE
|
||||
&& !isConnectionActive
|
||||
&& (isUserSwitch || !container.wasVideoRendered)) {
|
||||
showAvatar = true;
|
||||
}
|
||||
@@ -155,10 +159,22 @@ export default class LargeVideoManager {
|
||||
|
||||
// Make sure no notification about remote failure is shown as
|
||||
// its UI conflicts with the one for local connection interrupted.
|
||||
const isConnected = APP.conference.isConnectionInterrupted()
|
||||
|| isConnectionActive;
|
||||
|
||||
// when isHavingConnectivityIssues, state can be inactive,
|
||||
// interrupted or restoring. We show different message for
|
||||
// interrupted and the rest.
|
||||
const isConnectionInterrupted =
|
||||
APP.conference.getParticipantConnectionStatus(id)
|
||||
=== ParticipantConnectionStatus.INTERRUPTED;
|
||||
|
||||
this.updateParticipantConnStatusIndication(
|
||||
id,
|
||||
APP.conference.isConnectionInterrupted()
|
||||
|| !isHavingConnectivityIssues);
|
||||
isConnected,
|
||||
(isConnectionInterrupted)
|
||||
? "connection.USER_CONNECTION_INTERRUPTED"
|
||||
: "connection.LOW_BANDWIDTH");
|
||||
|
||||
// resolve updateLargeVideo promise after everything is done
|
||||
promise.then(resolve);
|
||||
@@ -173,6 +189,22 @@ export default class LargeVideoManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a participant's peer connection status is active.
|
||||
* There is no JitsiParticipant for local id we skip checking local
|
||||
* participant and report it as having no connectivity issues.
|
||||
*
|
||||
* @param {string} id the id of participant(MUC nickname)
|
||||
* @returns {boolean} <tt>true</tt> when participant connection status is
|
||||
* {@link ParticipantConnectionStatus.ACTIVE} and <tt>false</tt> otherwise.
|
||||
* @private
|
||||
*/
|
||||
_isConnectionActive(id) {
|
||||
return APP.conference.isLocalId(id)
|
||||
|| APP.conference.getParticipantConnectionStatus(this.id)
|
||||
=== ParticipantConnectionStatus.ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides notification about participant's connectivity issues to be
|
||||
* shown on the large video area.
|
||||
@@ -180,10 +212,11 @@ export default class LargeVideoManager {
|
||||
* @param {string} id the id of remote participant(MUC nickname)
|
||||
* @param {boolean} isConnected true if the connection is active or false
|
||||
* when the user is having connectivity issues.
|
||||
* @param {string} messageKey the i18n key of the message
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateParticipantConnStatusIndication (id, isConnected) {
|
||||
updateParticipantConnStatusIndication (id, isConnected, messageKey) {
|
||||
|
||||
// Apply grey filter on the large video
|
||||
this.videoContainer.showRemoteConnectionProblemIndicator(!isConnected);
|
||||
@@ -196,7 +229,7 @@ export default class LargeVideoManager {
|
||||
let displayName
|
||||
= APP.conference.getParticipantDisplayName(id);
|
||||
this._setRemoteConnectionMessage(
|
||||
"connection.USER_CONNECTION_INTERRUPTED",
|
||||
messageKey,
|
||||
{ displayName: displayName });
|
||||
|
||||
// Show it now only if the VideoContainer is on top
|
||||
@@ -332,7 +365,7 @@ export default class LargeVideoManager {
|
||||
*/
|
||||
showRemoteConnectionMessage (show) {
|
||||
if (typeof show !== 'boolean') {
|
||||
show = APP.conference.isParticipantConnectionActive(this.id);
|
||||
show = !this._isConnectionActive(this.id);
|
||||
}
|
||||
|
||||
if (show) {
|
||||
@@ -458,7 +491,7 @@ export default class LargeVideoManager {
|
||||
// "avatar" and "video connection" can not be displayed both
|
||||
// at the same time, but the latter is of higher priority and it
|
||||
// will hide the avatar one if will be displayed.
|
||||
this.showRemoteConnectionMessage(/* fet the current state */);
|
||||
this.showRemoteConnectionMessage(/* fetch the current state */);
|
||||
this.showLocalConnectionMessage(/* fetch the current state */);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* global $, APP, interfaceConfig */
|
||||
/* global $, APP, interfaceConfig, JitsiMeetJS */
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
import ConnectionIndicator from './ConnectionIndicator';
|
||||
@@ -12,6 +12,8 @@ const MUTED_DIALOG_BUTTON_VALUES = {
|
||||
cancel: 0,
|
||||
muted: 1
|
||||
};
|
||||
const ParticipantConnectionStatus
|
||||
= JitsiMeetJS.constants.participantConnectionStatus;
|
||||
|
||||
/**
|
||||
* Creates new instance of the <tt>RemoteVideo</tt>.
|
||||
@@ -447,7 +449,7 @@ RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted, force) {
|
||||
RemoteVideo.prototype.setMutedView = function(isMuted) {
|
||||
SmallVideo.prototype.setMutedView.call(this, isMuted);
|
||||
// Update 'mutedWhileDisconnected' flag
|
||||
this._figureOutMutedWhileDisconnected(this.isConnectionActive() === false);
|
||||
this._figureOutMutedWhileDisconnected(this.isConnectionInterrupted());
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -460,12 +462,12 @@ RemoteVideo.prototype.setMutedView = function(isMuted) {
|
||||
* @private
|
||||
*/
|
||||
RemoteVideo.prototype._figureOutMutedWhileDisconnected
|
||||
= function(isDisconnected) {
|
||||
if (isDisconnected && this.isVideoMuted) {
|
||||
this.mutedWhileDisconnected = true;
|
||||
} else if (!isDisconnected && !this.isVideoMuted) {
|
||||
this.mutedWhileDisconnected = false;
|
||||
}
|
||||
= function(isDisconnected) {
|
||||
if (isDisconnected && this.isVideoMuted) {
|
||||
this.mutedWhileDisconnected = true;
|
||||
} else if (!isDisconnected && !this.isVideoMuted) {
|
||||
this.mutedWhileDisconnected = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -534,7 +536,8 @@ RemoteVideo.prototype.removeRemoteStreamElement = function (stream) {
|
||||
* <tt>false</tt> otherwise.
|
||||
*/
|
||||
RemoteVideo.prototype.isConnectionActive = function() {
|
||||
return this.user.isConnectionActive();
|
||||
return this.user.getConnectionStatus()
|
||||
=== ParticipantConnectionStatus.ACTIVE;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -554,8 +557,7 @@ RemoteVideo.prototype.isVideoPlayable = function () {
|
||||
*/
|
||||
RemoteVideo.prototype.updateView = function () {
|
||||
|
||||
this.updateConnectionStatusIndicator(
|
||||
null /* will obtain the status from 'conference' */);
|
||||
this.updateConnectionStatusIndicator();
|
||||
|
||||
// This must be called after 'updateConnectionStatusIndicator' because it
|
||||
// affects the display mode by modifying 'mutedWhileDisconnected' flag
|
||||
@@ -564,19 +566,13 @@ RemoteVideo.prototype.updateView = function () {
|
||||
|
||||
/**
|
||||
* Updates the UI to reflect user's connectivity status.
|
||||
* @param isActive {boolean|null} 'true' if user's connection is active or
|
||||
* 'false' when the use is having some connectivity issues and a warning
|
||||
* should be displayed. When 'null' is passed then the current value will be
|
||||
* obtained from the conference instance.
|
||||
*/
|
||||
RemoteVideo.prototype.updateConnectionStatusIndicator = function (isActive) {
|
||||
// Check for initial value if 'isActive' is not defined
|
||||
if (typeof isActive !== "boolean") {
|
||||
isActive = this.isConnectionActive();
|
||||
if (isActive === null) {
|
||||
// Cancel processing at this point - no update
|
||||
return;
|
||||
}
|
||||
RemoteVideo.prototype.updateConnectionStatusIndicator = function () {
|
||||
const isActive = this.isConnectionActive();
|
||||
|
||||
if (isActive === null) {
|
||||
// Cancel processing at this point - no update
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(this.id + " thumbnail is connection active ? " + isActive);
|
||||
@@ -700,50 +696,22 @@ RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show/hide peer container for the given id.
|
||||
*/
|
||||
RemoteVideo.prototype.showPeerContainer = function (state) {
|
||||
if (!this.container)
|
||||
return;
|
||||
|
||||
var isHide = state === 'hide';
|
||||
var resizeThumbnails = false;
|
||||
|
||||
if (!isHide) {
|
||||
if (!$(this.container).is(':visible')) {
|
||||
resizeThumbnails = true;
|
||||
$(this.container).show();
|
||||
}
|
||||
// Call updateView, so that we'll figure out if avatar
|
||||
// should be displayed based on video muted status and whether or not
|
||||
// it's in the lastN set
|
||||
this.updateView();
|
||||
}
|
||||
else if ($(this.container).is(':visible') && isHide)
|
||||
{
|
||||
resizeThumbnails = true;
|
||||
$(this.container).hide();
|
||||
if(this.connectionIndicator)
|
||||
this.connectionIndicator.hide();
|
||||
}
|
||||
|
||||
if (resizeThumbnails) {
|
||||
this.VideoLayout.resizeThumbnails();
|
||||
}
|
||||
|
||||
// We want to be able to pin a participant from the contact list, even
|
||||
// if he's not in the lastN set!
|
||||
// ContactList.setClickable(id, !isHide);
|
||||
|
||||
};
|
||||
|
||||
RemoteVideo.prototype.updateResolution = function (resolution) {
|
||||
if (this.connectionIndicator) {
|
||||
this.connectionIndicator.updateResolution(resolution);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates this video framerate indication.
|
||||
* @param framerate the value to update
|
||||
*/
|
||||
RemoteVideo.prototype.updateFramerate = function (framerate) {
|
||||
if (this.connectionIndicator) {
|
||||
this.connectionIndicator.updateFramerate(framerate);
|
||||
}
|
||||
};
|
||||
|
||||
RemoteVideo.prototype.removeConnectionIndicator = function () {
|
||||
if (this.connectionIndicator)
|
||||
this.connectionIndicator.remove();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* global $, JitsiMeetJS, interfaceConfig */
|
||||
/* global $, APP, JitsiMeetJS, interfaceConfig */
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
import Avatar from "../avatar/Avatar";
|
||||
@@ -446,7 +446,7 @@ SmallVideo.prototype.isCurrentlyOnLargeVideo = function () {
|
||||
SmallVideo.prototype.isVideoPlayable = function() {
|
||||
return this.videoStream // Is there anything to display ?
|
||||
&& !this.isVideoMuted && !this.videoStream.isMuted() // Muted ?
|
||||
&& (this.isLocal || this.VideoLayout.isInLastN(this.id));
|
||||
&& (this.isLocal || APP.conference.isInLastN(this.id));
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* global $, APP, interfaceConfig */
|
||||
/* jshint -W101 */
|
||||
|
||||
import FilmStrip from './FilmStrip';
|
||||
import Filmstrip from './Filmstrip';
|
||||
import LargeContainer from './LargeContainer';
|
||||
import UIEvents from "../../../service/UI/UIEvents";
|
||||
import UIUtil from "../util/UIUtil";
|
||||
@@ -48,7 +48,7 @@ function getDesktopVideoSize(videoWidth,
|
||||
let availableWidth = Math.max(videoWidth, videoSpaceWidth);
|
||||
let availableHeight = Math.max(videoHeight, videoSpaceHeight);
|
||||
|
||||
videoSpaceHeight -= FilmStrip.getFilmStripHeight();
|
||||
videoSpaceHeight -= Filmstrip.getFilmstripHeight();
|
||||
|
||||
if (availableWidth / aspectRatio >= videoSpaceHeight) {
|
||||
availableHeight = videoSpaceHeight;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user