mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-06-01 19:47:48 +00:00
Compare commits
1 Commits
1604
...
move_loggi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf83b4142a |
@@ -12,12 +12,6 @@ Installing Jitsi Meet is quite a simple experience. For Debian-based systems, we
|
||||
|
||||
For other systems, or if you wish to install all components manually, see the [detailed manual installation instructions](https://github.com/jitsi/jitsi-meet/blob/master/doc/manual-install.md).
|
||||
|
||||
## Download
|
||||
|
||||
You can download Debian/Ubuntu binaries:
|
||||
* [stable](https://download.jitsi.org/stable/) ([instructions](https://jitsi.org/Main/InstallJitsiMeetDebianStableRepository))
|
||||
* [nightly](https://download.jitsi.org/unstable/) ([instructions](https://jitsi.org/Main/InstallJitsiMeetDebianNightlyRepository))
|
||||
|
||||
## 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).
|
||||
|
||||
@@ -46,13 +46,13 @@ android_library(
|
||||
|
||||
android_build_config(
|
||||
name = 'build_config',
|
||||
package = 'org.jitsi.meet',
|
||||
package = 'org.jitsi.jitsi_meet_react',
|
||||
)
|
||||
|
||||
android_resource(
|
||||
name = 'res',
|
||||
res = 'src/main/res',
|
||||
package = 'org.jitsi.meet',
|
||||
package = 'org.jitsi.jitsi_meet_react',
|
||||
)
|
||||
|
||||
android_binary(
|
||||
|
||||
@@ -98,11 +98,11 @@ android {
|
||||
buildToolsVersion '23.0.1'
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'org.jitsi.meet'
|
||||
applicationId 'org.jitsi.jitsi_meet_react'
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
versionCode Integer.parseInt("${version}")
|
||||
versionName "1.1.${version}"
|
||||
versionCode 1
|
||||
versionName '1.0'
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'x86'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.jitsi.meet"
|
||||
package="org.jitsi.jitsi_meet_react"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,8 +1,11 @@
|
||||
package org.jitsi.meet;
|
||||
package org.jitsi.jitsi_meet_react;
|
||||
|
||||
import android.os.Bundle;
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
import com.facebook.react.ReactActivity;
|
||||
import com.facebook.react.ReactActivityDelegate;
|
||||
import com.facebook.react.ReactRootView;
|
||||
import io.fabric.sdk.android.Fabric;
|
||||
|
||||
public class MainActivity extends ReactActivity {
|
||||
/**
|
||||
@@ -43,4 +46,11 @@ public class MainActivity extends ReactActivity {
|
||||
protected String getMainComponentName() {
|
||||
return "App";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Fabric.with(this, new Crashlytics());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.jitsi.jitsi_meet_react;
|
||||
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
import com.oblador.vectoricons.VectorIconsPackage;
|
||||
import com.oney.WebRTCModule.WebRTCModulePackage;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
|
||||
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
||||
@Override
|
||||
protected boolean getUseDeveloperSupport() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage(),
|
||||
new VectorIconsPackage(),
|
||||
new WebRTCModulePackage()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public ReactNativeHost getReactNativeHost() {
|
||||
return mReactNativeHost;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package org.jitsi.meet;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import com.crashlytics.android.Crashlytics;
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
|
||||
import io.fabric.sdk.android.Fabric;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected boolean getUseDeveloperSupport() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new com.facebook.react.shell.MainReactPackage(),
|
||||
new com.oblador.vectoricons.VectorIconsPackage(),
|
||||
new com.oney.WebRTCModule.WebRTCModulePackage()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public ReactNativeHost getReactNativeHost() {
|
||||
return mReactNativeHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
if (!getReactNativeHost()
|
||||
.getReactInstanceManager()
|
||||
.getDevSupportManager()
|
||||
.getDevSupportEnabled()) {
|
||||
Fabric.with(this, new Crashlytics());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,4 +18,3 @@
|
||||
# org.gradle.parallel=true
|
||||
|
||||
android.useDeprecatedNdk=true
|
||||
version=1
|
||||
|
||||
257
app.js
257
app.js
@@ -1,4 +1,6 @@
|
||||
/* global $, config, getRoomName, loggingConfig, JitsiMeetJS */
|
||||
/* application specific logic */
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
import "babel-polyfill";
|
||||
import "jquery";
|
||||
@@ -6,6 +8,7 @@ import "jquery-contextmenu";
|
||||
import "jquery-ui";
|
||||
import "strophe";
|
||||
import "strophe-disco";
|
||||
import "strophe-caps";
|
||||
import "jQuery-Impromptu";
|
||||
import "autosize";
|
||||
|
||||
@@ -16,13 +19,102 @@ import 'aui-experimental-css';
|
||||
|
||||
window.toastr = require("toastr");
|
||||
|
||||
const Logger = require("jitsi-meet-logger");
|
||||
const LogCollector = Logger.LogCollector;
|
||||
import JitsiMeetLogStorage from "./modules/util/JitsiMeetLogStorage";
|
||||
|
||||
import URLProcessor from "./modules/config/URLProcessor";
|
||||
import RoomnameGenerator from './modules/util/RoomnameGenerator';
|
||||
|
||||
import UI from "./modules/UI/UI";
|
||||
import settings from "./modules/settings/Settings";
|
||||
import conference from './conference';
|
||||
import ConferenceUrl from './modules/URL/ConferenceUrl';
|
||||
import API from './modules/API/API';
|
||||
|
||||
import UIEvents from './service/UI/UIEvents';
|
||||
import getTokenData from "./modules/tokendata/TokenData";
|
||||
import translation from "./modules/translation/translation";
|
||||
|
||||
const ConferenceEvents = JitsiMeetJS.events.conference;
|
||||
|
||||
/**
|
||||
* Tries to push history state with the following parameters:
|
||||
* 'VideoChat', `Room: ${roomName}`, URL. If fail, prints the error and returns
|
||||
* it.
|
||||
*/
|
||||
function pushHistoryState(roomName, URL) {
|
||||
try {
|
||||
window.history.pushState(
|
||||
'VideoChat', `Room: ${roomName}`, URL
|
||||
);
|
||||
} catch (e) {
|
||||
logger.warn("Push history state failed with parameters:",
|
||||
'VideoChat', `Room: ${roomName}`, URL, e);
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces current history state(replaces the URL displayed by the browser).
|
||||
* @param {string} newUrl the URL string which is to be displayed by the browser
|
||||
* to the user.
|
||||
*/
|
||||
function replaceHistoryState (newUrl) {
|
||||
if (window.history
|
||||
&& typeof window.history.replaceState === 'function') {
|
||||
window.history.replaceState({}, document.title, newUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns the room name.
|
||||
*/
|
||||
function buildRoomName () {
|
||||
let roomName = getRoomName();
|
||||
|
||||
if(!roomName) {
|
||||
let word = RoomnameGenerator.generateRoomWithoutSeparator();
|
||||
roomName = word.toLowerCase();
|
||||
let historyURL = window.location.href + word;
|
||||
//Trying to push state with current URL + roomName
|
||||
pushHistoryState(word, historyURL);
|
||||
}
|
||||
|
||||
return roomName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the logging levels.
|
||||
* @private
|
||||
*/
|
||||
function configureLoggingLevels () {
|
||||
// NOTE The library Logger is separated from the app loggers, so the levels
|
||||
// have to be set in two places
|
||||
|
||||
// Set default logging level
|
||||
const defaultLogLevel
|
||||
= loggingConfig.defaultLogLevel || JitsiMeetJS.logLevels.TRACE;
|
||||
Logger.setLogLevel(defaultLogLevel);
|
||||
JitsiMeetJS.setLogLevel(defaultLogLevel);
|
||||
|
||||
// NOTE console was used on purpose here to go around the logging
|
||||
// and always print the default logging level to the console
|
||||
console.info("Default logging level set to: " + defaultLogLevel);
|
||||
|
||||
// Set log level for each logger
|
||||
if (loggingConfig) {
|
||||
Object.keys(loggingConfig).forEach(function(loggerName) {
|
||||
if ('defaultLogLevel' !== loggerName) {
|
||||
const level = loggingConfig[loggerName];
|
||||
Logger.setLogLevelById(level, loggerName);
|
||||
JitsiMeetJS.setLogLevelById(level, loggerName);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const APP = {
|
||||
// Used by do_external_connect.js if we receive the attach data after
|
||||
// connect was already executed. status property can be "initialized",
|
||||
@@ -59,15 +151,164 @@ const APP = {
|
||||
*/
|
||||
ConferenceUrl : null,
|
||||
connection: null,
|
||||
API
|
||||
API,
|
||||
init () {
|
||||
this.initLogging();
|
||||
this.keyboardshortcut =
|
||||
require("./modules/keyboardshortcut/keyboardshortcut");
|
||||
this.configFetch = require("./modules/config/HttpConfigFetch");
|
||||
this.tokenData = getTokenData();
|
||||
},
|
||||
initLogging () {
|
||||
// Adjust logging level
|
||||
configureLoggingLevels();
|
||||
// Create the LogCollector and register it as the global log transport.
|
||||
// It is done early to capture as much logs as possible. Captured logs
|
||||
// will be cached, before the JitsiMeetLogStorage gets ready (statistics
|
||||
// module is initialized).
|
||||
if (!this.logCollector && !loggingConfig.disableLogCollector) {
|
||||
this.logCollector = new LogCollector(new JitsiMeetLogStorage());
|
||||
Logger.addGlobalTransport(this.logCollector);
|
||||
JitsiMeetJS.addGlobalLogTransport(this.logCollector);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO The execution of the mobile app starts from react/index.native.js.
|
||||
// Similarly, the execution of the Web app should start from react/index.web.js
|
||||
// for the sake of consistency and ease of understanding. Temporarily though
|
||||
// because we are at the beginning of introducing React into the Web app, allow
|
||||
// the execution of the Web app to start from app.js in order to reduce the
|
||||
// complexity of the beginning step.
|
||||
require('./react');
|
||||
/**
|
||||
* If JWT token data it will be used for local user settings
|
||||
*/
|
||||
function setTokenData() {
|
||||
let localUser = APP.tokenData.caller;
|
||||
if(localUser) {
|
||||
APP.settings.setEmail((localUser.getEmail() || "").trim(), true);
|
||||
APP.settings.setAvatarUrl((localUser.getAvatarUrl() || "").trim());
|
||||
APP.settings.setDisplayName((localUser.getName() || "").trim(), true);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
setTokenData();
|
||||
// Initialize the conference URL handler
|
||||
APP.ConferenceUrl = new ConferenceUrl(window.location);
|
||||
// Clean up the URL displayed by the browser
|
||||
replaceHistoryState(APP.ConferenceUrl.getInviteUrl());
|
||||
|
||||
// TODO The execution of the mobile app starts from react/index.native.js.
|
||||
// Similarly, the execution of the Web app should start from
|
||||
// react/index.web.js for the sake of consistency and ease of understanding.
|
||||
// Temporarily though because we are at the beginning of introducing React
|
||||
// into the Web app, allow the execution of the Web app to start from app.js
|
||||
// in order to reduce the complexity of the beginning step.
|
||||
require('./react');
|
||||
|
||||
const isUIReady = APP.UI.start();
|
||||
if (isUIReady) {
|
||||
APP.conference.init({roomName: buildRoomName()}).then(() => {
|
||||
|
||||
if (APP.logCollector) {
|
||||
// Start the LogCollector's periodic "store logs" task only if
|
||||
// we're in the conference and not on the welcome page. This is
|
||||
// determined by the value of "isUIReady" const above.
|
||||
APP.logCollector.start();
|
||||
APP.logCollectorStarted = true;
|
||||
// Make an attempt to flush in case a lot of logs have been
|
||||
// cached, before the collector was started.
|
||||
APP.logCollector.flush();
|
||||
|
||||
// This event listener will flush the logs, before
|
||||
// the statistics module (CallStats) is stopped.
|
||||
//
|
||||
// NOTE The LogCollector is not stopped, because this event can
|
||||
// be triggered multiple times during single conference
|
||||
// (whenever statistics module is stopped). That includes
|
||||
// the case when Jicofo terminates the single person left in the
|
||||
// room. It will then restart the media session when someone
|
||||
// eventually join the room which will start the stats again.
|
||||
APP.conference.addConferenceListener(
|
||||
ConferenceEvents.BEFORE_STATISTICS_DISPOSED,
|
||||
() => {
|
||||
if (APP.logCollector) {
|
||||
APP.logCollector.flush();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
APP.UI.initConference();
|
||||
|
||||
APP.UI.addListener(UIEvents.LANG_CHANGED, language => {
|
||||
APP.translation.setLanguage(language);
|
||||
APP.settings.setLanguage(language);
|
||||
});
|
||||
|
||||
APP.keyboardshortcut.init();
|
||||
}).catch(err => {
|
||||
APP.UI.hideRingOverLay();
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
logger.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have an HTTP endpoint for getting config.json configured we're going to
|
||||
* read it and override properties from config.js and interfaceConfig.js.
|
||||
* If there is no endpoint we'll just continue with initialization.
|
||||
* Keep in mind that if the endpoint has been configured and we fail to obtain
|
||||
* the config for any reason then the conference won't start and error message
|
||||
* will be displayed to the user.
|
||||
*/
|
||||
function obtainConfigAndInit() {
|
||||
let roomName = APP.conference.roomName;
|
||||
|
||||
if (config.configLocation) {
|
||||
APP.configFetch.obtainConfig(
|
||||
config.configLocation, roomName,
|
||||
// Get config result callback
|
||||
function(success, error) {
|
||||
if (success) {
|
||||
var now = APP.connectionTimes["configuration.fetched"] =
|
||||
window.performance.now();
|
||||
logger.log("(TIME) configuration fetched:\t", now);
|
||||
init();
|
||||
} else {
|
||||
// Show obtain config error,
|
||||
// pass the error object for report
|
||||
APP.UI.messageHandler.openReportDialog(
|
||||
null, "dialog.connectError", error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
require("./modules/config/BoshAddressChoice").chooseAddress(
|
||||
config, roomName);
|
||||
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
var now = APP.connectionTimes["document.ready"] = window.performance.now();
|
||||
logger.log("(TIME) document ready:\t", now);
|
||||
|
||||
URLProcessor.setConfigParametersFromUrl();
|
||||
|
||||
APP.init();
|
||||
|
||||
APP.translation.init(settings.getLanguage());
|
||||
|
||||
APP.API.init(APP.tokenData.externalAPISettings);
|
||||
|
||||
obtainConfigAndInit();
|
||||
});
|
||||
|
||||
$(window).bind('beforeunload', function () {
|
||||
// Stop the LogCollector
|
||||
if (APP.logCollectorStarted) {
|
||||
APP.logCollector.stop();
|
||||
APP.logCollectorStarted = false;
|
||||
}
|
||||
APP.API.dispose();
|
||||
});
|
||||
|
||||
module.exports = APP;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<div class="hint-msg">
|
||||
<p>
|
||||
<span id="hintQuestion">Did you know?</span>
|
||||
<span>Did you know?</span>
|
||||
<span class="hint-msg__holder" id="hintMessage"></span>
|
||||
</p>
|
||||
<div class="happy-software"></div>
|
||||
|
||||
13
close.js
13
close.js
@@ -32,7 +32,7 @@ function insertTextMsg(id, msg){
|
||||
var el = document.getElementById(id);
|
||||
|
||||
if (el)
|
||||
el.innerHTML = msg;
|
||||
el.innerText = msg;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,17 +42,6 @@ function onLoad() {
|
||||
//Works only for close2.html because close.html doesn't have this element.
|
||||
insertTextMsg('thanksMessage',
|
||||
'Thank you for using ' + interfaceConfig.APP_NAME);
|
||||
|
||||
// If there is a setting show a special message only for the guests
|
||||
if (interfaceConfig.CLOSE_PAGE_GUEST_HINT) {
|
||||
if ( window.sessionStorage.getItem('guest') === 'true' ) {
|
||||
var element = document.getElementById('hintQuestion');
|
||||
element.classList.add('hide');
|
||||
insertTextMsg('hintMessage', interfaceConfig.CLOSE_PAGE_GUEST_HINT);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
insertTextMsg('hintMessage', getHint());
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<div class="hint-msg">
|
||||
<p>
|
||||
<span id="hintQuestion">Did you know?</span>
|
||||
<span>Did you know?</span>
|
||||
<span class="hint-msg__holder" id="hintMessage"></span>
|
||||
</p>
|
||||
<div class="happy-software"></div>
|
||||
|
||||
159
conference.js
159
conference.js
@@ -195,9 +195,6 @@ function muteLocalVideo (muted) {
|
||||
function maybeRedirectToWelcomePage(options) {
|
||||
// if close page is enabled redirect to it, without further action
|
||||
if (config.enableClosePage) {
|
||||
// save whether current user is guest or not, before navigating
|
||||
// to close page
|
||||
window.sessionStorage.setItem('guest', APP.tokenData.isGuest);
|
||||
if (options.feedbackSubmitted)
|
||||
window.location.pathname = "close.html";
|
||||
else
|
||||
@@ -263,6 +260,22 @@ function createLocalTracks (options, checkForPermissionPrompt) {
|
||||
'failed to create local tracks', options.devices, err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the email for the local user
|
||||
* @param email {string} the new email
|
||||
*/
|
||||
function changeLocalEmail(email = '') {
|
||||
email = email.trim();
|
||||
|
||||
if (email === APP.settings.getEmail()) {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.settings.setEmail(email);
|
||||
APP.UI.setUserEmail(room.myUserId(), email);
|
||||
sendData(commands.EMAIL, email);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -490,10 +503,8 @@ export default {
|
||||
this.isDesktopSharingEnabled =
|
||||
JitsiMeetJS.isDesktopSharingEnabled();
|
||||
|
||||
if (UIUtil.isButtonEnabled('contacts')
|
||||
&& !interfaceConfig.filmStripOnly) {
|
||||
if (UIUtil.isButtonEnabled('contacts'))
|
||||
APP.UI.ContactList = new ContactList(room);
|
||||
}
|
||||
|
||||
// if user didn't give access to mic or camera or doesn't have
|
||||
// them at all, we disable corresponding toolbar buttons
|
||||
@@ -1244,57 +1255,6 @@ export default {
|
||||
APP.API.notifyReceivedChatMessage(id, nick, text, ts);
|
||||
APP.UI.addMessage(id, nick, text, ts);
|
||||
});
|
||||
APP.UI.addListener(UIEvents.MESSAGE_CREATED, (message) => {
|
||||
APP.API.notifySendingChatMessage(message);
|
||||
room.sendTextMessage(message);
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, (id) => {
|
||||
try {
|
||||
// do not try to select participant if there is none (we
|
||||
// are alone in the room), otherwise an error will be
|
||||
// thrown cause reporting mechanism is not available
|
||||
// (datachannels currently)
|
||||
if (room.getParticipants().length === 0)
|
||||
return;
|
||||
|
||||
room.selectParticipant(id);
|
||||
} catch (e) {
|
||||
JitsiMeetJS.analytics.sendEvent(
|
||||
'selectParticipant.failed');
|
||||
reportError(e);
|
||||
}
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
|
||||
(smallVideo, isPinned) => {
|
||||
let smallVideoId = smallVideo.getId();
|
||||
let isLocal = APP.conference.isLocalId(smallVideoId);
|
||||
|
||||
let eventName
|
||||
= (isPinned ? "pinned" : "unpinned") + "." +
|
||||
(isLocal ? "local" : "remote");
|
||||
let participantCount = room.getParticipantCount();
|
||||
JitsiMeetJS.analytics.sendEvent(
|
||||
eventName,
|
||||
{ value: participantCount });
|
||||
|
||||
// FIXME why VIDEO_CONTAINER_TYPE instead of checking if
|
||||
// the participant is on the large video ?
|
||||
if (smallVideo.getVideoType() === VIDEO_CONTAINER_TYPE
|
||||
&& !isLocal) {
|
||||
|
||||
// When the library starts supporting multiple pins we
|
||||
// would pass the isPinned parameter together with the
|
||||
// identifier, but currently we send null to indicate that
|
||||
// we unpin the last pinned.
|
||||
try {
|
||||
room.pinParticipant(isPinned ? smallVideoId : null);
|
||||
} catch (e) {
|
||||
reportError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
room.on(ConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
||||
@@ -1372,6 +1332,13 @@ export default {
|
||||
APP.UI.addListener(UIEvents.AUDIO_MUTED, muteLocalAudio);
|
||||
APP.UI.addListener(UIEvents.VIDEO_MUTED, muteLocalVideo);
|
||||
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
APP.UI.addListener(UIEvents.MESSAGE_CREATED, (message) => {
|
||||
APP.API.notifySendingChatMessage(message);
|
||||
room.sendTextMessage(message);
|
||||
});
|
||||
}
|
||||
|
||||
room.on(ConnectionQualityEvents.LOCAL_STATS_UPDATED,
|
||||
(stats) => {
|
||||
APP.UI.updateLocalStats(stats.connectionQuality, stats);
|
||||
@@ -1387,7 +1354,7 @@ export default {
|
||||
APP.UI.initEtherpad(value);
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.EMAIL_CHANGED, this.changeLocalEmail);
|
||||
APP.UI.addListener(UIEvents.EMAIL_CHANGED, changeLocalEmail);
|
||||
room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
|
||||
APP.UI.setUserEmail(from, data.value);
|
||||
});
|
||||
@@ -1494,6 +1461,50 @@ export default {
|
||||
AuthHandler.authenticate(room);
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, (id) => {
|
||||
try {
|
||||
// do not try to select participant if there is none (we are
|
||||
// alone in the room), otherwise an error will be thrown cause
|
||||
// reporting mechanism is not available (datachannels currently)
|
||||
if (room.getParticipants().length === 0)
|
||||
return;
|
||||
|
||||
room.selectParticipant(id);
|
||||
} catch (e) {
|
||||
JitsiMeetJS.analytics.sendEvent('selectParticipant.failed');
|
||||
reportError(e);
|
||||
}
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.PINNED_ENDPOINT, (smallVideo, isPinned) => {
|
||||
let smallVideoId = smallVideo.getId();
|
||||
let isLocal = APP.conference.isLocalId(smallVideoId);
|
||||
|
||||
let eventName
|
||||
= (isPinned ? "pinned" : "unpinned") + "." +
|
||||
(isLocal ? "local" : "remote");
|
||||
let participantCount = room.getParticipantCount();
|
||||
JitsiMeetJS.analytics.sendEvent(
|
||||
eventName,
|
||||
{ value: participantCount });
|
||||
|
||||
// FIXME why VIDEO_CONTAINER_TYPE instead of checking if
|
||||
// the participant is on the large video ?
|
||||
if (smallVideo.getVideoType() === VIDEO_CONTAINER_TYPE
|
||||
&& !isLocal) {
|
||||
|
||||
// When the library starts supporting multiple pins we would
|
||||
// pass the isPinned parameter together with the identifier,
|
||||
// but currently we send null to indicate that we unpin the
|
||||
// last pinned.
|
||||
try {
|
||||
room.pinParticipant(isPinned ? smallVideoId : null);
|
||||
} catch (e) {
|
||||
reportError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
APP.UI.addListener(
|
||||
UIEvents.VIDEO_DEVICE_CHANGED,
|
||||
(cameraDeviceId) => {
|
||||
@@ -1786,37 +1797,5 @@ export default {
|
||||
APP.API.notifyReadyToClose();
|
||||
maybeRedirectToWelcomePage(values[0]);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the email for the local user
|
||||
* @param email {string} the new email
|
||||
*/
|
||||
changeLocalEmail(email = '') {
|
||||
email = email.trim();
|
||||
|
||||
if (email === APP.settings.getEmail()) {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.settings.setEmail(email);
|
||||
APP.UI.setUserEmail(room.myUserId(), email);
|
||||
sendData(commands.EMAIL, email);
|
||||
},
|
||||
|
||||
/**
|
||||
* Changes the avatar url for the local user
|
||||
* @param url {string} the new url
|
||||
*/
|
||||
changeLocalAvatarUrl(url = '') {
|
||||
url = url.trim();
|
||||
|
||||
if (url === APP.settings.getAvatarUrl()) {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.settings.setAvatarUrl(url);
|
||||
APP.UI.setUserAvatarUrl(room.myUserId(), url);
|
||||
sendData(commands.AVATAR_URL, url);
|
||||
}
|
||||
};
|
||||
|
||||
14
config.js
14
config.js
@@ -76,7 +76,15 @@ var config = { // eslint-disable-line no-unused-vars
|
||||
// If true - all users without token will be considered guests and all users
|
||||
// with token will be considered non-guests. Only guests will be allowed to
|
||||
// edit their profile.
|
||||
enableUserRolesBasedOnToken: false,
|
||||
// Suspending video might cause problems with audio playback. Disabling until these are fixed.
|
||||
disableSuspendVideo: true
|
||||
enableUserRolesBasedOnToken: false
|
||||
};
|
||||
|
||||
// Logging configuration
|
||||
var loggingConfig = { // eslint-disable-line no-unused-vars
|
||||
//default log level for the app and lib-jitsi-meet
|
||||
defaultLogLevel: 'trace',
|
||||
// Option to disable LogCollector (which stores the logs on CallStats)
|
||||
//disableLogCollector: true,
|
||||
// Logging level adjustments for verbose modules:
|
||||
'modules/xmpp/strophe.util.js': 'log'
|
||||
};
|
||||
|
||||
@@ -84,6 +84,7 @@ form {
|
||||
}
|
||||
|
||||
.leftwatermark {
|
||||
display: none;
|
||||
left: $defaultToolbarSize;
|
||||
margin-left: 10px;
|
||||
background-image: url($defaultWatermarkLink);
|
||||
@@ -91,11 +92,13 @@ form {
|
||||
}
|
||||
|
||||
.rightwatermark {
|
||||
display: none;
|
||||
right: 15;
|
||||
background-position: center right;
|
||||
}
|
||||
|
||||
.poweredby {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 25;
|
||||
bottom: 7;
|
||||
@@ -139,4 +142,4 @@ form {
|
||||
#inviteLinkRef {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
@@ -50,17 +50,14 @@
|
||||
position:relative;
|
||||
height:196px;
|
||||
padding: 0;
|
||||
/*The filmstrip should not be covered by the left toolbar*/
|
||||
padding-left: $defaultToolbarSize + 5;
|
||||
padding-left: 17px;
|
||||
bottom: 0;
|
||||
width:auto;
|
||||
border: $thumbnailsBorder solid transparent;
|
||||
z-index: 5;
|
||||
transition: bottom 2s;
|
||||
overflow: visible !important;
|
||||
/*!!!Removes the gap between the local video container and the remote
|
||||
videos.*/
|
||||
font-size: 0pt;
|
||||
font-size: 0pt; /*!!!Removes the gap between the local video container and the remote videos.*/
|
||||
|
||||
&.hidden {
|
||||
bottom: -196px;
|
||||
@@ -70,8 +67,8 @@
|
||||
display: none;
|
||||
position: relative;
|
||||
background-size: contain;
|
||||
border: $thumbnailVideoBorder solid $thumbnailBorderColor;
|
||||
border-radius: $borderRadius;
|
||||
border: $thumbnailVideoBorder solid transparent;
|
||||
border-radius:1px;
|
||||
margin: 0 $thumbnailVideoMargin;
|
||||
|
||||
&.videoContainerFocused, &:hover {
|
||||
@@ -115,7 +112,7 @@
|
||||
& > video,
|
||||
& > object {
|
||||
cursor: hand;
|
||||
border-radius: $borderRadius;
|
||||
border-radius:1px;
|
||||
object-fit: cover;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -34,15 +34,15 @@
|
||||
.icon-avatar:before {
|
||||
content: "\e901";
|
||||
}
|
||||
.icon-autorenew:before {
|
||||
content: "\e903";
|
||||
}
|
||||
.icon-hangup:before {
|
||||
content: "\e905";
|
||||
}
|
||||
.icon-chat:before {
|
||||
content: "\e906";
|
||||
}
|
||||
.icon-download:before {
|
||||
content: "\e902";
|
||||
}
|
||||
.icon-edit:before {
|
||||
content: "\e907";
|
||||
}
|
||||
@@ -55,6 +55,9 @@
|
||||
.icon-kick:before {
|
||||
content: "\e904";
|
||||
}
|
||||
.icon-menu:before {
|
||||
content: "\e91f";
|
||||
}
|
||||
.icon-menu-up:before {
|
||||
content: "\e91f";
|
||||
}
|
||||
@@ -67,6 +70,9 @@
|
||||
.icon-exit-full-screen:before {
|
||||
content: "\e90c";
|
||||
}
|
||||
.icon-star:before {
|
||||
content: "\e916";
|
||||
}
|
||||
.icon-star-full:before {
|
||||
content: "\e90a";
|
||||
}
|
||||
@@ -103,12 +109,6 @@
|
||||
.icon-settings:before {
|
||||
content: "\e915";
|
||||
}
|
||||
.icon-star:before {
|
||||
content: "\e916";
|
||||
}
|
||||
.icon-switch-camera:before {
|
||||
content: "\e921";
|
||||
}
|
||||
.icon-share-desktop:before {
|
||||
content: "\e917";
|
||||
}
|
||||
@@ -133,6 +133,7 @@
|
||||
.icon-recEnable:before {
|
||||
content: "\e614";
|
||||
}
|
||||
// FIXME not used anymore - consider removing in the next font update
|
||||
.icon-presentation:before {
|
||||
content: "\e603";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,12 +150,4 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a semi-transparent background with the given color and alpha
|
||||
* (opacity) value.
|
||||
*/
|
||||
@mixin transparentBg($color, $alpha) {
|
||||
background-color: rgba(red($color), green($color), blue($color), $alpha);
|
||||
}
|
||||
@@ -14,9 +14,9 @@ $defaultToolbarSize: 50px;
|
||||
|
||||
// Video layout.
|
||||
$thumbnailToolbarHeight: 22px;
|
||||
$thumbnailIndicatorBorder: 2px;
|
||||
$thumbnailIndicatorSize: $thumbnailToolbarHeight;
|
||||
$thumbnailVideoMargin: 5px;
|
||||
$thumbnailIndicatorBorder: 0;
|
||||
$thumbnailIndicatorSize: 3em;
|
||||
$thumbnailVideoMargin: 2px;
|
||||
$thumbnailsBorder: 2px;
|
||||
$thumbnailVideoBorder: 2px;
|
||||
$hideFilmstripButtonWidth: 17px;
|
||||
@@ -95,7 +95,7 @@ $notificationWidth: 215px;
|
||||
/**
|
||||
* Misc.
|
||||
*/
|
||||
$borderRadius: 4px;
|
||||
$borderRadius: 3px;
|
||||
$defaultWatermarkLink: '../images/watermark.png';
|
||||
$sidebarWidth: 220px;
|
||||
$popoverMenuPadding: 13px;
|
||||
|
||||
@@ -18,10 +18,9 @@
|
||||
|
||||
&__background {
|
||||
@include topLeft();
|
||||
background-color: black;
|
||||
border-radius: $borderRadius - 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,12 +106,11 @@
|
||||
}
|
||||
|
||||
&__hoverOverlay {
|
||||
background: rgba(0,0,0,.6);
|
||||
border-radius: $borderRadius;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
visibility: hidden;
|
||||
background: rgba(0,0,0,.6);
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
@@ -130,8 +128,8 @@
|
||||
|
||||
#localVideoWrapper>video,
|
||||
#localVideoWrapper>object {
|
||||
border-radius: $borderRadius !important;
|
||||
cursor: hand;
|
||||
border-radius:1px !important;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
@@ -213,7 +211,6 @@
|
||||
|
||||
.videocontainer .displayname {
|
||||
pointer-events: none;
|
||||
padding: 0 3px 0 3px;
|
||||
}
|
||||
|
||||
.videocontainer .editdisplayname {
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
float: left;
|
||||
}
|
||||
|
||||
.domain-name
|
||||
#domain_name
|
||||
{
|
||||
float: left;
|
||||
height: 55px;
|
||||
@@ -61,51 +61,37 @@
|
||||
color: $defaultDarkColor;
|
||||
}
|
||||
|
||||
.enter-room {
|
||||
&__field {
|
||||
font-size: 15px;
|
||||
border: none;
|
||||
-webkit-appearance: none;
|
||||
width: 228px;
|
||||
height: 55px;
|
||||
line-height: 55px;
|
||||
font-weight: 500;
|
||||
box-shadow: none;
|
||||
float: left;
|
||||
background-color: #FFFFFF;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
#enter_room_field {
|
||||
font-size: 15px;
|
||||
border: none;
|
||||
-webkit-appearance: none;
|
||||
width: 228px;
|
||||
height: 55px;
|
||||
line-height: 55px;
|
||||
font-weight: 500;
|
||||
box-shadow: none;
|
||||
float: left;
|
||||
background-color: #FFFFFF;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&__reload {
|
||||
display: block;
|
||||
width: 30px;
|
||||
color: #acacac;
|
||||
font-size: 1.9em;
|
||||
line-height: 55px;
|
||||
z-index: 3;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__button {
|
||||
width: 73px;
|
||||
height: 45px;
|
||||
background-color: #21B9FC;
|
||||
moz-border-radius: 1px;
|
||||
-webkit-border-radius: 1px;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
margin-top: 5px;
|
||||
font-size: 19px;
|
||||
padding-top: 6px;
|
||||
outline: none;
|
||||
float:left;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
#enter_room_button {
|
||||
width: 73px;
|
||||
height: 45px;
|
||||
background-color: #21B9FC;
|
||||
moz-border-radius: 1px;
|
||||
-webkit-border-radius: 1px;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
margin-top: 5px;
|
||||
font-size: 19px;
|
||||
padding-top: 6px;
|
||||
outline: none;
|
||||
float:left;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#enter_room_container {
|
||||
@@ -198,3 +184,16 @@
|
||||
line-height: 22px;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
#reload_roomname
|
||||
{
|
||||
width: 30px;
|
||||
color: #acacac;
|
||||
font-size: 1.9em;
|
||||
line-height: 55px;
|
||||
z-index: 3;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -82,8 +82,4 @@
|
||||
color: $linkHoverFontColor;
|
||||
}
|
||||
}
|
||||
|
||||
&_center {
|
||||
float: none !important;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
.overlay {
|
||||
&__container,
|
||||
&__container-light {
|
||||
&__container {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
@@ -10,10 +9,6 @@
|
||||
background: $defaultBackground;
|
||||
}
|
||||
|
||||
&__container-light {
|
||||
@include transparentBg($defaultBackground, 0.7);
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: absolute;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -85,9 +85,6 @@ $popupMenuSelectedItemBackground: rgba(256, 256, 256, .2);
|
||||
// Toolbar
|
||||
$splitterColor: #ccc;
|
||||
|
||||
// Thumbnail
|
||||
$thumbnailBorderColor: rgba(71, 71, 71, .7);
|
||||
|
||||
/**
|
||||
* Forms
|
||||
*/
|
||||
|
||||
5
debian/control
vendored
5
debian/control
vendored
@@ -21,7 +21,8 @@ Description: WebRTC JavaScript video conferences
|
||||
|
||||
Package: jitsi-meet-web-config
|
||||
Architecture: all
|
||||
Depends: openssl, openjdk-8-jre-headless | nginx | apache2
|
||||
Depends: openssl, openjdk-8-jre-headless | nginx | apache2,
|
||||
jitsi-meet-web
|
||||
Description: Configuration for web serving of Jitsi Meet
|
||||
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
|
||||
Videobridge to provide high quality, scalable video conferences.
|
||||
@@ -36,7 +37,7 @@ Description: Configuration for web serving of Jitsi Meet
|
||||
|
||||
Package: jitsi-meet-prosody
|
||||
Architecture: all
|
||||
Depends: openssl, prosody | prosody-trunk
|
||||
Depends: openssl, prosody | prosody-trunk, jitsi-meet-web
|
||||
Description: Prosody configuration for Jitsi Meet
|
||||
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
|
||||
Videobridge to provide high quality, scalable video conferences.
|
||||
|
||||
4
debian/jitsi-meet-web-config.docs
vendored
4
debian/jitsi-meet-web-config.docs
vendored
@@ -1,4 +0,0 @@
|
||||
doc/debian/jitsi-meet/jitsi-meet.example
|
||||
doc/debian/jitsi-meet/jitsi-meet.example-apache
|
||||
doc/debian/jitsi-meet/README
|
||||
config.js
|
||||
7
debian/jitsi-meet-web-config.postinst
vendored
7
debian/jitsi-meet-web-config.postinst
vendored
@@ -94,7 +94,7 @@ case "$1" in
|
||||
# jitsi meet
|
||||
JITSI_MEET_CONFIG="/etc/jitsi/meet/$JVB_HOSTNAME-config.js"
|
||||
if [ ! -f $JITSI_MEET_CONFIG ] ; then
|
||||
cp /usr/share/doc/jitsi-meet-web-config/config.js $JITSI_MEET_CONFIG
|
||||
cp /usr/share/doc/jitsi-meet-web/config.js $JITSI_MEET_CONFIG
|
||||
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" $JITSI_MEET_CONFIG
|
||||
fi
|
||||
|
||||
@@ -127,7 +127,6 @@ case "$1" in
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.resourceBase=/usr/share/jitsi-meet" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./config.js=/etc/jitsi/meet/$JVB_HOSTNAME-config.js" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./interface_config.js=/usr/share/jitsi-meet/interface_config.js" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./logging_config.js=/usr/share/jitsi-meet/logging_config.js" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.regex=^/([a-zA-Z0-9]+)$" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.replacement=/" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.SSIResourceHandler.paths=/" >> $JVB_CONFIG
|
||||
@@ -172,7 +171,7 @@ case "$1" in
|
||||
|
||||
# nginx conf
|
||||
if [ ! -f /etc/nginx/sites-available/$JVB_HOSTNAME.conf ] ; then
|
||||
cp /usr/share/doc/jitsi-meet-web-config/jitsi-meet.example /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
cp /usr/share/doc/jitsi-meet-web/jitsi-meet.example /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
if [ ! -f /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf ] ; then
|
||||
ln -s /etc/nginx/sites-available/$JVB_HOSTNAME.conf /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf
|
||||
fi
|
||||
@@ -203,7 +202,7 @@ case "$1" in
|
||||
if [ ! -f /etc/apache2/sites-available/$JVB_HOSTNAME.conf ] ; then
|
||||
# when creating new config, make sure all needed modules are enabled
|
||||
a2enmod rewrite ssl headers proxy_http include
|
||||
cp /usr/share/doc/jitsi-meet-web-config/jitsi-meet.example-apache /etc/apache2/sites-available/$JVB_HOSTNAME.conf
|
||||
cp /usr/share/doc/jitsi-meet-web/jitsi-meet.example-apache /etc/apache2/sites-available/$JVB_HOSTNAME.conf
|
||||
a2ensite $JVB_HOSTNAME.conf
|
||||
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /etc/apache2/sites-available/$JVB_HOSTNAME.conf
|
||||
fi
|
||||
|
||||
4
debian/jitsi-meet-web.docs
vendored
4
debian/jitsi-meet-web.docs
vendored
@@ -1 +1,5 @@
|
||||
README.md
|
||||
doc/debian/jitsi-meet/jitsi-meet.example
|
||||
doc/debian/jitsi-meet/jitsi-meet.example-apache
|
||||
doc/debian/jitsi-meet/README
|
||||
config.js
|
||||
|
||||
32
doc/api.md
32
doc/api.md
@@ -43,9 +43,11 @@ You can control the embedded Jitsi Meet conference using the JitsiMeetExternalAP
|
||||
|
||||
You can send command to Jitsi Meet conference using ```executeCommand```.
|
||||
```
|
||||
api.executeCommand(command, ...arguments)
|
||||
api.executeCommand(command, arguments)
|
||||
```
|
||||
The ```command``` parameter is String object with the name of the command.
|
||||
The ```arguments``` parameter is array with the arguments required by the command.
|
||||
If no arguments are required by the command this parameter can be omitted or you can pass empty array.
|
||||
Currently we support the following commands:
|
||||
|
||||
|
||||
@@ -56,43 +58,33 @@ api.executeCommand('displayName', 'New Nickname');
|
||||
```
|
||||
* **toggleAudio** - mutes / unmutes the audio for the local participant. No arguments are required.
|
||||
```
|
||||
api.executeCommand('toggleAudio')
|
||||
api.executeCommand('toggleAudio', [])
|
||||
```
|
||||
* **toggleVideo** - mutes / unmutes the video for the local participant. No arguments are required.
|
||||
```
|
||||
api.executeCommand('toggleVideo')
|
||||
api.executeCommand('toggleVideo', [])
|
||||
```
|
||||
* **toggleFilmStrip** - hides / shows the film strip. No arguments are required.
|
||||
```
|
||||
api.executeCommand('filmStrip')
|
||||
api.executeCommand('filmStrip', [])
|
||||
```
|
||||
* **toggleChat** - hides / shows the chat. No arguments are required.
|
||||
```
|
||||
api.executeCommand('toggleChat')
|
||||
api.executeCommand('toggleChat', [])
|
||||
```
|
||||
* **toggleContactList** - hides / shows the contact list. No arguments are required.
|
||||
```
|
||||
api.executeCommand('toggleContactList')
|
||||
api.executeCommand('toggleContactList', [])
|
||||
```
|
||||
|
||||
* **toggleShareScreen** - starts / stops the screen sharing. No arguments are required.
|
||||
```
|
||||
api.executeCommand('toggleShareScreen')
|
||||
api.executeCommand('toggleShareScreen', [])
|
||||
```
|
||||
|
||||
* **hangup** - Hangups the call. No arguments are required.
|
||||
```
|
||||
api.executeCommand('hangup')
|
||||
```
|
||||
|
||||
* **email** - Hangups the call. No arguments are required.
|
||||
```
|
||||
api.executeCommand('email', 'example@example.com')
|
||||
```
|
||||
|
||||
* **avatarUrl** - Hangups the call. No arguments are required.
|
||||
```
|
||||
api.executeCommand('avatarUrl', 'avatarUrl')
|
||||
api.executeCommand('hangup', [])
|
||||
```
|
||||
|
||||
You can also execute multiple commands using the method ```executeCommands```.
|
||||
@@ -154,14 +146,14 @@ The listener will receive object with the following structure:
|
||||
jid: jid //the jid of the participant
|
||||
}
|
||||
```
|
||||
* **videoConferenceJoined** - event notifications fired when the local user has joined the video conference.
|
||||
* **video-conference-joined** - 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
|
||||
}
|
||||
```
|
||||
* **videoConferenceLeft** - event notifications fired when the local user has left the video conference.
|
||||
* **video-conference-left** - event notifications fired when the local user has left the video conference.
|
||||
The listener will receive object with the following structure:
|
||||
```
|
||||
{
|
||||
|
||||
@@ -150,7 +150,7 @@ ant dist.{os-name}
|
||||
Run jicofo:
|
||||
```sh
|
||||
cd dist/{os-name}'
|
||||
./jicofo.sh --host=127.0.0.1 --domain=jitsi.example.com --secret=YOURSECRET2 --user_domain=auth.jitsi.example.com --user_name=focus --user_password=YOURSECRET3
|
||||
./jicofo.sh --domain=jitsi.example.com --secret=YOURSECRET2 --user_domain=auth.jitsi.example.com --user_name=focus --user_password=YOURSECRET3
|
||||
```
|
||||
|
||||
## Deploy Jitsi Meet
|
||||
|
||||
BIN
fonts/jitsi.eot
BIN
fonts/jitsi.eot
Binary file not shown.
@@ -14,6 +14,7 @@
|
||||
<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" />
|
||||
<glyph unicode="" glyph-name="autorenew" d="M800 694c34-52 54-116 54-182 0-188-154-342-342-342v-128l-170 172 170 170v-128c142 0 256 114 256 256 0 44-12 84-30 120zM512 768c-142 0-256-114-256-256 0-44 10-84 30-120l-62-62c-34 52-54 116-54 182 0 188 154 342 342 342v128l170-172-170-170v128z" />
|
||||
<glyph unicode="" glyph-name="kick" d="M512 810l284-426h-568zM214 298h596v-84h-596v84z" />
|
||||
<glyph unicode="" glyph-name="hangup" d="M512 640c-68 0-134-10-196-30v-132c0-16-10-34-24-40-42-20-80-46-114-78-8-8-18-12-30-12s-22 4-30 12l-106 106c-8 8-12 18-12 30s4 22 12 30c130 124 306 200 500 200s370-76 500-200c8-8 12-18 12-30s-4-22-12-30l-106-106c-8-8-18-12-30-12s-22 4-30 12c-34 32-72 58-114 78-14 6-24 20-24 38v132c-62 20-128 32-196 32z" />
|
||||
<glyph unicode="" glyph-name="chat" d="M854 342v512h-684v-598l86 86h598zM854 938c46 0 84-38 84-84v-512c0-46-38-86-84-86h-598l-170-170v768c0 46 38 84 84 84h684z" />
|
||||
@@ -43,5 +44,4 @@
|
||||
<glyph unicode="" glyph-name="raised-hand" d="M982 790v-620c0-94-78-170-172-170h-310c-46 0-90 18-122 50l-336 342s54 52 56 52c10 8 22 12 34 12 10 0 18-2 26-6 2 0 184-104 184-104v508c0 36 28 64 64 64s64-28 64-64v-300h42v406c0 36 28 64 64 64s64-28 64-64v-406h42v364c0 36 28 64 64 64s64-28 64-64v-364h44v236c0 36 28 64 64 64s64-28 64-64z" />
|
||||
<glyph unicode="" glyph-name="menu-up" d="M512 682l256-256-60-60-196 196-196-196-60 60z" />
|
||||
<glyph unicode="" glyph-name="menu-down" d="M708 658l60-60-256-256-256 256 60 60 196-196z" />
|
||||
<glyph unicode="" glyph-name="switch-camera" d="M640 362l150 150-150 150v-108h-256v108l-150-150 150-150v108h256v-108zM854 854c46 0 84-40 84-86v-512c0-46-38-86-84-86h-684c-46 0-84 40-84 86v512c0 46 38 86 84 86h136l78 84h256l78-84h136z" />
|
||||
</font></defs></svg>
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
BIN
fonts/jitsi.ttf
BIN
fonts/jitsi.ttf
Binary file not shown.
BIN
fonts/jitsi.woff
BIN
fonts/jitsi.woff
Binary file not shown.
@@ -85,6 +85,32 @@
|
||||
"setId": 2,
|
||||
"iconIdx": 12
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
"M800 330c34 52 54 116 54 182 0 188-154 342-342 342v128l-170-172 170-170v128c142 0 256-114 256-256 0-44-12-84-30-120zM512 256c-142 0-256 114-256 256 0 44 10 84 30 120l-62 62c-34-52-54-116-54-182 0-188 154-342 342-342v-128l170 172-170 170v-128z"
|
||||
],
|
||||
"isMulticolor": false,
|
||||
"isMulticolor2": false,
|
||||
"tags": [
|
||||
"autorenew"
|
||||
],
|
||||
"grid": 0,
|
||||
"attrs": []
|
||||
},
|
||||
"attrs": [],
|
||||
"properties": {
|
||||
"id": 68,
|
||||
"order": 84,
|
||||
"ligatures": "autorenew",
|
||||
"prevSize": 32,
|
||||
"code": 59651,
|
||||
"name": "autorenew"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 2,
|
||||
"iconIdx": 69
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
@@ -709,32 +735,6 @@
|
||||
"setId": 2,
|
||||
"iconIdx": 718
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
"M640 662l150-150-150-150v108h-256v-108l-150 150 150 150v-108h256v108zM854 170c46 0 84 40 84 86v512c0 46-38 86-84 86h-684c-46 0-84-40-84-86v-512c0-46 38-86 84-86h136l78-84h256l78 84h136z"
|
||||
],
|
||||
"isMulticolor": false,
|
||||
"isMulticolor2": false,
|
||||
"tags": [
|
||||
"switch_camera"
|
||||
],
|
||||
"grid": 0,
|
||||
"attrs": []
|
||||
},
|
||||
"attrs": [],
|
||||
"properties": {
|
||||
"id": 741,
|
||||
"order": 108,
|
||||
"ligatures": "switch_camera",
|
||||
"prevSize": 32,
|
||||
"code": 59681,
|
||||
"name": "switch-camera"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 2,
|
||||
"iconIdx": 742
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
|
||||
90
index.html
90
index.html
@@ -1,6 +1,6 @@
|
||||
<html itemscope itemtype="http://schema.org/Product" prefix="og: http://ogp.me/ns#" xmlns="http://www.w3.org/1999/html">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
||||
<!--#include virtual="base.html" -->
|
||||
<script>
|
||||
@@ -28,90 +28,9 @@
|
||||
&& criticalFiles.some(
|
||||
function(file) { return fileRef.indexOf(file) !== -1 })) {
|
||||
window.onload = function() {
|
||||
// The whole complex part below implements page reloads with
|
||||
// "exponential backoff". The retry attempt is passes as
|
||||
// "rCounter" query parameter
|
||||
var href = window.location.href;
|
||||
|
||||
var retryMatch = href.match(/.+(\?|&)rCounter=(\d+)/);
|
||||
var retryCountStr = retryMatch ? retryMatch[2] : "0";
|
||||
var retryCount = Number.parseInt(retryCountStr);
|
||||
|
||||
if (retryMatch == null) {
|
||||
var separator = href.indexOf("?") === -1 ? "?" : "&";
|
||||
var hashIdx = href.indexOf("#");
|
||||
|
||||
if (hashIdx === -1) {
|
||||
href += separator + "rCounter=1";
|
||||
} else {
|
||||
var hashPart = href.substr(hashIdx);
|
||||
|
||||
href = href.substr(0, hashIdx)
|
||||
+ separator + "rCounter=1" + hashPart;
|
||||
}
|
||||
} else {
|
||||
var separator = retryMatch[1];
|
||||
|
||||
href = href.replace(
|
||||
/(\?|&)rCounter=(\d+)/,
|
||||
separator + "rCounter=" + (retryCount + 1));
|
||||
}
|
||||
|
||||
var delay = Math.pow(2, retryCount) * 2000;
|
||||
if (isNaN(delay) || delay < 2000 || delay > 60000)
|
||||
delay = 10000;
|
||||
|
||||
var showMoreText = "show more";
|
||||
var showLessText = "show less";
|
||||
|
||||
document.body.innerHTML
|
||||
= "<div style='"
|
||||
+ "position: absolute;top: 50%;left: 50%;"
|
||||
+ "text-align: center;"
|
||||
+ "font-size: medium;"
|
||||
+ "font-weight: 400;"
|
||||
+ "transform: translate(-50%, -50%)'>"
|
||||
+ "Uh oh! We couldn't fully download everything we needed :(" // jshint ignore:line
|
||||
+ "<br/> "
|
||||
+ "We will try again shortly. In the mean time, check for problems with your Internet connection!" // jshint ignore:line
|
||||
+ "<br/><br/> "
|
||||
+ "<div id='moreInfo' style='"
|
||||
+ "display: none;'>" + "Missing " + fileRef
|
||||
+ "<br/><br/></div>"
|
||||
+ "<a id='showMore' style='"
|
||||
+ "text-decoration: underline;"
|
||||
+ "font-size:small;"
|
||||
+ "cursor: pointer'>" + showMoreText + "</a>"
|
||||
+ " "
|
||||
+ "<a href='" + href + "' style='"
|
||||
+ "text-decoration: underline;"
|
||||
+ "font-size:small;"
|
||||
+ "'>reload now</a>"
|
||||
+ "</div>";
|
||||
|
||||
var showMoreElem = document.getElementById("showMore");
|
||||
showMoreElem.addEventListener('click', function () {
|
||||
var moreInfoElem
|
||||
= document.getElementById("moreInfo");
|
||||
|
||||
if (showMoreElem.innerHTML === showMoreText) {
|
||||
moreInfoElem.setAttribute(
|
||||
"style",
|
||||
"display: block;"
|
||||
+ "color:#FF991F;"
|
||||
+ "font-size:small;"
|
||||
+ "user-select:text;");
|
||||
showMoreElem.innerHTML = showLessText;
|
||||
}
|
||||
else {
|
||||
moreInfoElem.setAttribute(
|
||||
"style", "display: none;");
|
||||
showMoreElem.innerHTML = showMoreText;
|
||||
}
|
||||
});
|
||||
|
||||
window.setTimeout(
|
||||
function () { window.location.replace(href); }, delay);
|
||||
= "The application failed to load, missing file: "
|
||||
+ fileRef;
|
||||
};
|
||||
window.removeEventListener(
|
||||
'error', loadErrHandler, true /* capture phase */);
|
||||
@@ -125,11 +44,10 @@
|
||||
<!--#include virtual="connection_optimization/connection_optimization.html" -->
|
||||
<script src="connection_optimization/do_external_connect.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>
|
||||
<script src="libs/app.bundle.min.js?v=139"></script>
|
||||
<!--#include virtual="title.html" -->
|
||||
<link rel="stylesheet" href="css/all.css">
|
||||
<link rel="stylesheet" href="css/all.css"/>
|
||||
<!--#include virtual="plugin.head.html" -->
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -11,8 +11,6 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
|
||||
DEFAULT_LOCAL_DISPLAY_NAME: "me",
|
||||
SHOW_JITSI_WATERMARK: true,
|
||||
JITSI_WATERMARK_LINK: "https://jitsi.org",
|
||||
// if watermark is disabled by default, it can be shown only for guests
|
||||
SHOW_WATERMARK_FOR_GUESTS: true,
|
||||
SHOW_BRAND_WATERMARK: false,
|
||||
BRAND_WATERMARK_LINK: "",
|
||||
SHOW_POWERED_BY: false,
|
||||
@@ -55,8 +53,6 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
|
||||
* Whether to only show the filmstrip (and hide the toolbar).
|
||||
*/
|
||||
filmStripOnly: false,
|
||||
//A html text to be shown to guests on the close page, false disables it
|
||||
CLOSE_PAGE_GUEST_HINT: false,
|
||||
RANDOM_AVATAR_URL_PREFIX: false,
|
||||
RANDOM_AVATAR_URL_SUFFIX: false,
|
||||
FILM_STRIP_MAX_HEIGHT: 120,
|
||||
|
||||
@@ -10,35 +10,10 @@
|
||||
#import "AppDelegate.h"
|
||||
#import <Crashlytics/Crashlytics.h>
|
||||
#import <Fabric/Fabric.h>
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBundleURLProvider.h"
|
||||
#import "RCTLinkingManager.h"
|
||||
#import "RCTRootView.h"
|
||||
|
||||
/**
|
||||
* A <tt>RCTFatalHandler</tt> implementation which swallows JavaScript errors.
|
||||
* In the Release configuration, React Native will (intentionally) raise an
|
||||
* unhandled NSException for an unhandled JavaScript error. This will
|
||||
* effectively kill the application. <tt>_RCTFatal</tt> is suitable to be in
|
||||
* accord with the Web i.e. not kill the application.
|
||||
*/
|
||||
RCTFatalHandler _RCTFatal = ^(NSError *error) {
|
||||
id jsStackTrace = error.userInfo[RCTJSStackTraceKey];
|
||||
@try {
|
||||
NSString *name
|
||||
= [NSString stringWithFormat:@"%@: %@",
|
||||
RCTFatalExceptionName,
|
||||
error.localizedDescription];
|
||||
NSString *message
|
||||
= RCTFormatError(error.localizedDescription, jsStackTrace, 75);
|
||||
[NSException raise:name format:@"%@", message];
|
||||
} @catch (NSException *e) {
|
||||
if (!jsStackTrace) {
|
||||
@throw;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
// https://facebook.github.io/react-native/docs/linking.html
|
||||
@@ -54,18 +29,8 @@ continueUserActivity:(NSUserActivity *)userActivity
|
||||
- (BOOL)application:(UIApplication *)application
|
||||
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
#if !DEBUG
|
||||
[Fabric with:@[[Crashlytics class]]];
|
||||
|
||||
// In the Release configuration, React Native will (intentionally) raise an
|
||||
// unhandled NSException for an unhandled JavaScript error. This will
|
||||
// effectively kill the application. In accord with the Web, do not kill the
|
||||
// application.
|
||||
if (!RCTGetFatalHandler()) {
|
||||
RCTSetFatalHandler(_RCTFatal);
|
||||
}
|
||||
#endif
|
||||
|
||||
NSURL *jsCodeLocation
|
||||
= [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios"
|
||||
fallbackResource:nil];
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.1</string>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
@interface POSIX : NSObject<RCTBridgeModule>
|
||||
@end
|
||||
@@ -1,63 +0,0 @@
|
||||
#import "POSIX.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
@implementation POSIX
|
||||
|
||||
RCT_EXPORT_MODULE();
|
||||
|
||||
RCT_EXPORT_METHOD(getaddrinfo:(NSString *)hostname
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject) {
|
||||
int err;
|
||||
struct addrinfo *res;
|
||||
NSString *rejectCode;
|
||||
|
||||
if (0 == (err = getaddrinfo(hostname.UTF8String, NULL, NULL, &res))) {
|
||||
int af = res->ai_family;
|
||||
struct sockaddr *sa = res->ai_addr;
|
||||
void *addr;
|
||||
|
||||
switch (af) {
|
||||
case AF_INET:
|
||||
addr = &(((struct sockaddr_in *) sa)->sin_addr);
|
||||
break;
|
||||
case AF_INET6:
|
||||
addr = &(((struct sockaddr_in6 *) sa)->sin6_addr);
|
||||
break;
|
||||
default:
|
||||
addr = NULL;
|
||||
break;
|
||||
}
|
||||
if (addr) {
|
||||
char v[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
|
||||
|
||||
if (inet_ntop(af, addr, v, sizeof(v))) {
|
||||
resolve([NSString stringWithUTF8String:v]);
|
||||
} else {
|
||||
err = errno;
|
||||
rejectCode = @"inet_ntop";
|
||||
}
|
||||
} else {
|
||||
err = EAFNOSUPPORT;
|
||||
rejectCode = @"EAFNOSUPPORT";
|
||||
}
|
||||
|
||||
freeaddrinfo(res);
|
||||
} else {
|
||||
rejectCode = @"getaddrinfo";
|
||||
}
|
||||
if (0 != err) {
|
||||
NSError *error
|
||||
= [NSError errorWithDomain:NSPOSIXErrorDomain
|
||||
code:err
|
||||
userInfo:nil];
|
||||
|
||||
reject(rejectCode, error.localizedDescription, error);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -26,7 +26,6 @@
|
||||
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
|
||||
B30EF2311DC0ED7C00690F45 /* WebRTC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B30EF2301DC0ED7C00690F45 /* WebRTC.framework */; };
|
||||
B30EF2331DC0EEA500690F45 /* WebRTC.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B30EF2301DC0ED7C00690F45 /* WebRTC.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B3A9D0251E0481E10009343D /* POSIX.m in Sources */ = {isa = PBXBuildFile; fileRef = B3A9D0241E0481E10009343D /* POSIX.m */; };
|
||||
BF9643821C34FBB300B0BBDF /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF9643811C34FBB300B0BBDF /* AVFoundation.framework */; };
|
||||
BF9643841C34FBBB00B0BBDF /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF9643831C34FBBB00B0BBDF /* AudioToolbox.framework */; };
|
||||
BF9643861C34FBC100B0BBDF /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF9643851C34FBC100B0BBDF /* CoreGraphics.framework */; };
|
||||
@@ -213,8 +212,6 @@
|
||||
821D8ABD506944B4BDBB069B /* libRNVectorIcons.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNVectorIcons.a; sourceTree = "<group>"; };
|
||||
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
|
||||
B30EF2301DC0ED7C00690F45 /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = "../node_modules/react-native-webrtc/ios/WebRTC.framework"; sourceTree = "<group>"; };
|
||||
B3A9D0231E0481E10009343D /* POSIX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = POSIX.h; path = app/POSIX.h; sourceTree = "<group>"; };
|
||||
B3A9D0241E0481E10009343D /* POSIX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = POSIX.m; path = app/POSIX.m; sourceTree = "<group>"; };
|
||||
B3B083EB1D4955FF0069CEE7 /* jitsi-meet-react.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "jitsi-meet-react.entitlements"; sourceTree = "<group>"; };
|
||||
B96AF9B6FBC0453798399985 /* FontAwesome.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"; sourceTree = "<group>"; };
|
||||
BF9643811C34FBB300B0BBDF /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
@@ -334,8 +331,6 @@
|
||||
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
|
||||
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
|
||||
13B07FB71A68108700A75B9A /* main.m */,
|
||||
B3A9D0231E0481E10009343D /* POSIX.h */,
|
||||
B3A9D0241E0481E10009343D /* POSIX.m */,
|
||||
);
|
||||
name = app;
|
||||
sourceTree = "<group>";
|
||||
@@ -760,7 +755,6 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B3A9D0251E0481E10009343D /* POSIX.m in Sources */,
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
||||
);
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
{
|
||||
"en": "English (английский)",
|
||||
"bg": "Болгарский",
|
||||
"de": "Немецкий",
|
||||
"es": "Испанский",
|
||||
"fr": "Французский",
|
||||
"hy": "Армянский",
|
||||
"it": "Итальянский",
|
||||
"oc": "Окситанский",
|
||||
"pl": "",
|
||||
"ptBR": "Португальский (Бразилия)",
|
||||
"ru": "",
|
||||
"sk": "Словацкий",
|
||||
"sl": "Словенский",
|
||||
"sv": "Шведский",
|
||||
"tr": "Турецкий"
|
||||
"en": "",
|
||||
"bg": "",
|
||||
"de": "",
|
||||
"es": "",
|
||||
"fr": "",
|
||||
"hy": "",
|
||||
"it": "",
|
||||
"oc": "",
|
||||
"ptBR": "",
|
||||
"sk": "",
|
||||
"sl": "",
|
||||
"sv": "",
|
||||
"tr": ""
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
{
|
||||
"contactlist": "Teilnehmer (__pcount__)",
|
||||
"addParticipants": "",
|
||||
"roomLocked": "Teilnehmer müssen ein Passwort eingeben",
|
||||
"roomUnlocked": "Jeder mit Zugriff auf den Link kann beitreten",
|
||||
"passwordSetRemotely": "von einem anderen Teilnehmer gesetzt",
|
||||
"contactlist": "Im Gespräch",
|
||||
"connectionsettings": "Verbindungseinstellungen",
|
||||
"poweredby": "Betrieben von",
|
||||
"feedback": "Wir freuen uns auf Ihr Feedback!",
|
||||
"inviteUrlDefaultMsg": "Die Konferenz wird erstellt...",
|
||||
"roomUrlDefaultMsg": "Die Konferenz wird erstellt...",
|
||||
"me": "ich",
|
||||
"speaker": "Sprecher",
|
||||
"raisedHand": "Möchte sprechen",
|
||||
@@ -25,18 +21,17 @@
|
||||
"nwjsGrantPermissions": "Bitte Berechtigungen zur Verwendung der Kamera und des Mikrofons erteilen"
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
"keyboardShortcuts": "Tastenkürzel",
|
||||
"raiseHand": "Hand erheben",
|
||||
"pushToTalk": "Drücken um zu sprechen",
|
||||
"toggleScreensharing": "Zwischen Kamera und Bildschirmfreigabe wechseln",
|
||||
"toggleFilmstrip": "Videos anzeigen oder verbergen",
|
||||
"toggleShortcuts": "Hilfe-Menu anzeigen oder verdecken",
|
||||
"focusLocal": "Lokales Video fokussieren",
|
||||
"focusRemote": "Auf das Video eines anderen Teilnehmers fokussieren",
|
||||
"toggleChat": "Chat öffnen oder schliessen",
|
||||
"mute": "Stummschaltung aktivieren oder deaktivieren",
|
||||
"fullScreen": "Vollbildmodus aktivieren / deaktivieren",
|
||||
"videoMute": "Kamera starten oder stoppen"
|
||||
"keyboardShortcuts": "Tastaturkürzel:",
|
||||
"raiseHand": "Heben Sie Ihre Hand.",
|
||||
"pushToTalk": "Drücken um zu sprechen.",
|
||||
"toggleScreensharing": "Zwischen Kamera und Bildschirmfreigabe wechseln.",
|
||||
"toggleFilmstrip": "Videos anzeigen oder verbergen.",
|
||||
"toggleShortcuts": "Hilfe-Menu anzeigen oder verdecken.",
|
||||
"focusLocal": "Lokales Video fokussieren.",
|
||||
"focusRemote": "Andere Videos fokussieren.",
|
||||
"toggleChat": "Chat öffnen oder schliessen.",
|
||||
"mute": "Stummschaltung aktivieren oder deaktivieren.",
|
||||
"videoMute": "Eigenes Video starten oder stoppen."
|
||||
},
|
||||
"welcomepage": {
|
||||
"go": "Los",
|
||||
@@ -75,45 +70,35 @@
|
||||
"content": "Die Verwendung kann durch die Integration mit Piwik, Google Analytics und anderen Überwachungs- und Statistikprogrammen protokolliert werden."
|
||||
}
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
"title": "__app__ benötigt Kamera und Mikrofon."
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"title": "Die Konferenz wurde unterbrochen weil der Computer den Standbymodus aktivierte.",
|
||||
"rejoinKeyTitle": "Erneut teilnehmen"
|
||||
},
|
||||
"toolbar": {
|
||||
"mute": "Stummschaltung aktivieren / deaktivieren",
|
||||
"videomute": "Kamera starten / stoppen",
|
||||
"authenticate": "Anmelden",
|
||||
"lock": "Konferenz schützen / Schutz aufheben",
|
||||
"invite": "Link teilen",
|
||||
"invite": "Andere einladen",
|
||||
"chat": "Chat öffnen / schliessen",
|
||||
"etherpad": "Geteiltes Dokument öffnen / schliessen",
|
||||
"etherpad": "Dokument teilen",
|
||||
"sharedvideo": "YouTube-Video teilen",
|
||||
"sharescreen": "Bildschirmfreigabe starten / stoppen",
|
||||
"sharescreen": "Bildschirm freigeben",
|
||||
"fullscreen": "Vollbildmodus aktivieren / deaktivieren",
|
||||
"sip": "SIP Nummer anrufen",
|
||||
"Settings": "Einstellungen",
|
||||
"hangup": "Verlassen",
|
||||
"hangup": "Konferenz verlassen",
|
||||
"login": "Anmelden",
|
||||
"logout": "Abmelden",
|
||||
"dialpad": "Wähltastatur öffnen / schliessen",
|
||||
"dialpad": "Tastenblock anzeigen",
|
||||
"sharedVideoMutedPopup": "Das geteilte Video wurde stumm geschaltet damit mit <br/>den anderen Teilnehmern gesprochen werden kann.",
|
||||
"micMutedPopup": "Ihr Mikrofon wurde stumm geschaltet damit das<br/>geteilte Video genossen werden kann.",
|
||||
"talkWhileMutedPopup": "Versuchen sie zu sprechen? Ihr Mikrofon ist stummgeschaltet.",
|
||||
"unableToUnmutePopup": "Die Stummschaltung kann nicht aufgehoben werden während das geteilte Video abgespielt wird.",
|
||||
"cameraDisabled": "Keine Kamera verfügbar",
|
||||
"micDisabled": "Kein Mikrofon verfügbar",
|
||||
"filmstrip": "Videos anzeigen / verbergen",
|
||||
"profile": "Profil bearbeiten",
|
||||
"raiseHand": "Hand erheben"
|
||||
"raiseHand": "Hand erheben um zu sprechen"
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Chat öffnen / schliessen",
|
||||
"filmstrip": "Videos anzeigen / verbergen",
|
||||
"contactlist": "Teilnehmer anzeigen und einladen"
|
||||
"contactlist": "Kontaktliste öffnen / schliessen"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
@@ -141,10 +126,9 @@
|
||||
"setPasswordLabel": "Konferenz mit einem Passwort schützen."
|
||||
},
|
||||
"profile": {
|
||||
"title": "Profil",
|
||||
"title": "PROFIL",
|
||||
"setDisplayNameLabel": "Anzeigename festlegen",
|
||||
"setEmailLabel": "E-Mail Adresse für Gravatar",
|
||||
"setEmailInput": "E-Mail eingeben"
|
||||
"setEmailLabel": "E-Mail Adresse für Gravatar"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "Klicken, um den Anzeigenamen zu bearbeiten",
|
||||
@@ -157,7 +141,6 @@
|
||||
"flip": "Spiegeln"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "Verbindungsdaten",
|
||||
"bitrate": "Bitrate:",
|
||||
"packetloss": "Paketverlust:",
|
||||
"resolution": "Auflösung:",
|
||||
@@ -191,30 +174,20 @@
|
||||
"raisedHand": "Möchte sprechen."
|
||||
},
|
||||
"dialog": {
|
||||
"add": "Hinzufügen",
|
||||
"kickMessage": "Oh! Sie wurden aus der Konferenz ausgeschlossen.",
|
||||
"popupError": "Ihr Browser blockiert Popup-Fenster von dieser Webseite. Bitte erlauben Sie dieser Seite Popups in den Sicherheitseinstellungen Ihres Browsers und versuchen Sie es erneut.",
|
||||
"passwordErrorTitle": "Passwort-Fehler",
|
||||
"passwordError": "Diese Konferenz ist mit einem Paswort geschützt. Nur der Besitzer der Konferenz kann ein Passwort vergeben.",
|
||||
"passwordError2": "Diese Konferenzt ist nicht mit einem Passwort geschützt. Nur der Besitzer der Konferenz kann ein Passwort vergeben.",
|
||||
"connectError": "Oh! Es hat etwas nicht geklappt und der Konferenz konnte nicht beigetreten werden.",
|
||||
"connectErrorWithMsg": "Oh! Es hat etwas nicht geklappt und der Konferenz konnte nicht beigetreten werden: __msg__",
|
||||
"incorrectPassword": "Das Passwort ist ungültig",
|
||||
"connecting": "Verbindung wird hergestellt",
|
||||
"copy": "Kopieren",
|
||||
"error": "Fehler",
|
||||
"roomLocked": "Diese Konferenz ist gesperrt. Neue Teilnehmer müssen über den Link beitreten das Passwort eingeben",
|
||||
"addPassword": "Passwort hinzufügen",
|
||||
"createPassword": "Passwort erstellen",
|
||||
"detectext": "Fehler bei der Erkennung der Bildschirmfreigabeerweiterung.",
|
||||
"failtoinstall": "Die Bildschirmfreigabeerweiterung konnte nicht installiert werden.",
|
||||
"failedpermissions": "Die Zugriffsberechtigungen auf das Mikrofon und/oder die Kamera konnten nicht eingeholt werden.",
|
||||
"conferenceReloadTitle": "Leider ist etwas schiefgegangen",
|
||||
"conferenceReloadMsg": "Wir versuchen das zu beheben",
|
||||
"conferenceDisconnectTitle": "Sie wurden getrennt. Prüfen Sie Ihre Netzwerkverbindung.",
|
||||
"conferenceDisconnectMsg": "Verbinde erneut in...",
|
||||
"reconnectNow": "Jetzt erneut verbinden",
|
||||
"conferenceReloadTimeLeft": "__seconds__ sek.",
|
||||
"bridgeUnavailable": "Die Jitsi Videobridge ist momentan nicht erreichbar. Bitte versuchen Sie es später noch einmal.",
|
||||
"jicofoUnavailable": "Jicofo ist momentan nicht erreichbar. Bitte versuchen Sie es später noch einmal.",
|
||||
"maxUsersLimitReached": "Die maximale Teilnehmerzahl dieser Konferenz ist erreicht. Die Konferenz ist voll. Bitte versuchen Sie es später noch einmal.",
|
||||
"lockTitle": "Sperren fehlgeschlagen",
|
||||
"lockMessage": "Die Konferenz konnte nicht gesperrt werden.",
|
||||
@@ -226,14 +199,10 @@
|
||||
"SLDFailure": "Oh! Die Stummschaltung konnte nicht aktiviert werden. (SLD Fehler)",
|
||||
"SRDFailure": "Oh! Das Video konnte nicht gestoppt werden. (SRD Fehler)",
|
||||
"oops": "Oh!",
|
||||
"currentPassword": "Das aktuelle Passwort ist",
|
||||
"passwordLabel": "Passwort",
|
||||
"defaultError": "Es ist ein Fehler aufgetreten",
|
||||
"passwordRequired": "Passwort erforderlich",
|
||||
"Ok": "OK",
|
||||
"done": "Fertig",
|
||||
"Remove": "Entfernen",
|
||||
"removePassword": "Passwort entfernen",
|
||||
"shareVideoTitle": "Video teilen",
|
||||
"shareVideoLinkError": "Bitte einen gültigen YouTube-Link angeben.",
|
||||
"removeSharedVideoTitle": "Freigegebenes Video entfernen",
|
||||
@@ -243,7 +212,6 @@
|
||||
"WaitForHostMsg": "Die Konferenz <b>__room__</b> hat noch nicht begonnen. Wenn Sie der Organisator sind, melden Sie sich bitte an. Anderenfalls warten Sie bitte, bis der Organisator beigetreten ist.",
|
||||
"IamHost": "Ich bin der Organisator",
|
||||
"Cancel": "Abbrechen",
|
||||
"Submit": "OK",
|
||||
"retry": "Wiederholen",
|
||||
"logoutTitle": "Abmelden",
|
||||
"logoutQuestion": "Sind Sie sicher, dass Sie sich abmelden und die Konferenz verlassen möchten?",
|
||||
@@ -258,7 +226,7 @@
|
||||
"sipMsg": "Geben Sie eine SIP Nummer ein",
|
||||
"passwordCheck": "Sind Sie sicher, dass Sie das Passwort entfernen möchten?",
|
||||
"passwordMsg": "Passwort setzen um die Konferenz zu schützen",
|
||||
"shareLink": "Link zu dieser Konferenz teilen",
|
||||
"shareLink": "Diesen Link kopieren und teilen",
|
||||
"settings1": "Konferenz einrichten",
|
||||
"settings2": "Teilnehmer treten stummgeschaltet bei",
|
||||
"settings3": "Name erforderlich<br/><br/>Setzen Sie ein Passwort, um die Konferenz zu schützen:",
|
||||
@@ -274,8 +242,7 @@
|
||||
"token": "Token",
|
||||
"tokenAuthFailedTitle": "Authentifizierungsfehler",
|
||||
"tokenAuthFailed": "Sie sind nicht berechtigt dieser Konferenz beizutreten.",
|
||||
"displayNameRequired": "Anzeigename ist erforderlich",
|
||||
"enterDisplayName": "Geben Sie Ihren Anzeigenamen ein",
|
||||
"displayNameRequired": "Geben Sie Ihren Anzeigenamen ein",
|
||||
"extensionRequired": "Erweiterung erforderlich:",
|
||||
"firefoxExtensionPrompt": "Um die Bildschirmfreigabe nutzen zu können, muss eine Firefox-Erweiterung installiert werden. Bitte versuchen Sie es erneut nachdem die <a href='__url__'>Erweiterung installiert</a> wurde.",
|
||||
"rateExperience": "Bitte bewerten Sie diese Konferenz.",
|
||||
@@ -291,7 +258,6 @@
|
||||
"stopLiveStreaming": "Live-Streaming stoppen",
|
||||
"stopRecording": "Aufnahme stoppen",
|
||||
"doNotShowWarningAgain": "Diesen Hinweis nicht mehr anzeigen",
|
||||
"doNotShowMessageAgain": "Diesen Hinweis nicht mehr anzeigen",
|
||||
"permissionDenied": "Zugriff verweigert",
|
||||
"screenSharingPermissionDeniedError": "Sie haben die Berechtigung für die Bildschirmfreigabe nicht erteilt.",
|
||||
"micErrorPresent": "Fehler beim Verbinden zum Mikrofon.",
|
||||
@@ -309,10 +275,7 @@
|
||||
"cameraNotSendingData": "Die Kamera kann nicht verwendet werden. Bitte wählen Sie eine andere Kamera in den Einstellungen oder laden Sie die Konferenz neu.",
|
||||
"goToStore": "Zum Store",
|
||||
"externalInstallationTitle": "Erweiterung erforderlich",
|
||||
"externalInstallationMsg": "Die Bildschirmfreigabeerweiterung muss installiert werden.",
|
||||
"muteParticipantTitle": "Teilnehmer stummschalten?",
|
||||
"muteParticipantBody": "Sie können die Stummschaltung anderer Teilnehmer nicht aufheben, aber ein Teilnehmer kann seine eigene Stummschaltung jederzeit beenden.",
|
||||
"muteParticipantButton": "Stummschalten"
|
||||
"externalInstallationMsg": "Die Bildschirmfreigabeerweiterung muss installiert werden."
|
||||
},
|
||||
"\u0005dialog": {},
|
||||
"email": {
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
{
|
||||
"contactlist": "Participantes (__pcount__)",
|
||||
"addParticipants": "",
|
||||
"roomLocked": "Visitantes precisam digitar uma senha",
|
||||
"roomUnlocked": "Qualquer um com o link pode entrar",
|
||||
"passwordSetRemotely": "Definido por outro participante",
|
||||
"contactlist": "Na chamada",
|
||||
"connectionsettings": "Configurações de conexão",
|
||||
"poweredby": "distribuído por",
|
||||
"feedback": "Dê seus comentários",
|
||||
"inviteUrlDefaultMsg": "Sua conferência está sendo criado...",
|
||||
"roomUrlDefaultMsg": "Sua conferência está sendo criado...",
|
||||
"me": "eu",
|
||||
"speaker": "Orador",
|
||||
"raisedHand": "Gostaria de falar",
|
||||
@@ -25,18 +21,17 @@
|
||||
"nwjsGrantPermissions": "Dê as permissões para usar sua câmera e microfone"
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
"keyboardShortcuts": "Atalhos de teclado",
|
||||
"raiseHand": "Erga ou baixe sua mão",
|
||||
"pushToTalk": "Pressione para falar",
|
||||
"toggleScreensharing": "Trocar entre câmera e compartilhamento de tela",
|
||||
"toggleFilmstrip": "Mostrar ou ocultar a barra lateral",
|
||||
"toggleShortcuts": "Mostrar ou ocultar este menu de ajuda",
|
||||
"focusLocal": "Foco em seu vídeo",
|
||||
"focusRemote": "Foco no vídeo de outro visitante",
|
||||
"toggleChat": "Abrir ou fechar o painel de bate-papo",
|
||||
"mute": "Deixar mudo ou não o microfone",
|
||||
"fullScreen": "Entrar ou sair da tela cheia",
|
||||
"videoMute": "Iniciar ou parar sua câmera"
|
||||
"keyboardShortcuts": "Atalhos de teclado:",
|
||||
"raiseHand": "Erguer sua mão.",
|
||||
"pushToTalk": "Pressione para falar.",
|
||||
"toggleScreensharing": "Trocar entre câmera e compartilhamento de tela.",
|
||||
"toggleFilmstrip": "Mostrar ou ocultar os vídeos.",
|
||||
"toggleShortcuts": "Mostrar ou ocultar este menu de ajuda.",
|
||||
"focusLocal": "Foco no vídeo local.",
|
||||
"focusRemote": "Foco em um dos vídeos remotos.",
|
||||
"toggleChat": "Abrir ou fechar o painel de bate-papo.",
|
||||
"mute": "Deixar mudo ou não o microfone.",
|
||||
"videoMute": "Parar ou iniciar o vídeo local."
|
||||
},
|
||||
"welcomepage": {
|
||||
"go": "IR",
|
||||
@@ -75,45 +70,35 @@
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
"title": "O __app__ precisa usar seu microfone e câmera."
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"title": "Sua chamada de vídeo foi interrompida, porque seu computador foi dormir.",
|
||||
"rejoinKeyTitle": "Reentrar"
|
||||
},
|
||||
"toolbar": {
|
||||
"mute": "Mudo / Não mudo",
|
||||
"videomute": "Iniciar ou parar a câmera",
|
||||
"videomute": "Iniciar / parar a câmera",
|
||||
"authenticate": "Autenticar",
|
||||
"lock": "Travar ou destravar a sala",
|
||||
"invite": "Compartilhar o link",
|
||||
"chat": "Abrir ou fechar o bate-papo",
|
||||
"etherpad": "Abrir ou fechar o documento compartilhado",
|
||||
"lock": "Travar / destravar a sala",
|
||||
"invite": "Convidar outros",
|
||||
"chat": "Abrir / fechar bate-papo",
|
||||
"etherpad": "Documento compartilhado",
|
||||
"sharedvideo": "Compartilhar um vídeo do YouTube",
|
||||
"sharescreen": "Iniciar ou parar o compartilhamento de tela",
|
||||
"fullscreen": "Entrar ou sair da tela cheia",
|
||||
"sharescreen": "Compartilhar tela",
|
||||
"fullscreen": "Entrar / Sair de Tela Cheia",
|
||||
"sip": "Chamar número SIP",
|
||||
"Settings": "Configurações",
|
||||
"hangup": "Sair",
|
||||
"hangup": "Desligar",
|
||||
"login": "Iniciar sessão",
|
||||
"logout": "Encerrar sessão",
|
||||
"dialpad": "Abrir ou fechar teclado de discagem",
|
||||
"dialpad": "Mostrar teclas de discagem",
|
||||
"sharedVideoMutedPopup": "Seu vídeo compartilhado está mudo assim<br/>você pode falar com os outros participantes.",
|
||||
"micMutedPopup": "Seu microfone está mudo assim que você<br/>pode curtir plenamente seu vídeo compartilhado.",
|
||||
"talkWhileMutedPopup": "Tentando falar? Você está em mudo.",
|
||||
"unableToUnmutePopup": "Você não pode sair do mudo enquanto seu vídeo compartilhado está ativo.",
|
||||
"cameraDisabled": "A câmera não está disponível",
|
||||
"micDisabled": "O microfone não está disponível",
|
||||
"filmstrip": "Mostrar / ocultar vídeos",
|
||||
"profile": "Editar seu perfil",
|
||||
"raiseHand": "Erguer o baixar sua mão"
|
||||
"filmstrip": "",
|
||||
"raiseHand": "Levantar a mão para falar"
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Abrir / fechar bate-papo",
|
||||
"filmstrip": "Mostrar/ocultar vídeos",
|
||||
"contactlist": "Ver e convidar participantes"
|
||||
"contactlist": "Abrir / fechar a lista de contatos"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
@@ -141,10 +126,9 @@
|
||||
"setPasswordLabel": "Trancar sua sala com uma senha."
|
||||
},
|
||||
"profile": {
|
||||
"title": "Perfil",
|
||||
"title": "PERFIL",
|
||||
"setDisplayNameLabel": "Definir seu nome de exibição",
|
||||
"setEmailLabel": "Definir seu email de gravatar",
|
||||
"setEmailInput": "Digite e-mail"
|
||||
"setEmailLabel": "Definir seu email de gravatar"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "Clique para editar o seu <br/>nome de exibição",
|
||||
@@ -157,7 +141,6 @@
|
||||
"flip": "Inverter"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "Dados da conexão",
|
||||
"bitrate": "Taxa de bits:",
|
||||
"packetloss": "Perda de pacote:",
|
||||
"resolution": "Resolução:",
|
||||
@@ -191,30 +174,20 @@
|
||||
"raisedHand": "Gostaria de falar."
|
||||
},
|
||||
"dialog": {
|
||||
"add": "Adicionar",
|
||||
"kickMessage": "Ouch! Você o chutou para fora da reunião!",
|
||||
"popupError": "Seu navegador está bloqueando janelas popup deste site. Por favor, habilite popups nas configurações de segurança do seu navegador e tente novamente.",
|
||||
"passwordErrorTitle": "Erro na senha",
|
||||
"passwordError": "Esta conversa está protegida atualmente por uma senha. Somente o dono da conferência pode definir a senha.",
|
||||
"passwordError2": "Esta reunião não está protegida por senha atualmente. Somente o dono da conferência pode definir a senha.",
|
||||
"connectError": "Oops! Alguma coisa está errada e nós não pudemos conectar à conferência.",
|
||||
"connectErrorWithMsg": "Oops! Alguma coisa está errada e não podemos conectar à conferência: __msg__",
|
||||
"incorrectPassword": "A senha está incorreta",
|
||||
"connecting": "Conectando",
|
||||
"copy": "Copiar",
|
||||
"error": "Erro",
|
||||
"roomLocked": "Esta chamada está fechada. Novos visitantes precisam ter o link e digitar a senha para entrar",
|
||||
"addPassword": "Adicionar uma senha",
|
||||
"createPassword": "Criar uma senha",
|
||||
"detectext": "Erro enquanto tenta detectar a extensão de compartilhamento de tela.",
|
||||
"failtoinstall": "Falhou a instalação da extensão de compartilhamento de tela",
|
||||
"failedpermissions": "Falha ao obter permissões para usar o microfone e/ou câmera local.",
|
||||
"conferenceReloadTitle": "Infelizmente, algo deu errado",
|
||||
"conferenceReloadMsg": "Estamos tentando concertar isso",
|
||||
"conferenceDisconnectTitle": "Você foi desconectado. Verifique sua conexão de rede.",
|
||||
"conferenceDisconnectMsg": "Reconectando em...",
|
||||
"reconnectNow": "Reconecte agora",
|
||||
"conferenceReloadTimeLeft": "__seconds__ s.",
|
||||
"bridgeUnavailable": "Jitsi Videobridge está atualmente indisponível. Por favor, tente mais tarde!",
|
||||
"jicofoUnavailable": "Jicofo está atualmente indisponível. Por favor, tente mais tarde!",
|
||||
"maxUsersLimitReached": "O limite para o número máximo de participantes na conferência foi atingida. A conferência está cheia. Por favor, tente mais tarde!",
|
||||
"lockTitle": "Bloqueio falhou",
|
||||
"lockMessage": "Falha ao travar a conferência.",
|
||||
@@ -226,14 +199,10 @@
|
||||
"SLDFailure": "Oops! Alguma coisa está errada e nós falhamos em silenciar! (Falha do SLD)",
|
||||
"SRDFailure": "Oops! Alguma coisa está errada e nós falhamos em parar o vídeo! (Falha do SRD)",
|
||||
"oops": "Oops!",
|
||||
"currentPassword": "A senha atual é",
|
||||
"passwordLabel": "Senha",
|
||||
"defaultError": "Aqui teve algum tipo de erro",
|
||||
"passwordRequired": "Senha requerida",
|
||||
"Ok": "Ok",
|
||||
"done": "Feito",
|
||||
"Remove": "Remover",
|
||||
"removePassword": "Remover senha",
|
||||
"shareVideoTitle": "Compartilhar um vídeo",
|
||||
"shareVideoLinkError": "Por favor, forneça um link do youtube correto.",
|
||||
"removeSharedVideoTitle": "Remover vídeo compartilhado",
|
||||
@@ -243,13 +212,12 @@
|
||||
"WaitForHostMsg": "A conferência <b>__room__</b> não foi iniciada. Se você é o hospedeiro, então autentique-se. Caso contrário, aguarde o hospedeiro chegar.",
|
||||
"IamHost": "Eu sou o hospedeiro",
|
||||
"Cancel": "Cancelar",
|
||||
"Submit": "Enviar",
|
||||
"retry": "Tentar novamente",
|
||||
"logoutTitle": "Encerrar sessão",
|
||||
"logoutQuestion": "Está certo em encerrar a sessão e terminar a conferência?",
|
||||
"sessTerminated": "Sessão Terminada",
|
||||
"hungUp": "Você desconectou",
|
||||
"joinAgain": "Entrar novamente",
|
||||
"joinAgain": "Conectar novamente",
|
||||
"Share": "Compartilhar",
|
||||
"Save": "Salvar",
|
||||
"recording": "Gravando",
|
||||
@@ -258,7 +226,7 @@
|
||||
"sipMsg": "Digite o número SIP",
|
||||
"passwordCheck": "Você tem certeza que deseja remover sua senha?",
|
||||
"passwordMsg": "Definir uma senha para trancar sua sala",
|
||||
"shareLink": "Compartilhar o link para a chamada",
|
||||
"shareLink": "Copiar e compartilhar este link",
|
||||
"settings1": "Configure sua conferência",
|
||||
"settings2": "Participantes entram mudos",
|
||||
"settings3": "Requer apelidos<br/><br/>Defina uma senha para trancar sua sala:",
|
||||
@@ -274,8 +242,7 @@
|
||||
"token": "token",
|
||||
"tokenAuthFailedTitle": "Problema na autenticação",
|
||||
"tokenAuthFailed": "Desculpe, você não está autorizado a entrar nesta chamada.",
|
||||
"displayNameRequired": "Mostrar o nome é requerido",
|
||||
"enterDisplayName": "Digite seu nome de exibição",
|
||||
"displayNameRequired": "Digite seu nome de exibição",
|
||||
"extensionRequired": "Extensão requerida:",
|
||||
"firefoxExtensionPrompt": "Você precisa instalar uma extensão do Firefox para compartilhar a tela. Tente novamente depois que você <a href='__url__'>pegá-lo aqui</a>!",
|
||||
"rateExperience": "Por favor, avalie sua experiência na reunião.",
|
||||
@@ -291,7 +258,6 @@
|
||||
"stopLiveStreaming": "Parar o live streaming",
|
||||
"stopRecording": "Parar a gravação",
|
||||
"doNotShowWarningAgain": "Não exibir este aviso novamente",
|
||||
"doNotShowMessageAgain": "Não mostre esta mensagem novamente",
|
||||
"permissionDenied": "Permissão Negada",
|
||||
"screenSharingPermissionDeniedError": "Você não tem permissão concedida para compartilhar sua tela.",
|
||||
"micErrorPresent": "Ocorreu um erro conectando seu microfone.",
|
||||
@@ -309,10 +275,7 @@
|
||||
"cameraNotSendingData": "Sua câmera está inacessível. Verifique se outra aplicação está usando este dispositivo, selecione outro dispositivo do menu de configurações ou tente reiniciar a aplicação.",
|
||||
"goToStore": "Vá para a loja virtual",
|
||||
"externalInstallationTitle": "Extensão requerida",
|
||||
"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"
|
||||
"externalInstallationMsg": "Você precisa instalar nossa extensão de compartilhamento de tela."
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
@@ -327,18 +290,14 @@
|
||||
"body": [
|
||||
"Olá, gostaria de convidá-lo para uma conferência do __appName__ na qual eu estou participando.",
|
||||
"",
|
||||
"",
|
||||
"Por favor clique no link a seguir para entrar na conferência.",
|
||||
"",
|
||||
"Por favor clique no endereço a seguir para participar:",
|
||||
"",
|
||||
"__roomUrl__",
|
||||
"",
|
||||
"",
|
||||
"__sharedKeyText__",
|
||||
" Note que o __appName__ atualmente só funciona nos navegadores __supportedBrowsers__, assim é necessário usar um destes navegadores.",
|
||||
" Note que o __appName__ atualmente só funciona nos navegadores __supportedBrowsers__, assim é preciso abrir o link com um deles para poder participar.",
|
||||
"",
|
||||
"",
|
||||
"Falo com com você em um segundo!"
|
||||
"Aguardo sua presença!"
|
||||
],
|
||||
"and": "e"
|
||||
},
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
{
|
||||
"contactlist": "",
|
||||
"addParticipants": "",
|
||||
"roomLocked": "",
|
||||
"roomUnlocked": "",
|
||||
"passwordSetRemotely": "",
|
||||
"connectionsettings": "Настройки подключения",
|
||||
"poweredby": "работает на",
|
||||
"feedback": "Оставьте нам свой отзыв",
|
||||
"inviteUrlDefaultMsg": "Ваша конференция создается в данный момент...",
|
||||
"me": "Я",
|
||||
"speaker": "Говорящий",
|
||||
"raisedHand": "Хочет говорить",
|
||||
"defaultNickname": "напр. Яна Цветочкина",
|
||||
"defaultLink": "напр. __url__",
|
||||
"callingName": "",
|
||||
"connectionsettings": "",
|
||||
"poweredby": "",
|
||||
"downloadlogs": "",
|
||||
"feedback": "",
|
||||
"roomUrlDefaultMsg": "",
|
||||
"participant": "",
|
||||
"me": "",
|
||||
"speaker": "",
|
||||
"raisedHand": "",
|
||||
"defaultNickname": "",
|
||||
"defaultLink": "",
|
||||
"calling": "",
|
||||
"userMedia": {
|
||||
"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": "Пожалуйста дайте разрешение на доступ к камере и микрофону"
|
||||
"react-nativeGrantPermissions": "",
|
||||
"chromeGrantPermissions": "",
|
||||
"androidGrantPermissions": "",
|
||||
"firefoxGrantPermissions": "",
|
||||
"operaGrantPermissions": "",
|
||||
"iexplorerGrantPermissions": "",
|
||||
"safariGrantPermissions": "",
|
||||
"nwjsGrantPermissions": ""
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
"keyboardShortcuts": "",
|
||||
@@ -35,347 +33,269 @@
|
||||
"focusRemote": "",
|
||||
"toggleChat": "",
|
||||
"mute": "",
|
||||
"fullScreen": "",
|
||||
"videoMute": ""
|
||||
},
|
||||
"welcomepage": {
|
||||
"go": "Вперед!",
|
||||
"roomname": "Введите название комнаты",
|
||||
"disable": "Не показывать эту страницу снова",
|
||||
"go": "",
|
||||
"roomname": "",
|
||||
"disable": "",
|
||||
"feature1": {
|
||||
"title": "Простой в использовании",
|
||||
"content": "Нет нужды что-либо скачивать. __app__ работает прямо из вашего браузера. Просто отправьте URL ссылку на вашу конференцию другим, чтобы начать общение."
|
||||
"title": "",
|
||||
"content": ""
|
||||
},
|
||||
"feature2": {
|
||||
"title": "Низкие требования к ширине канала",
|
||||
"content": "Многопользовательским видеоконференциям достаточно скорости передачи данных в 128 Кбит/с. Демонстрация экрана или аудиоконференции требуют и того меньше."
|
||||
"title": "",
|
||||
"content": ""
|
||||
},
|
||||
"feature3": {
|
||||
"title": "Открытый исходный код",
|
||||
"content": "__app__ лицензирован под Apache License. Вы можете свободно скачивать, использовать, изменять это ПО в соответствии с условиями лицензии."
|
||||
"title": "",
|
||||
"content": ""
|
||||
},
|
||||
"feature4": {
|
||||
"title": "Неограниченное количество пользовательниц",
|
||||
"content": "Нет никаких искусственных ограничений по количеству пользовательниц или участников конференций. Вас отграничивают только мощность сервера и качество соединения."
|
||||
"title": "",
|
||||
"content": ""
|
||||
},
|
||||
"feature5": {
|
||||
"title": "Общий доступ к экрану",
|
||||
"content": "С лёгкостью можно пользоваться экраном совместно. __app__ идеально для онлайн презентаций, лекций и сеансов техподдержки."
|
||||
"title": "",
|
||||
"content": ""
|
||||
},
|
||||
"feature6": {
|
||||
"title": "Защищённые комнаты",
|
||||
"content": "Нужно больше приватности? __app__ конференц-комнаты могут быть защищены паролем, чтобы исключить незваных гостей или заминки."
|
||||
"title": "",
|
||||
"content": ""
|
||||
},
|
||||
"feature7": {
|
||||
"title": "Поделиться заметками",
|
||||
"content": "__app__ включает Etherpad, текстовый редактор для совместной работы над текстом в реальном времени, который замечательно подходит, чтобы вести протоколы или совместно писать статьи."
|
||||
"title": "",
|
||||
"content": ""
|
||||
},
|
||||
"feature8": {
|
||||
"title": "Статистика использования",
|
||||
"content": "Узнайте больше о пользователях с помощью интеграции с Piwik, Google Analytics и другими системами мониторига и сбора статистики."
|
||||
"title": "",
|
||||
"content": ""
|
||||
}
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": "",
|
||||
"title": ""
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"title": "",
|
||||
"rejoinKeyTitle": ""
|
||||
},
|
||||
"toolbar": {
|
||||
"mute": "Вкл. / Выкл. звук",
|
||||
"mute": "",
|
||||
"videomute": "",
|
||||
"authenticate": "Аутентифицировать",
|
||||
"authenticate": "",
|
||||
"lock": "",
|
||||
"invite": "",
|
||||
"chat": "",
|
||||
"etherpad": "",
|
||||
"sharedvideo": "Поделиться YouTube видео",
|
||||
"sharedvideo": "",
|
||||
"sharescreen": "",
|
||||
"fullscreen": "",
|
||||
"sip": "Набрать SIP номер",
|
||||
"Settings": "Настройки",
|
||||
"sip": "",
|
||||
"Settings": "",
|
||||
"hangup": "",
|
||||
"login": "Войти",
|
||||
"logout": "Завершить сеанс",
|
||||
"login": "",
|
||||
"logout": "",
|
||||
"dialpad": "",
|
||||
"sharedVideoMutedPopup": "У видео, которым Вы поделились, отключён звук, чтобы вы могли говорить с остальными.",
|
||||
"micMutedPopup": "Ваш микрофон отключён, чтобы вы могли сосредоточиться на видео, которым поделились.",
|
||||
"talkWhileMutedPopup": "",
|
||||
"unableToUnmutePopup": "Вы не можете включить звук, потому что включено видео.",
|
||||
"cameraDisabled": "Камера недоступна",
|
||||
"micDisabled": "Микрофон недоступен",
|
||||
"filmstrip": "",
|
||||
"profile": "",
|
||||
"raiseHand": ""
|
||||
"sharedVideoMutedPopup": "",
|
||||
"micMutedPopup": "",
|
||||
"unableToUnmutePopup": "",
|
||||
"cameraDisabled": "",
|
||||
"micDisabled": ""
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Открыть / Закрыть чат",
|
||||
"chat": "",
|
||||
"filmstrip": "",
|
||||
"contactlist": ""
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "Введите имя в поле ниже",
|
||||
"popover": "Выберите имя"
|
||||
"title": "",
|
||||
"popover": ""
|
||||
},
|
||||
"messagebox": "Введите текст.."
|
||||
"messagebox": ""
|
||||
},
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
"update": "Обновить",
|
||||
"name": "Имя",
|
||||
"title": "",
|
||||
"update": "",
|
||||
"name": "",
|
||||
"startAudioMuted": "",
|
||||
"startVideoMuted": "",
|
||||
"selectCamera": "",
|
||||
"selectMic": "",
|
||||
"selectAudioOutput": "",
|
||||
"followMe": "",
|
||||
"noDevice": "Нет",
|
||||
"noPermission": "Нет прав пользоваться устройством",
|
||||
"cameraAndMic": "",
|
||||
"moderator": "",
|
||||
"password": "",
|
||||
"audioVideo": "",
|
||||
"setPasswordLabel": ""
|
||||
},
|
||||
"profile": {
|
||||
"title": "",
|
||||
"setDisplayNameLabel": "",
|
||||
"setEmailLabel": "",
|
||||
"setEmailInput": ""
|
||||
"noDevice": "",
|
||||
"noPermission": "",
|
||||
"avatarUrl": ""
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "Нажми, чтобы<br/>поменять имя экрана",
|
||||
"moderator": "Хозяйка конференции.",
|
||||
"editnickname": "",
|
||||
"moderator": "",
|
||||
"videomute": "",
|
||||
"mute": "Без звука",
|
||||
"kick": "Прогнать",
|
||||
"muted": "Звук выключен",
|
||||
"domute": "Выключить звук",
|
||||
"flip": "Отразить"
|
||||
"mute": "",
|
||||
"kick": "",
|
||||
"muted": "",
|
||||
"domute": "",
|
||||
"flip": ""
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "",
|
||||
"bitrate": "Битрейт",
|
||||
"packetloss": "Потеря пакетов:",
|
||||
"resolution": "Разрешение:",
|
||||
"less": "Свернуть",
|
||||
"more": "Показать больше",
|
||||
"address": "Адрес:",
|
||||
"remoteport": "Удалённый порт:",
|
||||
"bitrate": "",
|
||||
"packetloss": "",
|
||||
"resolution": "",
|
||||
"less": "",
|
||||
"more": "",
|
||||
"address": "",
|
||||
"remoteport": "",
|
||||
"remoteport_plural_2": "",
|
||||
"remoteport_plural_5": "",
|
||||
"localport": "Локальный порт:",
|
||||
"localport_plural_2": "Локальные порты:",
|
||||
"localport_plural_5": "",
|
||||
"localaddress": "Локальный адрес:",
|
||||
"localaddress_plural_2": "Локальные адреса:",
|
||||
"localaddress_plural_5": "",
|
||||
"remoteaddress": "Удалённый адрес:",
|
||||
"localport": "",
|
||||
"localport_plural_2": "",
|
||||
"localaddress": "",
|
||||
"localaddress_plural_2": "",
|
||||
"remoteaddress": "",
|
||||
"remoteaddress_plural_2": "",
|
||||
"remoteaddress_plural_5": "",
|
||||
"transport": "Метод отправки:",
|
||||
"bandwidth": "Средняя скорость соединения:",
|
||||
"na": "Вернитесь сюда за информацией о соединении, когда конференция начнётся"
|
||||
"transport": "",
|
||||
"bandwidth": "",
|
||||
"na": ""
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "соединение разорвано",
|
||||
"moderator": "Получены права для модерации!",
|
||||
"connected": "подключено",
|
||||
"somebody": "Кто-то",
|
||||
"me": "Я",
|
||||
"focus": "Фокусировка конференции",
|
||||
"focusFail": "__component__ недоступен - повторите через __ms__ секунд",
|
||||
"grantedTo": "Теперь модерирует __to__!",
|
||||
"grantedToUnknown": "Права модератора теперь у $t(somebody)!",
|
||||
"muted": "Вы начали конференцию без звука.",
|
||||
"mutedTitle": "Вы без звука!",
|
||||
"raisedHand": "Хочу высказаться."
|
||||
"disconnected": "",
|
||||
"moderator": "",
|
||||
"connected": "",
|
||||
"somebody": "",
|
||||
"me": "",
|
||||
"focus": "",
|
||||
"focusFail": "",
|
||||
"grantedTo": "",
|
||||
"grantedToUnknown": "",
|
||||
"muted": "",
|
||||
"mutedTitle": "",
|
||||
"raisedHand": ""
|
||||
},
|
||||
"dialog": {
|
||||
"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": "",
|
||||
"maxUsersLimitReached": "Достигнут максимум количества участников конференции. Конференция заполнена. Пожалуйста попробуйте позже!",
|
||||
"lockTitle": "Блокировка не удалась",
|
||||
"lockMessage": "Не удалось запереть конференцию",
|
||||
"warning": "Внимание",
|
||||
"passwordNotSupported": "Пароли для комнат сейчас не поддерживаются.",
|
||||
"internalErrorTitle": "",
|
||||
"kickMessage": "",
|
||||
"popupError": "",
|
||||
"passwordError": "",
|
||||
"passwordError2": "",
|
||||
"connectError": "",
|
||||
"connectErrorWithMsg": "",
|
||||
"connecting": "",
|
||||
"error": "",
|
||||
"detectext": "",
|
||||
"failtoinstall": "",
|
||||
"failedpermissions": "",
|
||||
"bridgeUnavailable": "",
|
||||
"jicofoUnavailable": "",
|
||||
"maxUsersLimitReached": "",
|
||||
"lockTitle": "",
|
||||
"lockMessage": "",
|
||||
"warning": "",
|
||||
"passwordNotSupported": "",
|
||||
"sorry": "",
|
||||
"internalError": "",
|
||||
"unableToSwitch": "Невозможно сменить видео трансляцию.",
|
||||
"SLDFailure": "Ёпрст! Что-то пошло не так и мы не можем отключить звук! (ошибка SLD)",
|
||||
"SRDFailure": "Ёпрст! Что-то пошло не так и мы не можем остановить видео! (ошибка SRD)",
|
||||
"oops": "Ёпрст!",
|
||||
"currentPassword": "",
|
||||
"passwordLabel": "",
|
||||
"defaultError": "Какая-то ошибка",
|
||||
"passwordRequired": "Требуется пароль",
|
||||
"Ok": "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": "Введите пароль для вашей комнаты",
|
||||
"unableToSwitch": "",
|
||||
"SLDFailure": "",
|
||||
"SRDFailure": "",
|
||||
"oops": "",
|
||||
"defaultError": "",
|
||||
"passwordRequired": "",
|
||||
"Ok": "",
|
||||
"Remove": "",
|
||||
"shareVideoTitle": "",
|
||||
"shareVideoLinkError": "",
|
||||
"removeSharedVideoTitle": "",
|
||||
"removeSharedVideoMsg": "",
|
||||
"alreadySharedVideoMsg": "",
|
||||
"WaitingForHost": "",
|
||||
"WaitForHostMsg": "",
|
||||
"IamHost": "",
|
||||
"Cancel": "",
|
||||
"retry": "",
|
||||
"logoutTitle": "",
|
||||
"logoutQuestion": "",
|
||||
"sessTerminated": "",
|
||||
"hungUp": "",
|
||||
"joinAgain": "",
|
||||
"Share": "",
|
||||
"Save": "",
|
||||
"recording": "",
|
||||
"recordingToken": "",
|
||||
"Dial": "",
|
||||
"sipMsg": "",
|
||||
"passwordCheck": "",
|
||||
"passwordMsg": "",
|
||||
"Invite": "",
|
||||
"shareLink": "",
|
||||
"settings1": "Настройка Вашей конференции",
|
||||
"settings2": "Участница подключилась без звука",
|
||||
"settings3": "Нужны имена<br/><br/>Установите пароль, чтобы запереть Вашу комнату:",
|
||||
"settings1": "",
|
||||
"settings2": "",
|
||||
"settings3": "",
|
||||
"yourPassword": "",
|
||||
"Back": "Назад",
|
||||
"serviceUnavailable": "Служба недоступна",
|
||||
"gracefulShutdown": "Сервис закрыт на переучёт. Пожалуйста попробуйте позже.",
|
||||
"Yes": "Да",
|
||||
"reservationError": "Ошибка системы резервации",
|
||||
"reservationErrorMsg": "Код ошибки: __code__, сообщение: __msg__",
|
||||
"Back": "",
|
||||
"serviceUnavailable": "",
|
||||
"gracefulShutdown": "",
|
||||
"Yes": "",
|
||||
"reservationError": "",
|
||||
"reservationErrorMsg": "",
|
||||
"password": "",
|
||||
"userPassword": "пароль пользователя",
|
||||
"token": "токен",
|
||||
"tokenAuthFailedTitle": "",
|
||||
"userPassword": "",
|
||||
"token": "",
|
||||
"tokenAuthFailed": "",
|
||||
"displayNameRequired": "",
|
||||
"enterDisplayName": "Пожалуйста, введите Ваше имя экрана",
|
||||
"extensionRequired": "Требуется расширение:",
|
||||
"firefoxExtensionPrompt": "Нужно установить расширение Firefox, чтобы совместно пользоваться экраном. Попробуйте позже, скачав его <a href='__url__'>отсюда</a>!",
|
||||
"rateExperience": "",
|
||||
"feedbackHelp": "",
|
||||
"extensionRequired": "",
|
||||
"firefoxExtensionPrompt": "",
|
||||
"feedbackQuestion": "",
|
||||
"thankYou": "Спасибо за использование __appName__!",
|
||||
"sorryFeedback": "Мы удручены услышанным. Может расскажете поподробнее?",
|
||||
"liveStreaming": "Трансляция",
|
||||
"streamKey": "Имя/ключ трансляции",
|
||||
"startLiveStreaming": "Начать трансляцию",
|
||||
"stopStreamingWarning": "Вы уверены, что хотите остановить трансляцию?",
|
||||
"stopRecordingWarning": "Вы уверены, что хотите остановить запись?",
|
||||
"stopLiveStreaming": "Остановить трансляцию",
|
||||
"stopRecording": "Остановить запись",
|
||||
"doNotShowWarningAgain": "Больше не показывать это предупреждение",
|
||||
"doNotShowMessageAgain": "",
|
||||
"permissionDenied": "Доступ запрещён",
|
||||
"screenSharingPermissionDeniedError": "У Вас нет прав совместно использовать Ваш экран",
|
||||
"micErrorPresent": "Произошла ошибка при подключении к Вашему микрофону",
|
||||
"cameraErrorPresent": "Произошла ошибка при подключении к Вашей камере",
|
||||
"cameraUnsupportedResolutionError": "Ваша камера не поддерживает необходимое разрешение.",
|
||||
"cameraUnknownError": "Не могу использовать камеру по неизвестной причине.",
|
||||
"cameraPermissionDeniedError": "У вас нет прав на использование камеры. Вы можете участвовать в конференции, но другие не будут Вас видеть. Используйте значок с камерой в строке адреса, чтобы устранить проблему.",
|
||||
"thankYou": "",
|
||||
"sorryFeedback": "",
|
||||
"liveStreaming": "",
|
||||
"streamKey": "",
|
||||
"startLiveStreaming": "",
|
||||
"stopStreamingWarning": "",
|
||||
"stopRecordingWarning": "",
|
||||
"stopLiveStreaming": "",
|
||||
"stopRecording": "",
|
||||
"doNotShowWarningAgain": "",
|
||||
"permissionDenied": "",
|
||||
"screenSharingPermissionDeniedError": "",
|
||||
"micErrorPresent": "",
|
||||
"cameraErrorPresent": "",
|
||||
"cameraUnsupportedResolutionError": "",
|
||||
"cameraUnknownError": "",
|
||||
"cameraPermissionDeniedError": "",
|
||||
"cameraNotFoundError": "",
|
||||
"cameraConstraintFailedError": "Ваша камера не отвечает некоторым требованиям.",
|
||||
"micUnknownError": "Не могу пользоваться микрофоном по непонятным причинам.",
|
||||
"micPermissionDeniedError": "Вы не дали прав на использование микрофона. Вы все-равно можете присоединиться к конференции, но никто не будет Вас слышать. Используйте иконку с камерой в адресной строке браузера, чтобы исправить это.",
|
||||
"cameraConstraintFailedError": "",
|
||||
"micUnknownError": "",
|
||||
"micPermissionDeniedError": "",
|
||||
"micNotFoundError": "",
|
||||
"micConstraintFailedError": "Ваш микрофон не отвечает некоторым необходимым требованиям.",
|
||||
"micNotSendingData": "",
|
||||
"cameraNotSendingData": "",
|
||||
"goToStore": "",
|
||||
"externalInstallationTitle": "",
|
||||
"externalInstallationMsg": "",
|
||||
"muteParticipantTitle": "",
|
||||
"muteParticipantBody": "",
|
||||
"muteParticipantButton": "Выключить звук"
|
||||
"micConstraintFailedError": ""
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
"Эта конференция защищена паролем. Пожалуйста, используйте это пин для входа:",
|
||||
"",
|
||||
"",
|
||||
"__sharedKey__",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"subject": "Приглашение для __appName__ (__conferenceName__)",
|
||||
"body": [
|
||||
"Привет! я бы хотел пригласить тебя на __appName__ конференцию, которую мы как раз начали.",
|
||||
"",
|
||||
"",
|
||||
"Пожелуста, следуй по ссылке, чтобы подключиться к конференции.",
|
||||
"",
|
||||
"",
|
||||
"__roomUrl__",
|
||||
"",
|
||||
"",
|
||||
"__sharedKeyText__",
|
||||
"Имей в виду, что __appName__ сейчас поддерживается только __supportedBrowsers__, так что полюзуйся одним из этих браузеров.",
|
||||
"",
|
||||
"",
|
||||
"Услышимся через секунду!"
|
||||
],
|
||||
"and": "и"
|
||||
"sharedKey": "",
|
||||
"subject": "",
|
||||
"body": "",
|
||||
"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": "Ошибка при начале записи",
|
||||
"pending": "",
|
||||
"on": "",
|
||||
"off": "",
|
||||
"failedToStart": "",
|
||||
"buttonTooltip": "",
|
||||
"error": "Ошибка записи. Попробуйте позже.",
|
||||
"unavailable": "Сервис записи сейчас недоступен. Попробуйте позже."
|
||||
"error": "",
|
||||
"unavailable": ""
|
||||
},
|
||||
"liveStreaming": {
|
||||
"pending": "Начинаю трансляцию...",
|
||||
"on": "Трансляция",
|
||||
"off": "Трансляция остановлена",
|
||||
"unavailable": "Служба трансляций сейчас недоступна. Попробуйте позже.",
|
||||
"failedToStart": "Трансляция видео не может быть начата",
|
||||
"pending": "",
|
||||
"on": "",
|
||||
"off": "",
|
||||
"unavailable": "",
|
||||
"failedToStart": "",
|
||||
"buttonTooltip": "",
|
||||
"streamIdRequired": "Пожалуйста введите идентификатор трансляции, чтобы запустить её.",
|
||||
"error": "Не удалось начать трансляцию. Попробуйте снова.",
|
||||
"busy": "Все рекордеры сейчас заняты. Попробуйте позже."
|
||||
"streamIdRequired": "",
|
||||
"error": "",
|
||||
"busy": ""
|
||||
}
|
||||
}
|
||||
@@ -215,9 +215,6 @@
|
||||
"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.",
|
||||
"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",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
// Logging configuration
|
||||
var loggingConfig = { // eslint-disable-line no-unused-vars
|
||||
//default log level for the app and lib-jitsi-meet
|
||||
defaultLogLevel: 'trace',
|
||||
// Option to disable LogCollector (which stores the logs on CallStats)
|
||||
//disableLogCollector: true,
|
||||
// Logging level adjustments for verbose modules:
|
||||
'modules/xmpp/strophe.util.js': 'log'
|
||||
};
|
||||
@@ -54,12 +54,10 @@ function initCommands() {
|
||||
"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
|
||||
"video-hangup": () => APP.conference.hangup()
|
||||
};
|
||||
Object.keys(commands).forEach(function (key) {
|
||||
postis.listen(key, args => commands[key](...args));
|
||||
postis.listen(key, commands[key]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
12
modules/API/external/external_api.js
vendored
12
modules/API/external/external_api.js
vendored
@@ -36,9 +36,7 @@ var commands = {
|
||||
"toggleChat": "toggle-chat",
|
||||
"toggleContactList": "toggle-contact-list",
|
||||
"toggleShareScreen": "toggle-share-screen",
|
||||
"hangup": "video-hangup",
|
||||
"email": "email",
|
||||
"avatarUrl": "avatar-url"
|
||||
"hangup": "video-hangup"
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -176,13 +174,15 @@ function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
|
||||
* @param name the name of the command
|
||||
* @param arguments array of arguments
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.executeCommand
|
||||
= function(name, ...argumentsList) {
|
||||
JitsiMeetExternalAPI.prototype.executeCommand = function(name, argumentsList) {
|
||||
if(!(name in commands)) {
|
||||
logger.error("Not supported command name.");
|
||||
return;
|
||||
}
|
||||
sendMessage(this.postis, {method: commands[name], params: argumentsList});
|
||||
var argumentsArray = argumentsList;
|
||||
if (!argumentsArray)
|
||||
argumentsArray = [];
|
||||
sendMessage(this.postis, {method: commands[name], params: argumentsArray});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/* global APP, JitsiMeetJS, $, config, interfaceConfig, toastr */
|
||||
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
var UI = {};
|
||||
@@ -18,7 +17,7 @@ import Recording from "./recording/Recording";
|
||||
import GumPermissionsOverlay
|
||||
from './gum_overlay/UserMediaPermissionsGuidanceOverlay';
|
||||
|
||||
import * as PageReloadOverlay from './reload_overlay/PageReloadOverlay';
|
||||
import PageReloadOverlay from './reload_overlay/PageReloadOverlay';
|
||||
import SuspendedOverlay from './suspended_overlay/SuspendedOverlay';
|
||||
import VideoLayout from "./videolayout/VideoLayout";
|
||||
import FilmStrip from "./videolayout/FilmStrip";
|
||||
@@ -26,7 +25,7 @@ import SettingsMenu from "./side_pannels/settings/SettingsMenu";
|
||||
import Profile from "./side_pannels/profile/Profile";
|
||||
import Settings from "./../settings/Settings";
|
||||
import RingOverlay from "./ring_overlay/RingOverlay";
|
||||
import { randomInt } from "../../react/features/base/util/randomUtil";
|
||||
import RandomUtil from "../util/RandomUtil";
|
||||
import UIErrors from './UIErrors';
|
||||
import { debounce } from "../util/helpers";
|
||||
|
||||
@@ -397,6 +396,20 @@ UI.getSharedVideoManager = function () {
|
||||
*/
|
||||
UI.start = function () {
|
||||
document.title = interfaceConfig.APP_NAME;
|
||||
var setupWelcomePage = null;
|
||||
if(config.enableWelcomePage && window.location.pathname == "/" &&
|
||||
Settings.isWelcomePageEnabled()) {
|
||||
$("#videoconference_page").hide();
|
||||
if (!setupWelcomePage)
|
||||
setupWelcomePage = require("./welcome_page/WelcomePage");
|
||||
setupWelcomePage();
|
||||
|
||||
// Return false to indicate that the UI hasn't been fully started and
|
||||
// conference ready. We're still waiting for input from the user.
|
||||
return false;
|
||||
}
|
||||
|
||||
$("#welcome_page").hide();
|
||||
|
||||
// Set the defaults for prompt dialogs.
|
||||
$.prompt.setDefaults({persistent: false});
|
||||
@@ -1087,7 +1100,8 @@ UI.notifyFocusDisconnected = function (focus, retrySec) {
|
||||
*/
|
||||
UI.showPageReloadOverlay = function (isNetworkFailure, reason) {
|
||||
// Reload the page after 10 - 30 seconds
|
||||
PageReloadOverlay.show(10 + randomInt(0, 20), isNetworkFailure, reason);
|
||||
PageReloadOverlay.show(
|
||||
10 + RandomUtil.randomInt(0, 20), isNetworkFailure, reason);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1386,13 +1400,13 @@ UI.setMicrophoneButtonEnabled = function (enabled) {
|
||||
|
||||
UI.showRingOverlay = function () {
|
||||
RingOverlay.show(APP.tokenData.callee, interfaceConfig.DISABLE_RINGING);
|
||||
FilmStrip.toggleFilmStrip(false, false);
|
||||
FilmStrip.toggleFilmStrip(false);
|
||||
};
|
||||
|
||||
UI.hideRingOverLay = function () {
|
||||
if (!RingOverlay.hide())
|
||||
return;
|
||||
FilmStrip.toggleFilmStrip(true, false);
|
||||
FilmStrip.toggleFilmStrip(true);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,38 +15,26 @@ export default class Overlay{
|
||||
* @type {jQuery}
|
||||
*/
|
||||
this.$overlay = null;
|
||||
|
||||
/**
|
||||
* Indicates if this overlay should use the light look & feel or the
|
||||
* standard one.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isLightOverlay = false;
|
||||
}
|
||||
/**
|
||||
* Template method which should be used by subclasses to provide the overlay
|
||||
* content. The contents provided by this method are later subject to
|
||||
* the translation using {@link APP.translation.translateElement}.
|
||||
* @return {string} HTML representation of the overlay dialog contents.
|
||||
* @protected
|
||||
* @private
|
||||
*/
|
||||
_buildOverlayContent() {
|
||||
return '';
|
||||
}
|
||||
/**
|
||||
* Constructs the HTML body of the overlay dialog.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_buildOverlayHtml() {
|
||||
buildOverlayHtml() {
|
||||
|
||||
let overlayContent = this._buildOverlayContent();
|
||||
|
||||
let containerClass = this.isLightOverlay ? "overlay__container-light"
|
||||
: "overlay__container";
|
||||
|
||||
this.$overlay = $(`
|
||||
<div class=${containerClass}>
|
||||
<div class='overlay__container'>
|
||||
<div class='overlay__content'>
|
||||
${overlayContent}
|
||||
</div>
|
||||
@@ -65,18 +53,18 @@ export default class Overlay{
|
||||
/**
|
||||
* Template method called just after the overlay is displayed for the first
|
||||
* time.
|
||||
* @protected
|
||||
* @private
|
||||
*/
|
||||
_onShow() {
|
||||
// To be overridden by subclasses.
|
||||
}
|
||||
/**
|
||||
* Shows the overlay dialog and attaches the underlying HTML representation
|
||||
* Shows the overlay dialog adn attaches the underlying HTML representation
|
||||
* to the DOM.
|
||||
*/
|
||||
show() {
|
||||
|
||||
!this.$overlay && this._buildOverlayHtml();
|
||||
!this.$overlay && this.buildOverlayHtml();
|
||||
|
||||
if (!this.isVisible()) {
|
||||
this.$overlay.appendTo('body');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* global $, APP, AJS */
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
import Overlay from "../overlay/Overlay";
|
||||
import Overlay from '../overlay/Overlay';
|
||||
|
||||
/**
|
||||
* An overlay dialog which is shown before the conference is reloaded. Shows
|
||||
@@ -12,14 +12,8 @@ class PageReloadOverlayImpl extends Overlay{
|
||||
* Creates new <tt>PageReloadOverlayImpl</tt>
|
||||
* @param {number} timeoutSeconds how long the overlay dialog will be
|
||||
* displayed, before the conference will be reloaded.
|
||||
* @param {string} title the title of the overlay message
|
||||
* @param {string} message the message of the overlay
|
||||
* @param {string} buttonHtml the button html or an empty string if there's
|
||||
* no button
|
||||
* @param {boolean} isLightOverlay indicates if the overlay should be a
|
||||
* light overlay or a standard one
|
||||
*/
|
||||
constructor(timeoutSeconds, title, message, buttonHtml, isLightOverlay) {
|
||||
constructor(timeoutSeconds) {
|
||||
super();
|
||||
/**
|
||||
* Conference reload counter in seconds.
|
||||
@@ -31,11 +25,6 @@ class PageReloadOverlayImpl extends Overlay{
|
||||
* @type {number}
|
||||
*/
|
||||
this.timeout = timeoutSeconds;
|
||||
|
||||
this.title = title;
|
||||
this.message = message;
|
||||
this.buttonHtml = buttonHtml;
|
||||
this.isLightOverlay = isLightOverlay;
|
||||
}
|
||||
/**
|
||||
* Constructs overlay body with the warning message and count down towards
|
||||
@@ -44,9 +33,9 @@ class PageReloadOverlayImpl extends Overlay{
|
||||
*/
|
||||
_buildOverlayContent() {
|
||||
return `<div class="inlay">
|
||||
<span data-i18n=${this.title}
|
||||
<span data-i18n='dialog.conferenceReloadTitle'
|
||||
class='reload_overlay_title'></span>
|
||||
<span data-i18n=${this.message}
|
||||
<span data-i18n='dialog.conferenceReloadMsg'
|
||||
class='reload_overlay_msg'></span>
|
||||
<div>
|
||||
<div id='reloadProgressBar'
|
||||
@@ -58,7 +47,6 @@ class PageReloadOverlayImpl extends Overlay{
|
||||
class='reload_overlay_msg'>
|
||||
</span>
|
||||
</div>
|
||||
${this.buttonHtml}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -79,9 +67,6 @@ class PageReloadOverlayImpl extends Overlay{
|
||||
* @override
|
||||
*/
|
||||
_onShow() {
|
||||
$("#reconnectNow").click(() => {
|
||||
APP.ConferenceUrl.reload();
|
||||
});
|
||||
|
||||
// Initialize displays
|
||||
this.updateDisplay();
|
||||
@@ -113,63 +98,40 @@ class PageReloadOverlayImpl extends Overlay{
|
||||
*/
|
||||
let overlay;
|
||||
|
||||
/**
|
||||
* Checks whether the page reload overlay has been displayed.
|
||||
* @return {boolean} <tt>true</tt> if the page reload overlay is currently
|
||||
* visible or <tt>false</tt> otherwise.
|
||||
*/
|
||||
export function isVisible() {
|
||||
export default {
|
||||
/**
|
||||
* Checks whether the page reload overlay has been displayed.
|
||||
* @return {boolean} <tt>true</tt> if the page reload overlay is currently
|
||||
* visible or <tt>false</tt> otherwise.
|
||||
*/
|
||||
isVisible() {
|
||||
return overlay && overlay.isVisible();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Shows the page reload overlay which will do the conference reload after
|
||||
* the given amount of time.
|
||||
*
|
||||
* @param {number} timeoutSeconds how many seconds before the conference
|
||||
* reload will happen.
|
||||
* @param {boolean} isNetworkFailure <tt>true</tt> indicates that it's
|
||||
* caused by network related failure or <tt>false</tt> when it's
|
||||
* the infrastructure.
|
||||
* @param {string} reason a label string identifying the reason for the page
|
||||
* reload which will be included in details of the log event
|
||||
*/
|
||||
show(timeoutSeconds, isNetworkFailure, reason) {
|
||||
|
||||
/**
|
||||
* Shows the page reload overlay which will do the conference reload after
|
||||
* the given amount of time.
|
||||
*
|
||||
* @param {number} timeoutSeconds how many seconds before the conference
|
||||
* reload will happen.
|
||||
* @param {boolean} isNetworkFailure <tt>true</tt> indicates that it's
|
||||
* caused by network related failure or <tt>false</tt> when it's
|
||||
* the infrastructure.
|
||||
* @param {string} reason a label string identifying the reason for the page
|
||||
* reload which will be included in details of the log event
|
||||
*/
|
||||
export function show(timeoutSeconds, isNetworkFailure, reason) {
|
||||
let title;
|
||||
let message;
|
||||
let buttonHtml;
|
||||
let isLightOverlay;
|
||||
|
||||
if (isNetworkFailure) {
|
||||
title = "dialog.conferenceDisconnectTitle";
|
||||
message = "dialog.conferenceDisconnectMsg";
|
||||
buttonHtml
|
||||
= `<button id="reconnectNow" data-i18n="dialog.reconnectNow"
|
||||
class="button-control button-control_primary
|
||||
button-control_center"></button>`;
|
||||
isLightOverlay = true;
|
||||
if (!overlay) {
|
||||
overlay = new PageReloadOverlayImpl(timeoutSeconds);
|
||||
}
|
||||
// Log the page reload event
|
||||
if (!this.isVisible()) {
|
||||
// FIXME (CallStats - issue) this event will not make it to
|
||||
// the CallStats, because the log queue is not flushed, before
|
||||
// "fabric terminated" is sent to the backed
|
||||
APP.conference.logEvent(
|
||||
'page.reload', undefined /* value */, reason /* label */);
|
||||
}
|
||||
overlay.show();
|
||||
}
|
||||
else {
|
||||
title = "dialog.conferenceReloadTitle";
|
||||
message = "dialog.conferenceReloadMsg";
|
||||
buttonHtml = "";
|
||||
isLightOverlay = false;
|
||||
}
|
||||
|
||||
if (!overlay) {
|
||||
overlay = new PageReloadOverlayImpl(timeoutSeconds,
|
||||
title,
|
||||
message,
|
||||
buttonHtml,
|
||||
isLightOverlay);
|
||||
}
|
||||
// Log the page reload event
|
||||
if (!this.isVisible()) {
|
||||
// FIXME (CallStats - issue) this event will not make it to
|
||||
// the CallStats, because the log queue is not flushed, before
|
||||
// "fabric terminated" is sent to the backed
|
||||
APP.conference.logEvent(
|
||||
'page.reload', undefined /* value */, reason /* label */);
|
||||
}
|
||||
overlay.show();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -42,14 +42,13 @@ const FilmStrip = {
|
||||
let container = document.createElement('div');
|
||||
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="hideVideoToolbar">
|
||||
<i class="icon-menu-${isVisible ? 'down' : 'up'}">
|
||||
</i>
|
||||
</button>
|
||||
`;
|
||||
|
||||
return container;
|
||||
},
|
||||
@@ -95,10 +94,8 @@ const FilmStrip = {
|
||||
*/
|
||||
showMenuDownIcon() {
|
||||
let icon = this.toggleFilmStripIcon;
|
||||
if(icon) {
|
||||
icon.classList.add(this.iconMenuDownClassName);
|
||||
icon.classList.remove(this.iconMenuUpClassName);
|
||||
}
|
||||
icon.classList.add(this.iconMenuDownClassName);
|
||||
icon.classList.remove(this.iconMenuUpClassName);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -106,10 +103,8 @@ const FilmStrip = {
|
||||
*/
|
||||
showMenuUpIcon() {
|
||||
let icon = this.toggleFilmStripIcon;
|
||||
if(icon) {
|
||||
icon.classList.add(this.iconMenuUpClassName);
|
||||
icon.classList.remove(this.iconMenuDownClassName);
|
||||
}
|
||||
icon.classList.add(this.iconMenuUpClassName);
|
||||
icon.classList.remove(this.iconMenuDownClassName);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -119,34 +114,30 @@ const FilmStrip = {
|
||||
* of the film strip. 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
|
||||
* default value is true.
|
||||
*
|
||||
* Note:
|
||||
* This method shouldn't be executed directly to hide the filmstrip.
|
||||
* It's important to hide the filmstrip with UI.toggleFilmstrip in order
|
||||
* to correctly resize the video area.
|
||||
*/
|
||||
toggleFilmStrip(visible, sendAnalytics = true) {
|
||||
const isVisibleDefined = typeof visible === 'boolean';
|
||||
toggleFilmStrip(visible) {
|
||||
let isVisibleDefined = typeof visible === 'boolean';
|
||||
if (!isVisibleDefined) {
|
||||
visible = this.isFilmStripVisible();
|
||||
} else if (this.isFilmStripVisible() === visible) {
|
||||
return;
|
||||
}
|
||||
if (sendAnalytics) {
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.filmstrip.toggled');
|
||||
}
|
||||
JitsiMeetJS.analytics.sendEvent('toolbar.filmstrip.toggled');
|
||||
this.filmStrip.toggleClass("hidden");
|
||||
|
||||
if (visible) {
|
||||
this.showMenuUpIcon();
|
||||
} else {
|
||||
if (!visible) {
|
||||
this.showMenuDownIcon();
|
||||
} else {
|
||||
this.showMenuUpIcon();
|
||||
}
|
||||
|
||||
// Emit/fire UIEvents.TOGGLED_FILM_STRIP.
|
||||
const eventEmitter = this.eventEmitter;
|
||||
var eventEmitter = this.eventEmitter;
|
||||
if (eventEmitter) {
|
||||
eventEmitter.emit(
|
||||
UIEvents.TOGGLED_FILM_STRIP,
|
||||
@@ -381,7 +372,9 @@ const FilmStrip = {
|
||||
* @param forceUpdate
|
||||
* @returns {Promise}
|
||||
*/
|
||||
resizeThumbnails(local, remote, animate = false, forceUpdate = false) {
|
||||
resizeThumbnails(local, remote,
|
||||
animate = false, forceUpdate = false) {
|
||||
|
||||
return new Promise(resolve => {
|
||||
let thumbs = this.getThumbs(!forceUpdate);
|
||||
let promises = [];
|
||||
@@ -462,7 +455,9 @@ const FilmStrip = {
|
||||
} else {
|
||||
return { remoteThumbs, localThumb };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export default FilmStrip;
|
||||
|
||||
@@ -37,8 +37,7 @@ export default class LargeVideoManager {
|
||||
display: 'inline-block'
|
||||
});
|
||||
|
||||
if (interfaceConfig.SHOW_JITSI_WATERMARK
|
||||
&& !interfaceConfig.filmStripOnly) {
|
||||
if (interfaceConfig.SHOW_JITSI_WATERMARK) {
|
||||
let leftWatermarkDiv
|
||||
= this.$container.find("div.watermark.leftwatermark");
|
||||
|
||||
@@ -49,8 +48,7 @@ export default class LargeVideoManager {
|
||||
interfaceConfig.JITSI_WATERMARK_LINK);
|
||||
}
|
||||
|
||||
if (interfaceConfig.SHOW_BRAND_WATERMARK
|
||||
&& !interfaceConfig.filmStripOnly) {
|
||||
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
|
||||
let rightWatermarkDiv
|
||||
= this.$container.find("div.watermark.rightwatermark");
|
||||
|
||||
|
||||
@@ -302,23 +302,26 @@ RemoteVideo.prototype._figureOutMutedWhileDisconnected
|
||||
* @param id the id indicating the video for which we're adding a menu.
|
||||
* @param parentElement the parent element where this menu will be added
|
||||
*/
|
||||
RemoteVideo.prototype.addRemoteVideoMenu = function () {
|
||||
if (interfaceConfig.filmStripOnly) {
|
||||
return;
|
||||
}
|
||||
var spanElement = document.createElement('span');
|
||||
spanElement.className = 'remotevideomenu';
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
RemoteVideo.prototype.addRemoteVideoMenu = function () {
|
||||
|
||||
this.container.appendChild(spanElement);
|
||||
var spanElement = document.createElement('span');
|
||||
spanElement.className = 'remotevideomenu';
|
||||
|
||||
var menuElement = document.createElement('i');
|
||||
menuElement.className = 'icon-menu-up';
|
||||
menuElement.title = 'Remote user controls';
|
||||
spanElement.appendChild(menuElement);
|
||||
this.container.appendChild(spanElement);
|
||||
|
||||
this._initPopupMenu(this._generatePopupContent());
|
||||
this.hasRemoteVideoMenu = true;
|
||||
};
|
||||
var menuElement = document.createElement('i');
|
||||
menuElement.className = 'icon-menu';
|
||||
menuElement.title = 'Remote user controls';
|
||||
spanElement.appendChild(menuElement);
|
||||
|
||||
this._initPopupMenu(this._generatePopupContent());
|
||||
this.hasRemoteVideoMenu = true;
|
||||
};
|
||||
|
||||
} else {
|
||||
RemoteVideo.prototype.addRemoteVideoMenu = function() {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the remote stream element corresponding to the given stream and
|
||||
|
||||
@@ -636,7 +636,7 @@ var VideoLayout = {
|
||||
// Update the large video if the video source is already available,
|
||||
// otherwise wait for the "videoactive.jingle" event.
|
||||
// FIXME: there is no "videoactive.jingle" event.
|
||||
if (!interfaceConfig.filmStripOnly && !pinnedId
|
||||
if (!pinnedId
|
||||
&& remoteVideo.hasVideoStarted()
|
||||
&& !this.getCurrentlyOnLargeContainer().stayOnStage()) {
|
||||
this.updateLargeVideo(id);
|
||||
|
||||
97
modules/UI/welcome_page/WelcomePage.js
Normal file
97
modules/UI/welcome_page/WelcomePage.js
Normal file
@@ -0,0 +1,97 @@
|
||||
/* global $, interfaceConfig, APP */
|
||||
var animateTimeout, updateTimeout;
|
||||
|
||||
var RoomnameGenerator = require("../../util/RoomnameGenerator");
|
||||
import UIUtil from "../util/UIUtil";
|
||||
|
||||
function enter_room() {
|
||||
var val = $("#enter_room_field").val();
|
||||
if(!val) {
|
||||
val = $("#enter_room_field").attr("room_name");
|
||||
}
|
||||
if (val) {
|
||||
window.location.pathname = "/" + val;
|
||||
}
|
||||
}
|
||||
|
||||
function animate(word) {
|
||||
var currentVal = $("#enter_room_field").attr("placeholder");
|
||||
$("#enter_room_field").attr("placeholder", currentVal + word.substr(0, 1));
|
||||
animateTimeout = setTimeout(function() {
|
||||
animate(word.substring(1, word.length));
|
||||
}, 70);
|
||||
}
|
||||
|
||||
function update_roomname() {
|
||||
var word = RoomnameGenerator.generateRoomWithoutSeparator();
|
||||
$("#enter_room_field").attr("room_name", word);
|
||||
$("#enter_room_field").attr("placeholder", "");
|
||||
clearTimeout(animateTimeout);
|
||||
animate(word);
|
||||
updateTimeout = setTimeout(update_roomname, 10000);
|
||||
}
|
||||
|
||||
function setupWelcomePage() {
|
||||
$("#videoconference_page").hide();
|
||||
$("#domain_name").text(
|
||||
window.location.protocol + "//" + window.location.host + "/");
|
||||
if (interfaceConfig.SHOW_JITSI_WATERMARK) {
|
||||
var leftWatermarkDiv =
|
||||
$("#welcome_page_header div[class='watermark leftwatermark']");
|
||||
if(leftWatermarkDiv && leftWatermarkDiv.length > 0) {
|
||||
leftWatermarkDiv.css({display: 'block'});
|
||||
UIUtil.setLinkHref(
|
||||
leftWatermarkDiv.parent(),
|
||||
interfaceConfig.JITSI_WATERMARK_LINK);
|
||||
}
|
||||
}
|
||||
|
||||
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
|
||||
var rightWatermarkDiv =
|
||||
$("#welcome_page_header div[class='watermark rightwatermark']");
|
||||
if(rightWatermarkDiv && rightWatermarkDiv.length > 0) {
|
||||
rightWatermarkDiv.css({display: 'block'});
|
||||
UIUtil.setLinkHref(
|
||||
rightWatermarkDiv.parent(),
|
||||
interfaceConfig.BRAND_WATERMARK_LINK);
|
||||
rightWatermarkDiv.get(0).style.backgroundImage =
|
||||
"url(images/rightwatermark.png)";
|
||||
}
|
||||
}
|
||||
|
||||
if (interfaceConfig.SHOW_POWERED_BY) {
|
||||
$("#welcome_page_header>a[class='poweredby']")
|
||||
.css({display: 'block'});
|
||||
}
|
||||
|
||||
$("#enter_room_button").click(function() {
|
||||
enter_room();
|
||||
});
|
||||
|
||||
$("#enter_room_field").keydown(function (event) {
|
||||
if (event.keyCode === 13 /* enter */) {
|
||||
enter_room();
|
||||
}
|
||||
});
|
||||
|
||||
if (interfaceConfig.GENERATE_ROOMNAMES_ON_WELCOME_PAGE !== false) {
|
||||
var selector = $("#reload_roomname");
|
||||
selector.click(function () {
|
||||
clearTimeout(updateTimeout);
|
||||
clearTimeout(animateTimeout);
|
||||
update_roomname();
|
||||
});
|
||||
selector.show();
|
||||
|
||||
update_roomname();
|
||||
}
|
||||
|
||||
$("#disable_welcome").click(function () {
|
||||
APP.settings.setWelcomePageEnabled(
|
||||
!$("#disable_welcome").is(":checked")
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
module.exports = setupWelcomePage;
|
||||
73
modules/util/RandomUtil.js
Normal file
73
modules/util/RandomUtil.js
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @const
|
||||
*/
|
||||
var ALPHANUM = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
/**
|
||||
* Hexadecimal digits.
|
||||
* @const
|
||||
*/
|
||||
var HEX_DIGITS = '0123456789abcdef';
|
||||
|
||||
/**
|
||||
* Generates random int within the range [min, max]
|
||||
* @param min the minimum value for the generated number
|
||||
* @param max the maximum value for the generated number
|
||||
* @returns random int number
|
||||
*/
|
||||
function randomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get random element from array or string.
|
||||
* @param {Array|string} arr source
|
||||
* @returns array element or string character
|
||||
*/
|
||||
function randomElement(arr) {
|
||||
return arr[randomInt(0, arr.length -1)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random alphanumeric string.
|
||||
* @param {number} length expected string length
|
||||
* @returns {string} random string of specified length
|
||||
*/
|
||||
function randomAlphanumStr(length) {
|
||||
var result = '';
|
||||
|
||||
for (var i = 0; i < length; i += 1) {
|
||||
result += randomElement(ALPHANUM);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exported interface.
|
||||
*/
|
||||
var RandomUtil = {
|
||||
/**
|
||||
* Returns a random hex digit.
|
||||
* @returns {*}
|
||||
*/
|
||||
randomHexDigit: function() {
|
||||
return randomElement(HEX_DIGITS);
|
||||
},
|
||||
/**
|
||||
* Returns a random string of hex digits with length 'len'.
|
||||
* @param len the length.
|
||||
*/
|
||||
randomHexString: function (len) {
|
||||
var ret = '';
|
||||
while (len--) {
|
||||
ret += this.randomHexDigit();
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
randomElement: randomElement,
|
||||
randomAlphanumStr: randomAlphanumStr,
|
||||
randomInt: randomInt
|
||||
};
|
||||
|
||||
module.exports = RandomUtil;
|
||||
198
modules/util/RoomnameGenerator.js
Normal file
198
modules/util/RoomnameGenerator.js
Normal file
@@ -0,0 +1,198 @@
|
||||
var RandomUtil = require('./RandomUtil');
|
||||
//var nouns = [
|
||||
//];
|
||||
var pluralNouns = [
|
||||
"Aliens", "Animals", "Antelopes", "Ants", "Apes", "Apples", "Baboons",
|
||||
"Bacteria", "Badgers", "Bananas", "Bats", "Bears", "Birds", "Bonobos",
|
||||
"Brides", "Bugs", "Bulls", "Butterflies", "Cheetahs", "Cherries", "Chicken",
|
||||
"Children", "Chimps", "Clowns", "Cows", "Creatures", "Dinosaurs", "Dogs",
|
||||
"Dolphins", "Donkeys", "Dragons", "Ducks", "Dwarfs", "Eagles", "Elephants",
|
||||
"Elves", "Fathers", "Fish", "Flowers", "Frogs", "Fruit", "Fungi",
|
||||
"Galaxies", "Geese", "Goats", "Gorillas", "Hedgehogs", "Hippos", "Horses",
|
||||
"Hunters", "Insects", "Kids", "Knights", "Lemons", "Lemurs", "Leopards",
|
||||
"LifeForms", "Lions", "Lizards", "Mice", "Monkeys", "Monsters", "Mushrooms",
|
||||
"Octopodes", "Oranges", "Orangutans", "Organisms", "Pants", "Parrots",
|
||||
"Penguins", "People", "Pigeons", "Pigs", "Pineapples", "Plants", "Potatoes",
|
||||
"Priests", "Rats", "Reptiles", "Reptilians", "Rhinos", "Seagulls", "Sheep",
|
||||
"Siblings", "Snakes", "Spaghetti", "Spiders", "Squid", "Squirrels",
|
||||
"Stars", "Students", "Teachers", "Tigers", "Tomatoes", "Trees", "Vampires",
|
||||
"Vegetables", "Viruses", "Vulcans", "Weasels", "Werewolves", "Whales",
|
||||
"Witches", "Wizards", "Wolves", "Workers", "Worms", "Zebras"
|
||||
];
|
||||
//var places = [
|
||||
// "Pub", "University", "Airport", "Library", "Mall", "Theater", "Stadium",
|
||||
// "Office", "Show", "Gallows", "Beach", "Cemetery", "Hospital", "Reception",
|
||||
// "Restaurant", "Bar", "Church", "House", "School", "Square", "Village",
|
||||
// "Cinema", "Movies", "Party", "Restroom", "End", "Jail", "PostOffice",
|
||||
// "Station", "Circus", "Gates", "Entrance", "Bridge"
|
||||
//];
|
||||
var verbs = [
|
||||
"Abandon", "Adapt", "Advertise", "Answer", "Anticipate", "Appreciate",
|
||||
"Approach", "Argue", "Ask", "Bite", "Blossom", "Blush", "Breathe", "Breed",
|
||||
"Bribe", "Burn", "Calculate", "Clean", "Code", "Communicate", "Compute",
|
||||
"Confess", "Confiscate", "Conjugate", "Conjure", "Consume", "Contemplate",
|
||||
"Crawl", "Dance", "Delegate", "Devour", "Develop", "Differ", "Discuss",
|
||||
"Dissolve", "Drink", "Eat", "Elaborate", "Emancipate", "Estimate", "Expire",
|
||||
"Extinguish", "Extract", "Facilitate", "Fall", "Feed", "Finish", "Floss",
|
||||
"Fly", "Follow", "Fragment", "Freeze", "Gather", "Glow", "Grow", "Hex",
|
||||
"Hide", "Hug", "Hurry", "Improve", "Intersect", "Investigate", "Jinx",
|
||||
"Joke", "Jubilate", "Kiss", "Laugh", "Manage", "Meet", "Merge", "Move",
|
||||
"Object", "Observe", "Offer", "Paint", "Participate", "Party", "Perform",
|
||||
"Plan", "Pursue", "Pierce", "Play", "Postpone", "Pray", "Proclaim",
|
||||
"Question", "Read", "Reckon", "Rejoice", "Represent", "Resize", "Rhyme",
|
||||
"Scream", "Search", "Select", "Share", "Shoot", "Shout", "Signal", "Sing",
|
||||
"Skate", "Sleep", "Smile", "Smoke", "Solve", "Spell", "Steer", "Stink",
|
||||
"Substitute", "Swim", "Taste", "Teach", "Terminate", "Think", "Type",
|
||||
"Unite", "Vanish", "Worship"
|
||||
];
|
||||
var adverbs = [
|
||||
"Absently", "Accurately", "Accusingly", "Adorably", "AllTheTime", "Alone",
|
||||
"Always", "Amazingly", "Angrily", "Anxiously", "Anywhere", "Appallingly",
|
||||
"Apparently", "Articulately", "Astonishingly", "Badly", "Barely",
|
||||
"Beautifully", "Blindly", "Bravely", "Brightly", "Briskly", "Brutally",
|
||||
"Calmly", "Carefully", "Casually", "Cautiously", "Cleverly", "Constantly",
|
||||
"Correctly", "Crazily", "Curiously", "Cynically", "Daily", "Dangerously",
|
||||
"Deliberately", "Delicately", "Desperately", "Discreetly", "Eagerly",
|
||||
"Easily", "Euphoricly", "Evenly", "Everywhere", "Exactly", "Expectantly",
|
||||
"Extensively", "Ferociously", "Fiercely", "Finely", "Flatly", "Frequently",
|
||||
"Frighteningly", "Gently", "Gloriously", "Grimly", "Guiltily", "Happily",
|
||||
"Hard", "Hastily", "Heroically", "High", "Highly", "Hourly", "Humbly",
|
||||
"Hysterically", "Immensely", "Impartially", "Impolitely", "Indifferently",
|
||||
"Intensely", "Jealously", "Jovially", "Kindly", "Lazily", "Lightly",
|
||||
"Loudly", "Lovingly", "Loyally", "Magnificently", "Malevolently", "Merrily",
|
||||
"Mightily", "Miserably", "Mysteriously", "NOT", "Nervously", "Nicely",
|
||||
"Nowhere", "Objectively", "Obnoxiously", "Obsessively", "Obviously",
|
||||
"Often", "Painfully", "Patiently", "Playfully", "Politely", "Poorly",
|
||||
"Precisely", "Promptly", "Quickly", "Quietly", "Randomly", "Rapidly",
|
||||
"Rarely", "Recklessly", "Regularly", "Remorsefully", "Responsibly",
|
||||
"Rudely", "Ruthlessly", "Sadly", "Scornfully", "Seamlessly", "Seldom",
|
||||
"Selfishly", "Seriously", "Shakily", "Sharply", "Sideways", "Silently",
|
||||
"Sleepily", "Slightly", "Slowly", "Slyly", "Smoothly", "Softly", "Solemnly",
|
||||
"Steadily", "Sternly", "Strangely", "Strongly", "Stunningly", "Surely",
|
||||
"Tenderly", "Thoughtfully", "Tightly", "Uneasily", "Vanishingly",
|
||||
"Violently", "Warmly", "Weakly", "Wearily", "Weekly", "Weirdly", "Well",
|
||||
"Well", "Wickedly", "Wildly", "Wisely", "Wonderfully", "Yearly"
|
||||
];
|
||||
var adjectives = [
|
||||
"Abominable", "Accurate", "Adorable", "All", "Alleged", "Ancient", "Angry",
|
||||
"Anxious", "Appalling", "Apparent", "Astonishing", "Attractive", "Awesome",
|
||||
"Baby", "Bad", "Beautiful", "Benign", "Big", "Bitter", "Blind", "Blue",
|
||||
"Bold", "Brave", "Bright", "Brisk", "Calm", "Camouflaged", "Casual",
|
||||
"Cautious", "Choppy", "Chosen", "Clever", "Cold", "Cool", "Crawly",
|
||||
"Crazy", "Creepy", "Cruel", "Curious", "Cynical", "Dangerous", "Dark",
|
||||
"Delicate", "Desperate", "Difficult", "Discreet", "Disguised", "Dizzy",
|
||||
"Dumb", "Eager", "Easy", "Edgy", "Electric", "Elegant", "Emancipated",
|
||||
"Enormous", "Euphoric", "Evil", "Fast", "Ferocious", "Fierce", "Fine",
|
||||
"Flawed", "Flying", "Foolish", "Foxy", "Freezing", "Funny", "Furious",
|
||||
"Gentle", "Glorious", "Golden", "Good", "Green", "Green", "Guilty",
|
||||
"Hairy", "Happy", "Hard", "Hasty", "Hazy", "Heroic", "Hostile", "Hot",
|
||||
"Humble", "Humongous", "Humorous", "Hysterical", "Idealistic", "Ignorant",
|
||||
"Immense", "Impartial", "Impolite", "Indifferent", "Infuriated",
|
||||
"Insightful", "Intense", "Interesting", "Intimidated", "Intriguing",
|
||||
"Jealous", "Jolly", "Jovial", "Jumpy", "Kind", "Laughing", "Lazy", "Liquid",
|
||||
"Lonely", "Longing", "Loud", "Loving", "Loyal", "Macabre", "Mad", "Magical",
|
||||
"Magnificent", "Malevolent", "Medieval", "Memorable", "Mere", "Merry",
|
||||
"Mighty", "Mischievous", "Miserable", "Modified", "Moody", "Most",
|
||||
"Mysterious", "Mystical", "Needy", "Nervous", "Nice", "Objective",
|
||||
"Obnoxious", "Obsessive", "Obvious", "Opinionated", "Orange", "Painful",
|
||||
"Passionate", "Perfect", "Pink", "Playful", "Poisonous", "Polite", "Poor",
|
||||
"Popular", "Powerful", "Precise", "Preserved", "Pretty", "Purple", "Quick",
|
||||
"Quiet", "Random", "Rapid", "Rare", "Real", "Reassuring", "Reckless", "Red",
|
||||
"Regular", "Remorseful", "Responsible", "Rich", "Rude", "Ruthless", "Sad",
|
||||
"Scared", "Scary", "Scornful", "Screaming", "Selfish", "Serious", "Shady",
|
||||
"Shaky", "Sharp", "Shiny", "Shy", "Simple", "Sleepy", "Slow", "Sly",
|
||||
"Small", "Smart", "Smelly", "Smiling", "Smooth", "Smug", "Sober", "Soft",
|
||||
"Solemn", "Square", "Square", "Steady", "Strange", "Strong", "Stunning",
|
||||
"Subjective", "Successful", "Surly", "Sweet", "Tactful", "Tense",
|
||||
"Thoughtful", "Tight", "Tiny", "Tolerant", "Uneasy", "Unique", "Unseen",
|
||||
"Warm", "Weak", "Weird", "WellCooked", "Wild", "Wise", "Witty", "Wonderful",
|
||||
"Worried", "Yellow", "Young", "Zealous"
|
||||
];
|
||||
//var pronouns = [
|
||||
//];
|
||||
//var conjunctions = [
|
||||
//"And", "Or", "For", "Above", "Before", "Against", "Between"
|
||||
//];
|
||||
|
||||
/*
|
||||
* Maps a string (category name) to the array of words from that category.
|
||||
*/
|
||||
var CATEGORIES =
|
||||
{
|
||||
//"_NOUN_": nouns,
|
||||
"_PLURALNOUN_": pluralNouns,
|
||||
//"_PLACE_": places,
|
||||
"_VERB_": verbs,
|
||||
"_ADVERB_": adverbs,
|
||||
"_ADJECTIVE_": adjectives
|
||||
//"_PRONOUN_": pronouns,
|
||||
//"_CONJUNCTION_": conjunctions,
|
||||
};
|
||||
|
||||
var PATTERNS = [
|
||||
"_ADJECTIVE__PLURALNOUN__VERB__ADVERB_"
|
||||
|
||||
// BeautifulFungiOrSpaghetti
|
||||
//"_ADJECTIVE__PLURALNOUN__CONJUNCTION__PLURALNOUN_",
|
||||
|
||||
// AmazinglyScaryToy
|
||||
//"_ADVERB__ADJECTIVE__NOUN_",
|
||||
|
||||
// NeitherTrashNorRifle
|
||||
//"Neither_NOUN_Nor_NOUN_",
|
||||
//"Either_NOUN_Or_NOUN_",
|
||||
|
||||
// EitherCopulateOrInvestigate
|
||||
//"Either_VERB_Or_VERB_",
|
||||
//"Neither_VERB_Nor_VERB_",
|
||||
|
||||
//"The_ADJECTIVE__ADJECTIVE__NOUN_",
|
||||
//"The_ADVERB__ADJECTIVE__NOUN_",
|
||||
//"The_ADVERB__ADJECTIVE__NOUN_s",
|
||||
//"The_ADVERB__ADJECTIVE__PLURALNOUN__VERB_",
|
||||
|
||||
// WolvesComputeBadly
|
||||
//"_PLURALNOUN__VERB__ADVERB_",
|
||||
|
||||
// UniteFacilitateAndMerge
|
||||
//"_VERB__VERB_And_VERB_",
|
||||
|
||||
//NastyWitchesAtThePub
|
||||
//"_ADJECTIVE__PLURALNOUN_AtThe_PLACE_",
|
||||
];
|
||||
|
||||
/*
|
||||
* Returns true if the string 's' contains one of the
|
||||
* template strings.
|
||||
*/
|
||||
function hasTemplate(s) {
|
||||
for (var template in CATEGORIES){
|
||||
if (s.indexOf(template) >= 0){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates new room name.
|
||||
*/
|
||||
var RoomnameGenerator = {
|
||||
generateRoomWithoutSeparator: function() {
|
||||
// Note that if more than one pattern is available, the choice of
|
||||
// 'name' won't have a uniform distribution amongst all patterns (names
|
||||
// from patterns with fewer options will have higher probability of
|
||||
// being chosen that names from patterns with more options).
|
||||
var name = RandomUtil.randomElement(PATTERNS);
|
||||
var word;
|
||||
while (hasTemplate(name)) {
|
||||
for (var template in CATEGORIES) {
|
||||
word = RandomUtil.randomElement(CATEGORIES[template]);
|
||||
name = name.replace(template, word);
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = RoomnameGenerator;
|
||||
28
package.json
28
package.json
@@ -28,17 +28,17 @@
|
||||
"jquery-i18next": "1.1.0",
|
||||
"jQuery-Impromptu": "trentrichardson/jQuery-Impromptu#v6.0.0",
|
||||
"jquery-ui": "1.10.5",
|
||||
"json-loader": "0.5.4",
|
||||
"jssha": "1.5.0",
|
||||
"jws": "*",
|
||||
"lib-jitsi-meet": "jitsi/lib-jitsi-meet",
|
||||
"postis": "^2.2.0",
|
||||
"react": "15.4.2",
|
||||
"react-dom": "15.4.2",
|
||||
"react-native": "0.39.2",
|
||||
"react-native-prompt": "^1.0.0",
|
||||
"react": "15.4.1",
|
||||
"react-dom": "15.4.1",
|
||||
"react-native": "0.38.0",
|
||||
"react-native-vector-icons": "^3.0.0",
|
||||
"react-native-webrtc": "jitsi/react-native-webrtc",
|
||||
"react-redux": "^5.0.2",
|
||||
"react-redux": "^4.4.6",
|
||||
"react-router": "^3.0.0",
|
||||
"react-router-redux": "^4.0.7",
|
||||
"redux": "^3.5.2",
|
||||
@@ -51,31 +51,29 @@
|
||||
"xmldom": "^0.1.27"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.18.0",
|
||||
"babel-core": "*",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-loader": "^6.2.10",
|
||||
"babel-loader": "^6.2.8",
|
||||
"babel-polyfill": "*",
|
||||
"babel-preset-es2015": "^6.18.0",
|
||||
"babel-preset-react": "^6.16.0",
|
||||
"babel-preset-stage-1": "^6.16.0",
|
||||
"clean-css": "*",
|
||||
"css-loader": "*",
|
||||
"eslint": "^3.13.1",
|
||||
"eslint": ">=3",
|
||||
"eslint-plugin-jsdoc": "*",
|
||||
"eslint-plugin-react": "*",
|
||||
"eslint-plugin-react-native": "^2.2.1",
|
||||
"eslint-plugin-react-native": "*",
|
||||
"expose-loader": "*",
|
||||
"file-loader": "*",
|
||||
"haste-resolver-webpack-plugin": "^0.2.2",
|
||||
"imports-loader": "*",
|
||||
"jshint": "2.9.4",
|
||||
"json-loader": "0.5.4",
|
||||
"jshint": "2.8.0",
|
||||
"node-sass": "^3.8.0",
|
||||
"precommit-hook": "3.0.0",
|
||||
"string-replace-loader": "*",
|
||||
"style-loader": "*",
|
||||
"webpack": "^1.14.0",
|
||||
"webpack-dev-server": "^1.16.2"
|
||||
"webpack": "*"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
@@ -86,14 +84,12 @@
|
||||
"lint"
|
||||
],
|
||||
"browser": {
|
||||
"aui": "@atlassian/aui/lib/js/aui.js",
|
||||
"aui-css": "./node_modules/@atlassian/aui/dist/aui/css/aui.min.css",
|
||||
"aui-experimental": "@atlassian/aui/lib/js/aui-experimental.js",
|
||||
"aui-experimental-css": "./node_modules/@atlassian/aui/dist/aui/css/aui-experimental.min.css",
|
||||
"autosize": "./node_modules/autosize/build/jquery.autosize.js",
|
||||
"jQuery-Impromptu": "jQuery-Impromptu/src/jquery-impromptu.js",
|
||||
"popover": "./node_modules/bootstrap/js/popover.js",
|
||||
"strophe-disco": "./node_modules/strophejs-plugins/disco/strophe.disco.js",
|
||||
"strophe-caps": "./node_modules/strophejs-plugins/caps/strophe.caps.jsonly.js",
|
||||
"tooltip": "./node_modules/bootstrap/js/tooltip.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Symbol } from '../base/react';
|
||||
|
||||
/**
|
||||
* The type of the actions which signals that a specific App will mount (in the
|
||||
* terms of React).
|
||||
@@ -9,7 +7,7 @@ import { Symbol } from '../base/react';
|
||||
* app: App
|
||||
* }
|
||||
*/
|
||||
export const APP_WILL_MOUNT = Symbol('APP_WILL_MOUNT');
|
||||
export const APP_WILL_MOUNT = 'APP_WILL_MOUNT';
|
||||
|
||||
/**
|
||||
* The type of the actions which signals that a specific App will unmount (in
|
||||
@@ -20,4 +18,4 @@ export const APP_WILL_MOUNT = Symbol('APP_WILL_MOUNT');
|
||||
* app: App
|
||||
* }
|
||||
*/
|
||||
export const APP_WILL_UNMOUNT = Symbol('APP_WILL_UNMOUNT');
|
||||
export const APP_WILL_UNMOUNT = 'APP_WILL_UNMOUNT';
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
import { setRoom } from '../base/conference';
|
||||
import { getDomain, setDomain } from '../base/connection';
|
||||
import { loadConfig, setConfig } from '../base/lib-jitsi-meet';
|
||||
import {
|
||||
getDomain,
|
||||
setDomain
|
||||
} from '../base/connection';
|
||||
import {
|
||||
loadConfig,
|
||||
setConfig
|
||||
} from '../base/lib-jitsi-meet';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes';
|
||||
import {
|
||||
APP_WILL_MOUNT,
|
||||
APP_WILL_UNMOUNT
|
||||
} from './actionTypes';
|
||||
import {
|
||||
_getRoomAndDomainFromUrlString,
|
||||
_getRouteToRender,
|
||||
init
|
||||
_getRouteToRender
|
||||
} from './functions';
|
||||
import './reducer';
|
||||
|
||||
/**
|
||||
* Temporary solution. Should dispatch actions related to initial settings of
|
||||
* the app like setting log levels, reading the config parameters from query
|
||||
* string etc.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function appInit() {
|
||||
return () => init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an in-app navigation to a different route. Allows navigation to be
|
||||
* abstracted between the mobile and web versions.
|
||||
@@ -53,41 +50,18 @@ export function appNavigate(urlOrRoom) {
|
||||
// race conditions when we will start to load config multiple times.
|
||||
dispatch(setDomain(domain));
|
||||
|
||||
// If domain has changed, we need to load the config of the new
|
||||
// domain and set it, and only after that we can navigate to
|
||||
// different route.
|
||||
// If domain has changed, that means we need to load new config
|
||||
// for that new domain and set it, and only after that we can
|
||||
// navigate to different route.
|
||||
loadConfig(`https://${domain}`)
|
||||
.then(
|
||||
config => configLoaded(/* err */ undefined, config),
|
||||
err => configLoaded(err, /* config */ undefined));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies that an attempt to load the config(uration) of domain has
|
||||
* completed.
|
||||
*
|
||||
* @param {string|undefined} err - If the loading has failed, the error
|
||||
* detailing the cause of the failure.
|
||||
* @param {Object|undefined} config - If the loading has succeeded, the
|
||||
* loaded config(uration).
|
||||
* @returns {void}
|
||||
*/
|
||||
function configLoaded(err, config) {
|
||||
if (err) {
|
||||
// XXX The failure could be, for example, because of a
|
||||
// certificate-related error. In which case the connection will
|
||||
// fail later in Strophe anyway even if we use the default
|
||||
// config here.
|
||||
|
||||
// The function loadConfig will log the err.
|
||||
return;
|
||||
}
|
||||
|
||||
// We set room name only here to prevent race conditions on app
|
||||
// start to not make app re-render conference page for two times.
|
||||
dispatch(setRoom(room));
|
||||
dispatch(setConfig(config));
|
||||
_navigate(getState());
|
||||
.then(config => {
|
||||
// We set room name only here to prevent race conditions on
|
||||
// app start to not make app re-render conference page for
|
||||
// two times.
|
||||
dispatch(setRoom(room));
|
||||
dispatch(setConfig(config));
|
||||
_navigate(getState());
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
/* global __DEV__ */
|
||||
|
||||
import React from 'react';
|
||||
import { Linking, Navigator, Platform } from 'react-native';
|
||||
import { Linking, Navigator } from 'react-native';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { _getRouteToRender } from '../functions';
|
||||
@@ -32,12 +30,6 @@ export class App extends AbstractApp {
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._navigatorRenderScene = this._navigatorRenderScene.bind(this);
|
||||
this._onLinkingURL = this._onLinkingURL.bind(this);
|
||||
|
||||
// In the Release configuration, React Native will (intentionally) throw
|
||||
// an unhandled JavascriptException for an unhandled JavaScript error.
|
||||
// This will effectively kill the application. In accord with the Web,
|
||||
// do not kill the application.
|
||||
this._maybeDisableExceptionsManager();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,44 +101,6 @@ export class App extends AbstractApp {
|
||||
navigator && navigator.replace({ ...route });
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to disable the use of React Native
|
||||
* {@link ExceptionsManager#handleException} on platforms and in
|
||||
* configurations on/in which the use of the method in questions has been
|
||||
* determined to be undesirable. For example, React Native will
|
||||
* (intentionally) throw an unhandled JavascriptException for an
|
||||
* unhandled JavaScript error in the Release configuration. This will
|
||||
* effectively kill the application. In accord with the Web, do not kill the
|
||||
* application.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_maybeDisableExceptionsManager() {
|
||||
if (__DEV__) {
|
||||
// As mentioned above, only the Release configuration was observed
|
||||
// to suffer.
|
||||
return;
|
||||
}
|
||||
if (Platform.OS !== 'android') {
|
||||
// A solution based on RTCSetFatalHandler was implemented on iOS and
|
||||
// it is preferred because it is at a later step of the
|
||||
// error/exception handling and it is specific to fatal
|
||||
// errors/exceptions which were observed to kill the application.
|
||||
// The solution implemented bellow was tested on Android only so it
|
||||
// is considered safest to use it there only.
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHandler = global.ErrorUtils.getGlobalHandler();
|
||||
const newHandler = _handleException;
|
||||
|
||||
if (!oldHandler || oldHandler !== newHandler) {
|
||||
newHandler.next = oldHandler;
|
||||
global.ErrorUtils.setGlobalHandler(newHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the scene identified by a specific route in the Navigator of this
|
||||
* instance.
|
||||
@@ -179,29 +133,3 @@ export class App extends AbstractApp {
|
||||
this._openURL(event.url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a (possibly unhandled) JavaScript error by preventing React Native
|
||||
* from converting a fatal error into an unhandled native exception which will
|
||||
* kill the application.
|
||||
*
|
||||
* @param {Error} error - The (possibly unhandled) JavaScript error to handle.
|
||||
* @param {boolean} fatal - True if the specified error is fatal; otherwise,
|
||||
* false.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleException(error, fatal) {
|
||||
if (fatal) {
|
||||
// In the Release configuration, React Native will (intentionally) throw
|
||||
// an unhandled JavascriptException for an unhandled JavaScript error.
|
||||
// This will effectively kill the application. In accord with the Web,
|
||||
// do not kill the application.
|
||||
console.error(error);
|
||||
} else {
|
||||
// Forward to the next globalHandler of ErrorUtils.
|
||||
const next = _handleException.next;
|
||||
|
||||
typeof next === 'function' && next(error, fatal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { browserHistory, Route, Router } from 'react-router';
|
||||
import {
|
||||
browserHistory,
|
||||
Route,
|
||||
Router
|
||||
} from 'react-router';
|
||||
import { push, syncHistoryWithStore } from 'react-router-redux';
|
||||
|
||||
import { getDomain } from '../../base/connection';
|
||||
import { RouteRegistry } from '../../base/navigator';
|
||||
|
||||
import { appInit } from '../actions';
|
||||
import { AbstractApp } from './AbstractApp';
|
||||
|
||||
/**
|
||||
@@ -39,18 +42,30 @@ export class App extends AbstractApp {
|
||||
this.history = syncHistoryWithStore(browserHistory, props.store);
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onRouteEnter = this._onRouteEnter.bind(this);
|
||||
this._routerCreateElement = this._routerCreateElement.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inits the app before component will mount.
|
||||
* Temporarily, prevents the super from dispatching Redux actions until they
|
||||
* are integrated into the Web App.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillMount(...args) {
|
||||
super.componentWillMount(...args);
|
||||
componentWillMount() {
|
||||
// FIXME Do not override the super once the dispatching of Redux actions
|
||||
// is integrated into the Web App.
|
||||
}
|
||||
|
||||
this.props.store.dispatch(appInit());
|
||||
/**
|
||||
* Temporarily, prevents the super from dispatching Redux actions until they
|
||||
* are integrated into the Web App.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
// FIXME Do not override the super once the dispatching of Redux actions
|
||||
// is integrated into the Web App.
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,14 +75,19 @@ export class App extends AbstractApp {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const routes = RouteRegistry.getRoutes();
|
||||
|
||||
return (
|
||||
<Provider store = { this.props.store }>
|
||||
<Router
|
||||
createElement = { this._routerCreateElement }
|
||||
history = { this.history }>
|
||||
{
|
||||
this._renderRoutes()
|
||||
}
|
||||
{ routes.map(r =>
|
||||
<Route
|
||||
component = { r.component }
|
||||
key = { r.component }
|
||||
path = { r.path } />
|
||||
) }
|
||||
</Router>
|
||||
</Provider>
|
||||
);
|
||||
@@ -98,18 +118,10 @@ export class App extends AbstractApp {
|
||||
* Invoked by react-router to notify this App that a Route is about to be
|
||||
* rendered.
|
||||
*
|
||||
* @param {Route} route - The Route that is about to be rendered.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onRouteEnter(route, ...args) {
|
||||
// Notify the route that it is about to be entered.
|
||||
const onEnter = route.onEnter;
|
||||
|
||||
if (typeof onEnter === 'function') {
|
||||
onEnter(...args);
|
||||
}
|
||||
|
||||
_onRouteEnter() {
|
||||
// XXX The following is mandatory. Otherwise, moving back & forward
|
||||
// through the browser's history could leave this App on the Conference
|
||||
// page without a room name.
|
||||
@@ -128,37 +140,6 @@ export class App extends AbstractApp {
|
||||
this._openURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a specific Route (for the purposes of the Router of this App).
|
||||
*
|
||||
* @param {Object} route - The Route to render.
|
||||
* @returns {ReactElement}
|
||||
* @private
|
||||
*/
|
||||
_renderRoute(route) {
|
||||
const onEnter = (...args) => {
|
||||
this._onRouteEnter(route, ...args);
|
||||
};
|
||||
|
||||
return (
|
||||
<Route
|
||||
component = { route.component }
|
||||
key = { route.component }
|
||||
onEnter = { onEnter }
|
||||
path = { route.path } />
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the Routes of the Router of this App.
|
||||
*
|
||||
* @returns {Array.<ReactElement>}
|
||||
* @private
|
||||
*/
|
||||
_renderRoutes() {
|
||||
return RouteRegistry.getRoutes().map(this._renderRoute, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ReactElement from the specified component and props on behalf of
|
||||
* the associated Router.
|
||||
|
||||
@@ -19,7 +19,7 @@ function _getRoomAndDomainFromUrlObject(url) {
|
||||
|
||||
if (url) {
|
||||
domain = url.hostname;
|
||||
room = url.pathname.substr(1);
|
||||
room = url.pathname.substr(1).toLowerCase();
|
||||
|
||||
// Convert empty string to undefined to simplify checks.
|
||||
if (room === '') {
|
||||
@@ -64,8 +64,8 @@ export function _getRoomAndDomainFromUrlString(url) {
|
||||
|
||||
url
|
||||
= match[1] /* URL protocol */
|
||||
+ '://enso.hipchat.me/'
|
||||
+ url.substring(regex.lastIndex);
|
||||
+ '://enso.hipchat.me/'
|
||||
+ url.substring(regex.lastIndex);
|
||||
|
||||
/* eslint-enable no-param-reassign, prefer-template */
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/* global APP, JitsiMeetJS, loggingConfig */
|
||||
|
||||
import URLProcessor from '../../../modules/config/URLProcessor';
|
||||
import KeyboardShortcut
|
||||
from '../../../modules/keyboardshortcut/keyboardshortcut';
|
||||
import settings from '../../../modules/settings/Settings';
|
||||
import getTokenData from '../../../modules/tokendata/TokenData';
|
||||
import JitsiMeetLogStorage from '../../../modules/util/JitsiMeetLogStorage';
|
||||
|
||||
const Logger = require('jitsi-meet-logger');
|
||||
|
||||
export * from './functions.native';
|
||||
|
||||
/**
|
||||
* Temporary solution. Later we'll get rid of global APP and set its properties
|
||||
* in redux store.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export function init() {
|
||||
URLProcessor.setConfigParametersFromUrl();
|
||||
_initLogging();
|
||||
|
||||
APP.keyboardshortcut = KeyboardShortcut;
|
||||
APP.tokenData = getTokenData();
|
||||
APP.API.init(APP.tokenData.externalAPISettings);
|
||||
|
||||
APP.translation.init(settings.getLanguage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the logging levels.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _configureLoggingLevels() {
|
||||
// NOTE The library Logger is separated from the app loggers, so the levels
|
||||
// have to be set in two places
|
||||
|
||||
// Set default logging level
|
||||
const defaultLogLevel
|
||||
= loggingConfig.defaultLogLevel || JitsiMeetJS.logLevels.TRACE;
|
||||
|
||||
Logger.setLogLevel(defaultLogLevel);
|
||||
JitsiMeetJS.setLogLevel(defaultLogLevel);
|
||||
|
||||
// NOTE console was used on purpose here to go around the logging and always
|
||||
// print the default logging level to the console
|
||||
console.info(`Default logging level set to: ${defaultLogLevel}`);
|
||||
|
||||
// Set log level for each logger
|
||||
if (loggingConfig) {
|
||||
Object.keys(loggingConfig).forEach(loggerName => {
|
||||
if (loggerName !== 'defaultLogLevel') {
|
||||
const level = loggingConfig[loggerName];
|
||||
|
||||
Logger.setLogLevelById(level, loggerName);
|
||||
JitsiMeetJS.setLogLevelById(level, loggerName);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes logging in the app.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _initLogging() {
|
||||
// Adjust logging level
|
||||
_configureLoggingLevels();
|
||||
|
||||
// Create the LogCollector and register it as the global log transport. It
|
||||
// is done early to capture as much logs as possible. Captured logs will be
|
||||
// cached, before the JitsiMeetLogStorage gets ready (statistics module is
|
||||
// initialized).
|
||||
if (!APP.logCollector && !loggingConfig.disableLogCollector) {
|
||||
APP.logCollector = new Logger.LogCollector(new JitsiMeetLogStorage());
|
||||
Logger.addGlobalTransport(APP.logCollector);
|
||||
JitsiMeetJS.addGlobalLogTransport(APP.logCollector);
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,25 @@ import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes';
|
||||
|
||||
ReducerRegistry.register('features/app', (state = {}, action) => {
|
||||
/**
|
||||
* The initial Redux state of features/app.
|
||||
*/
|
||||
const INITIAL_STATE = {
|
||||
/**
|
||||
* The one and only (i.e. singleton) App instance which is currently
|
||||
* mounted.
|
||||
*
|
||||
* @type {App}
|
||||
*/
|
||||
app: undefined
|
||||
};
|
||||
|
||||
ReducerRegistry.register('features/app', (state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT:
|
||||
if (state.app !== action.app) {
|
||||
return {
|
||||
...state,
|
||||
|
||||
/**
|
||||
* The one and only (i.e. singleton) App instance which is
|
||||
* currently mounted.
|
||||
*
|
||||
* @type {App}
|
||||
*/
|
||||
app: action.app
|
||||
};
|
||||
}
|
||||
@@ -24,7 +30,7 @@ ReducerRegistry.register('features/app', (state = {}, action) => {
|
||||
if (state.app === action.app) {
|
||||
return {
|
||||
...state,
|
||||
app: undefined
|
||||
app: INITIAL_STATE.app
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1,82 +1,46 @@
|
||||
import { Symbol } from '../react';
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that a specific conference has
|
||||
* failed.
|
||||
* Action type to signal that we are joining the conference.
|
||||
*
|
||||
* {
|
||||
* type: CONFERENCE_FAILED,
|
||||
* conference: JitsiConference,
|
||||
* error: string
|
||||
* type: CONFERENCE_JOINED,
|
||||
* conference: {
|
||||
* jitsiConference: JitsiConference
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export const CONFERENCE_FAILED = Symbol('CONFERENCE_FAILED');
|
||||
export const CONFERENCE_JOINED = 'CONFERENCE_JOINED';
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that a specific conference has
|
||||
* been joined.
|
||||
* Action type to signal that we have left the conference.
|
||||
*
|
||||
* {
|
||||
* type: CONFERENCE_JOINED,
|
||||
* conference: JitsiConference
|
||||
* type: CONFERENCE_LEFT,
|
||||
* conference: {
|
||||
* jitsiConference: JitsiConference
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export const CONFERENCE_JOINED = Symbol('CONFERENCE_JOINED');
|
||||
export const CONFERENCE_LEFT = 'CONFERENCE_LEFT';
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that a specific conference has
|
||||
* been left.
|
||||
* Action type to signal that we will leave the specified conference.
|
||||
*
|
||||
* {
|
||||
* type: CONFERENCE_LEFT,
|
||||
* conference: JitsiConference
|
||||
* type: CONFERENCE_WILL_LEAVE,
|
||||
* conference: {
|
||||
* jitsiConference: JitsiConference
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export const CONFERENCE_LEFT = Symbol('CONFERENCE_LEFT');
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that a specific conference will be
|
||||
* left.
|
||||
*
|
||||
* {
|
||||
* type: CONFERENCE_WILL_LEAVE,
|
||||
* conference: JitsiConference
|
||||
* }
|
||||
*/
|
||||
export const CONFERENCE_WILL_LEAVE = Symbol('CONFERENCE_WILL_LEAVE');
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that the lock state of a specific
|
||||
* <tt>JitsiConference</tt> changed.
|
||||
*
|
||||
* {
|
||||
* type: LOCK_STATE_CHANGED,
|
||||
* conference: JitsiConference,
|
||||
* locked: boolean
|
||||
* }
|
||||
*/
|
||||
export const LOCK_STATE_CHANGED = Symbol('LOCK_STATE_CHANGED');
|
||||
|
||||
/**
|
||||
* The type of the Redux action which sets the password to join or lock a
|
||||
* specific JitsiConference.
|
||||
*
|
||||
* {
|
||||
* type: SET_PASSWORD,
|
||||
* conference: JitsiConference,
|
||||
* method: Function
|
||||
* password: string
|
||||
* }
|
||||
*/
|
||||
export const SET_PASSWORD = Symbol('SET_PASSWORD');
|
||||
export const CONFERENCE_WILL_LEAVE = 'CONFERENCE_WILL_LEAVE';
|
||||
|
||||
/**
|
||||
* The type of the Redux action which sets the name of the room of the
|
||||
* conference to be joined.
|
||||
*
|
||||
* {
|
||||
* type: SET_ROOM,
|
||||
* room: string
|
||||
* type: SET_ROOM,
|
||||
* room: string
|
||||
* }
|
||||
*/
|
||||
export const SET_ROOM = Symbol('SET_ROOM');
|
||||
export const SET_ROOM = 'SET_ROOM';
|
||||
|
||||
@@ -6,15 +6,15 @@ import {
|
||||
participantLeft,
|
||||
participantRoleChanged
|
||||
} from '../participants';
|
||||
import { trackAdded, trackRemoved } from '../tracks';
|
||||
import {
|
||||
trackAdded,
|
||||
trackRemoved
|
||||
} from '../tracks';
|
||||
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
LOCK_STATE_CHANGED,
|
||||
SET_PASSWORD,
|
||||
SET_ROOM
|
||||
} from './actionTypes';
|
||||
import { EMAIL_COMMAND } from './constants';
|
||||
@@ -22,78 +22,33 @@ import { _addLocalTracksToConference } from './functions';
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
||||
/**
|
||||
* Adds conference (event) listeners.
|
||||
*
|
||||
* @param {JitsiConference} conference - The JitsiConference instance.
|
||||
* @param {Dispatch} dispatch - The Redux dispatch function.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _addConferenceListeners(conference, dispatch) {
|
||||
const JitsiConferenceEvents = JitsiMeetJS.events.conference;
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.CONFERENCE_FAILED,
|
||||
(...args) => dispatch(_conferenceFailed(conference, ...args)));
|
||||
conference.on(
|
||||
JitsiConferenceEvents.CONFERENCE_JOINED,
|
||||
(...args) => dispatch(_conferenceJoined(conference, ...args)));
|
||||
conference.on(
|
||||
JitsiConferenceEvents.CONFERENCE_LEFT,
|
||||
(...args) => dispatch(_conferenceLeft(conference, ...args)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
|
||||
(...args) => dispatch(dominantSpeakerChanged(...args)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.LOCK_STATE_CHANGED,
|
||||
(...args) => dispatch(_lockStateChanged(conference, ...args)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.TRACK_ADDED,
|
||||
t => t && !t.isLocal() && dispatch(trackAdded(t)));
|
||||
conference.on(
|
||||
JitsiConferenceEvents.TRACK_REMOVED,
|
||||
t => t && !t.isLocal() && dispatch(trackRemoved(t)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.USER_JOINED,
|
||||
(id, user) => dispatch(participantJoined({
|
||||
id,
|
||||
name: user.getDisplayName(),
|
||||
role: user.getRole()
|
||||
})));
|
||||
conference.on(
|
||||
JitsiConferenceEvents.USER_LEFT,
|
||||
(...args) => dispatch(participantLeft(...args)));
|
||||
conference.on(
|
||||
JitsiConferenceEvents.USER_ROLE_CHANGED,
|
||||
(...args) => dispatch(participantRoleChanged(...args)));
|
||||
|
||||
conference.addCommandListener(
|
||||
EMAIL_COMMAND,
|
||||
(data, id) => dispatch(changeParticipantEmail(id, data.value)));
|
||||
}
|
||||
const JitsiConferenceEvents = JitsiMeetJS.events.conference;
|
||||
|
||||
/**
|
||||
* Signals that a specific conference has failed.
|
||||
* Initializes a new conference.
|
||||
*
|
||||
* @param {JitsiConference} conference - The JitsiConference that has failed.
|
||||
* @param {string} error - The error describing/detailing the cause of the
|
||||
* failure.
|
||||
* @returns {{
|
||||
* type: CONFERENCE_FAILED,
|
||||
* conference: JitsiConference,
|
||||
* error: string
|
||||
* }}
|
||||
* @returns {Function}
|
||||
*/
|
||||
function _conferenceFailed(conference, error) {
|
||||
return {
|
||||
type: CONFERENCE_FAILED,
|
||||
conference,
|
||||
error
|
||||
export function createConference() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const connection = state['features/base/connection'].jitsiConnection;
|
||||
const room = state['features/base/conference'].room;
|
||||
|
||||
if (!connection) {
|
||||
throw new Error('Cannot create conference without connection');
|
||||
}
|
||||
if (typeof room === 'undefined' || room === '') {
|
||||
throw new Error('Cannot join conference without room name');
|
||||
}
|
||||
|
||||
// TODO Take options from config.
|
||||
const conference
|
||||
= connection.initJitsiConference(room, { openSctp: true });
|
||||
|
||||
dispatch(_setupConferenceListeners(conference));
|
||||
|
||||
conference.join();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -105,12 +60,11 @@ function _conferenceFailed(conference, error) {
|
||||
* joined by the local participant.
|
||||
* @returns {Function}
|
||||
*/
|
||||
function _conferenceJoined(conference) {
|
||||
export function conferenceJoined(conference) {
|
||||
return (dispatch, getState) => {
|
||||
const localTracks
|
||||
= getState()['features/base/tracks']
|
||||
.filter(t => t.local)
|
||||
.map(t => t.jitsiTrack);
|
||||
const localTracks = getState()['features/base/tracks']
|
||||
.filter(t => t.local)
|
||||
.map(t => t.jitsiTrack);
|
||||
|
||||
if (localTracks.length) {
|
||||
_addLocalTracksToConference(conference, localTracks);
|
||||
@@ -118,31 +72,37 @@ function _conferenceJoined(conference) {
|
||||
|
||||
dispatch({
|
||||
type: CONFERENCE_JOINED,
|
||||
conference
|
||||
conference: {
|
||||
jitsiConference: conference
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that a specific conference has been left.
|
||||
* Signal that we have left the conference.
|
||||
*
|
||||
* @param {JitsiConference} conference - The JitsiConference instance which was
|
||||
* left by the local participant.
|
||||
* @returns {{
|
||||
* type: CONFERENCE_LEFT,
|
||||
* conference: JitsiConference
|
||||
* conference: {
|
||||
* jitsiConference: JitsiConference
|
||||
* }
|
||||
* }}
|
||||
*/
|
||||
function _conferenceLeft(conference) {
|
||||
export function conferenceLeft(conference) {
|
||||
return {
|
||||
type: CONFERENCE_LEFT,
|
||||
conference
|
||||
conference: {
|
||||
jitsiConference: conference
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals the intention of the application to have the local participant leave
|
||||
* a specific conference. Similar in fashion to CONFERENCE_LEFT. Contrary to it
|
||||
* Signal the intention of the application to have the local participant leave a
|
||||
* specific conference. Similar in fashion to CONFERENCE_LEFT. Contrary to it
|
||||
* though, it's not guaranteed because CONFERENCE_LEFT may be triggered by
|
||||
* lib-jitsi-meet and not the application.
|
||||
*
|
||||
@@ -150,126 +110,16 @@ function _conferenceLeft(conference) {
|
||||
* be left by the local participant.
|
||||
* @returns {{
|
||||
* type: CONFERENCE_LEFT,
|
||||
* conference: JitsiConference
|
||||
* conference: {
|
||||
* jitsiConference: JitsiConference
|
||||
* }
|
||||
* }}
|
||||
*/
|
||||
export function conferenceWillLeave(conference) {
|
||||
return {
|
||||
type: CONFERENCE_WILL_LEAVE,
|
||||
conference
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new conference.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function createConference() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const connection = state['features/base/connection'].connection;
|
||||
|
||||
if (!connection) {
|
||||
throw new Error('Cannot create conference without connection');
|
||||
}
|
||||
|
||||
const { password, room } = state['features/base/conference'];
|
||||
|
||||
if (typeof room === 'undefined' || room === '') {
|
||||
throw new Error('Cannot join conference without room name');
|
||||
}
|
||||
|
||||
// TODO Take options from config.
|
||||
const conference
|
||||
= connection.initJitsiConference(room, { openSctp: true });
|
||||
|
||||
_addConferenceListeners(conference, dispatch);
|
||||
|
||||
conference.join(password);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the lock state of a specific JitsiConference changed.
|
||||
*
|
||||
* @param {JitsiConference} conference - The JitsiConference which had its lock
|
||||
* state changed.
|
||||
* @param {boolean} locked - If the specified conference became locked, true;
|
||||
* otherwise, false.
|
||||
* @returns {{
|
||||
* type: LOCK_STATE_CHANGED,
|
||||
* conference: JitsiConference,
|
||||
* locked: boolean
|
||||
* }}
|
||||
*/
|
||||
function _lockStateChanged(conference, locked) {
|
||||
return {
|
||||
type: LOCK_STATE_CHANGED,
|
||||
conference,
|
||||
locked
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the password to join or lock a specific JitsiConference.
|
||||
*
|
||||
* @param {JitsiConference} conference - The JitsiConference which requires a
|
||||
* password to join or is to be locked with the specified password.
|
||||
* @param {Function} method - The JitsiConference method of password protection
|
||||
* such as join or lock.
|
||||
* @param {string} password - The password with which the specified conference
|
||||
* is to be joined or locked.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setPassword(conference, method, password) {
|
||||
return (dispatch, getState) => {
|
||||
switch (method) {
|
||||
case conference.join: {
|
||||
let state = getState()['features/base/conference'];
|
||||
|
||||
// Make sure that the action will set a password for a conference
|
||||
// that the application wants joined.
|
||||
if (state.passwordRequired === conference) {
|
||||
dispatch({
|
||||
type: SET_PASSWORD,
|
||||
conference,
|
||||
method,
|
||||
password
|
||||
});
|
||||
|
||||
// Join the conference with the newly-set password.
|
||||
|
||||
// Make sure that the action did set the password.
|
||||
state = getState()['features/base/conference'];
|
||||
if (state.password === password
|
||||
&& !state.passwordRequired
|
||||
|
||||
// Make sure that the application still wants the
|
||||
// conference joined.
|
||||
&& !state.conference) {
|
||||
method.call(conference, password);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case conference.lock: {
|
||||
const state = getState()['features/base/conference'];
|
||||
|
||||
if (state.conference === conference) {
|
||||
return (
|
||||
method.call(conference, password)
|
||||
.then(() => dispatch({
|
||||
type: SET_PASSWORD,
|
||||
conference,
|
||||
method,
|
||||
password
|
||||
})));
|
||||
}
|
||||
|
||||
return Promise.reject();
|
||||
}
|
||||
conference: {
|
||||
jitsiConference: conference
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -290,3 +140,52 @@ export function setRoom(room) {
|
||||
room
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup various conference event handlers.
|
||||
*
|
||||
* @param {JitsiConference} conference - Conference instance.
|
||||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
function _setupConferenceListeners(conference) {
|
||||
return dispatch => {
|
||||
conference.on(
|
||||
JitsiConferenceEvents.CONFERENCE_JOINED,
|
||||
() => dispatch(conferenceJoined(conference)));
|
||||
conference.on(
|
||||
JitsiConferenceEvents.CONFERENCE_LEFT,
|
||||
() => dispatch(conferenceLeft(conference)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
|
||||
id => dispatch(dominantSpeakerChanged(id)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.TRACK_ADDED,
|
||||
track =>
|
||||
track && !track.isLocal() && dispatch(trackAdded(track)));
|
||||
conference.on(
|
||||
JitsiConferenceEvents.TRACK_REMOVED,
|
||||
track =>
|
||||
track && !track.isLocal() && dispatch(trackRemoved(track)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.USER_JOINED,
|
||||
(id, user) => dispatch(participantJoined({
|
||||
id,
|
||||
name: user.getDisplayName(),
|
||||
role: user.getRole()
|
||||
})));
|
||||
conference.on(
|
||||
JitsiConferenceEvents.USER_LEFT,
|
||||
id => dispatch(participantLeft(id)));
|
||||
conference.on(
|
||||
JitsiConferenceEvents.USER_ROLE_CHANGED,
|
||||
(id, role) => dispatch(participantRoleChanged(id, role)));
|
||||
|
||||
conference.addCommandListener(
|
||||
EMAIL_COMMAND,
|
||||
(data, id) => dispatch(changeParticipantEmail(id, data.value)));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { CONNECTION_ESTABLISHED } from '../connection';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
PIN_PARTICIPANT
|
||||
} from '../participants';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { TRACK_ADDED, TRACK_REMOVED } from '../tracks';
|
||||
import {
|
||||
TRACK_ADDED,
|
||||
TRACK_REMOVED
|
||||
} from '../tracks';
|
||||
|
||||
import { createConference } from './actions';
|
||||
import {
|
||||
_addLocalTracksToConference,
|
||||
_handleParticipantError,
|
||||
@@ -15,68 +16,45 @@ import {
|
||||
} from './functions';
|
||||
|
||||
/**
|
||||
* Implements the middleware of the feature base/conference.
|
||||
* This middleware intercepts TRACK_ADDED and TRACK_REMOVED actions to sync
|
||||
* conference's local tracks with local tracks in state. Also captures
|
||||
* PIN_PARTICIPANT action to pin participant in conference.
|
||||
*
|
||||
* @param {Store} store - Redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case CONNECTION_ESTABLISHED:
|
||||
return _connectionEstablished(store, next, action);
|
||||
|
||||
case PIN_PARTICIPANT:
|
||||
return _pinParticipant(store, next, action);
|
||||
pinParticipant(store, action.participant.id);
|
||||
break;
|
||||
|
||||
case TRACK_ADDED:
|
||||
case TRACK_REMOVED:
|
||||
return _trackAddedOrRemoved(store, next, action);
|
||||
case TRACK_REMOVED: {
|
||||
const track = action.track;
|
||||
|
||||
if (track && track.local) {
|
||||
return syncConferenceLocalTracksWithState(store, action)
|
||||
.then(() => next(action));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Notifies the feature base/conference that the action CONNECTION_ESTABLISHED
|
||||
* is being dispatched within a specific Redux store.
|
||||
* Pins remote participant in conference, ignores local participant.
|
||||
*
|
||||
* @param {Store} store - The Redux store in which the specified action is being
|
||||
* dispatched.
|
||||
* @param {Dispatch} next - The Redux dispatch function to dispatch the
|
||||
* specified action to the specified store.
|
||||
* @param {Action} action - The Redux action CONNECTION_ESTABLISHED which is
|
||||
* being dispatched in the specified store.
|
||||
* @private
|
||||
* @returns {Object} The new state that is the result of the reduction of the
|
||||
* specified action.
|
||||
* @param {Store} store - Redux store.
|
||||
* @param {string|null} id - Participant id or null if no one is currently
|
||||
* pinned.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _connectionEstablished(store, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
store.dispatch(createConference());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the feature base/conference that the action PIN_PARTICIPANT is being
|
||||
* dispatched within a specific Redux store. Pins the specified remote
|
||||
* participant in the associated conference, ignores the local participant.
|
||||
*
|
||||
* @param {Store} store - The Redux store in which the specified action is being
|
||||
* dispatched.
|
||||
* @param {Dispatch} next - The Redux dispatch function to dispatch the
|
||||
* specified action to the specified store.
|
||||
* @param {Action} action - The Redux action PIN_PARTICIPANT which is being
|
||||
* dispatched in the specified store.
|
||||
* @private
|
||||
* @returns {Object} The new state that is the result of the reduction of the
|
||||
* specified action.
|
||||
*/
|
||||
function _pinParticipant(store, next, action) {
|
||||
function pinParticipant(store, id) {
|
||||
const state = store.getState();
|
||||
const participants = state['features/base/participants'];
|
||||
const id = action.participant.id;
|
||||
const participantById = getParticipantById(participants, id);
|
||||
let pin;
|
||||
|
||||
@@ -95,7 +73,7 @@ function _pinParticipant(store, next, action) {
|
||||
pin = !localParticipant || !localParticipant.pinned;
|
||||
}
|
||||
if (pin) {
|
||||
const conference = state['features/base/conference'].conference;
|
||||
const conference = state['features/base/conference'].jitsiConference;
|
||||
|
||||
try {
|
||||
conference.pinParticipant(id);
|
||||
@@ -103,27 +81,24 @@ function _pinParticipant(store, next, action) {
|
||||
_handleParticipantError(err);
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes local tracks from state with local tracks in JitsiConference
|
||||
* instance.
|
||||
* Syncs local tracks from state with local tracks in JitsiConference instance.
|
||||
*
|
||||
* @param {Store} store - Redux store.
|
||||
* @param {Object} action - Action object.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function _syncConferenceLocalTracksWithState(store, action) {
|
||||
const state = store.getState()['features/base/conference'];
|
||||
const conference = state.conference;
|
||||
function syncConferenceLocalTracksWithState(store, action) {
|
||||
const conferenceState = store.getState()['features/base/conference'];
|
||||
const conference = conferenceState.jitsiConference;
|
||||
const leavingConference = conferenceState.leavingJitsiConference;
|
||||
let promise;
|
||||
|
||||
// XXX The conference may already be in the process of being left, that's
|
||||
// XXX The conference in state might be already in 'leaving' state, that's
|
||||
// why we should not add/remove local tracks to such conference.
|
||||
if (conference && conference !== state.leaving) {
|
||||
if (conference && conference !== leavingConference) {
|
||||
const track = action.track.jitsiTrack;
|
||||
|
||||
if (action.type === TRACK_ADDED) {
|
||||
@@ -135,29 +110,3 @@ function _syncConferenceLocalTracksWithState(store, action) {
|
||||
|
||||
return promise || Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the feature base/conference that the action TRACK_ADDED
|
||||
* or TRACK_REMOVED is being dispatched within a specific Redux store.
|
||||
*
|
||||
* @param {Store} store - The Redux store in which the specified action is being
|
||||
* dispatched.
|
||||
* @param {Dispatch} next - The Redux dispatch function to dispatch the
|
||||
* specified action to the specified store.
|
||||
* @param {Action} action - The Redux action TRACK_ADDED or TRACK_REMOVED which
|
||||
* is being dispatched in the specified store.
|
||||
* @private
|
||||
* @returns {Object} The new state that is the result of the reduction of the
|
||||
* specified action.
|
||||
*/
|
||||
function _trackAddedOrRemoved(store, next, action) {
|
||||
const track = action.track;
|
||||
|
||||
if (track && track.local) {
|
||||
return (
|
||||
_syncConferenceLocalTracksWithState(store, action)
|
||||
.then(() => next(action)));
|
||||
}
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
@@ -1,264 +1,80 @@
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import {
|
||||
ReducerRegistry,
|
||||
setStateProperties,
|
||||
setStateProperty
|
||||
} from '../redux';
|
||||
import { ReducerRegistry } from '../redux';
|
||||
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
LOCK_STATE_CHANGED,
|
||||
SET_PASSWORD,
|
||||
SET_ROOM
|
||||
} from './actionTypes';
|
||||
import { isRoomValid } from './functions';
|
||||
|
||||
const INITIAL_STATE = {
|
||||
jitsiConference: null,
|
||||
|
||||
/**
|
||||
* Instance of JitsiConference that is currently in 'leaving' state.
|
||||
*/
|
||||
leavingJitsiConference: null,
|
||||
|
||||
/**
|
||||
* The name of the room of the conference (to be) joined (i.e.
|
||||
* {@link #jitsiConference}).
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
room: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen for actions that contain the conference object, so that it can be
|
||||
* stored for use by other action creators.
|
||||
*/
|
||||
ReducerRegistry.register('features/base/conference', (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_FAILED:
|
||||
return _conferenceFailed(state, action);
|
||||
ReducerRegistry.register('features/base/conference',
|
||||
(state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED:
|
||||
return {
|
||||
...state,
|
||||
jitsiConference: action.conference.jitsiConference
|
||||
};
|
||||
|
||||
case CONFERENCE_JOINED:
|
||||
return _conferenceJoined(state, action);
|
||||
case CONFERENCE_LEFT:
|
||||
if (state.jitsiConference === action.conference.jitsiConference) {
|
||||
return {
|
||||
...state,
|
||||
jitsiConference: null,
|
||||
leavingJitsiConference: state.leavingJitsiConference
|
||||
=== action.conference.jitsiConference
|
||||
? null
|
||||
: state.leavingJitsiConference
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case CONFERENCE_LEFT:
|
||||
return _conferenceLeft(state, action);
|
||||
case CONFERENCE_WILL_LEAVE:
|
||||
return {
|
||||
...state,
|
||||
leavingJitsiConference: action.conference.jitsiConference
|
||||
};
|
||||
|
||||
case CONFERENCE_WILL_LEAVE:
|
||||
return _conferenceWillLeave(state, action);
|
||||
case SET_ROOM: {
|
||||
let room = action.room;
|
||||
|
||||
case LOCK_STATE_CHANGED:
|
||||
return _lockStateChanged(state, action);
|
||||
|
||||
case SET_PASSWORD:
|
||||
return _setPassword(state, action);
|
||||
|
||||
case SET_ROOM:
|
||||
return _setRoom(state, action);
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action CONFERENCE_FAILED of the feature
|
||||
* base/conference.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/conference.
|
||||
* @param {Action} action - The Redux action CONFERENCE_FAILED to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _conferenceFailed(state, action) {
|
||||
const conference = action.conference;
|
||||
|
||||
if (state.conference && state.conference !== conference) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const JitsiConferenceErrors = JitsiMeetJS.errors.conference;
|
||||
const passwordRequired
|
||||
= JitsiConferenceErrors.PASSWORD_REQUIRED === action.error
|
||||
? conference
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
setStateProperties(state, {
|
||||
conference: undefined,
|
||||
leaving: undefined,
|
||||
locked: undefined,
|
||||
password: undefined,
|
||||
|
||||
/**
|
||||
* The JitsiConference instance which requires a password to join.
|
||||
*
|
||||
* @type {JitsiConference}
|
||||
*/
|
||||
passwordRequired
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action CONFERENCE_JOINED of the feature
|
||||
* base/conference.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/conference.
|
||||
* @param {Action} action - The Redux action CONFERENCE_JOINED to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _conferenceJoined(state, action) {
|
||||
const conference = action.conference;
|
||||
|
||||
// FIXME The indicator which determines whether a JitsiConference is locked
|
||||
// i.e. password-protected is private to lib-jitsi-meet. However, the
|
||||
// library does not fire LOCK_STATE_CHANGED upon joining a JitsiConference
|
||||
// with a password.
|
||||
const locked = conference.room.locked || undefined;
|
||||
|
||||
return (
|
||||
setStateProperties(state, {
|
||||
/**
|
||||
* The JitsiConference instance represented by the Redux state of
|
||||
* the feature base/conference.
|
||||
*
|
||||
* @type {JitsiConference}
|
||||
*/
|
||||
conference,
|
||||
leaving: undefined,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the conference is locked.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
locked,
|
||||
passwordRequired: undefined
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action CONFERENCE_LEFT of the feature
|
||||
* base/conference.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/conference.
|
||||
* @param {Action} action - The Redux action CONFERENCE_LEFT to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _conferenceLeft(state, action) {
|
||||
const conference = action.conference;
|
||||
|
||||
if (state.conference !== conference) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return (
|
||||
setStateProperties(state, {
|
||||
conference: undefined,
|
||||
leaving: undefined,
|
||||
locked: undefined,
|
||||
password: undefined,
|
||||
passwordRequired: undefined
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action CONFERENCE_WILL_LEAVE of the feature
|
||||
* base/conference.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/conference.
|
||||
* @param {Action} action - The Redux action CONFERENCE_WILL_LEAVE to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _conferenceWillLeave(state, action) {
|
||||
const conference = action.conference;
|
||||
|
||||
if (state.conference !== conference) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return (
|
||||
setStateProperties(state, {
|
||||
/**
|
||||
* The JitsiConference instance which is currently in the process of
|
||||
* being left.
|
||||
*
|
||||
* @type {JitsiConference}
|
||||
*/
|
||||
leaving: conference,
|
||||
passwordRequired: undefined
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action LOCK_STATE_CHANGED of the feature
|
||||
* base/conference.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/conference.
|
||||
* @param {Action} action - The Redux action LOCK_STATE_CHANGED to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _lockStateChanged(state, action) {
|
||||
if (state.conference !== action.conference) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return setStateProperty(state, 'locked', action.locked || undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action SET_PASSWORD of the feature base/conference.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/conference.
|
||||
* @param {Action} action - The Redux action SET_PASSWORD to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _setPassword(state, action) {
|
||||
const conference = action.conference;
|
||||
|
||||
switch (action.method) {
|
||||
case conference.join:
|
||||
if (state.passwordRequired === conference) {
|
||||
return (
|
||||
setStateProperties(state, {
|
||||
/**
|
||||
* The password with which the conference is to be joined.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
password: action.password,
|
||||
passwordRequired: undefined
|
||||
}));
|
||||
// Technically, there're multiple values which don't represent
|
||||
// valid room names. Practically, each of them is as bad as the rest
|
||||
// of them because we can't use any of them to join a conference.
|
||||
if (!isRoomValid(room)) {
|
||||
room = INITIAL_STATE.room;
|
||||
}
|
||||
if (state.room !== room) {
|
||||
return {
|
||||
...state,
|
||||
room
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action SET_ROOM of the feature base/conference.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/conference.
|
||||
* @param {Action} action - The Redux action SET_ROOM to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature base/conference after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _setRoom(state, action) {
|
||||
let room = action.room;
|
||||
|
||||
if (isRoomValid(room)) {
|
||||
// XXX Lib-jitsi-meet does not accept uppercase letters.
|
||||
room = room.toLowerCase();
|
||||
} else {
|
||||
// Technically, there are multiple values which don't represent valid
|
||||
// room names. Practically, each of them is as bad as the rest of them
|
||||
// because we can't use any of them to join a conference.
|
||||
room = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the room of the conference (to be) joined.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
return setStateProperty(state, 'room', room);
|
||||
}
|
||||
return state;
|
||||
});
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import { Symbol } from '../react';
|
||||
|
||||
/**
|
||||
* Action type to signal that connection has disconnected.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const CONNECTION_DISCONNECTED = Symbol('CONNECTION_DISCONNECTED');
|
||||
export const CONNECTION_DISCONNECTED = 'CONNECTION_DISCONNECTED';
|
||||
|
||||
/**
|
||||
* Action type to signal that have successfully established a connection.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const CONNECTION_ESTABLISHED = Symbol('CONNECTION_ESTABLISHED');
|
||||
export const CONNECTION_ESTABLISHED = 'CONNECTION_ESTABLISHED';
|
||||
|
||||
/**
|
||||
* Action type to signal a connection failed.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const CONNECTION_FAILED = Symbol('CONNECTION_FAILED');
|
||||
export const CONNECTION_FAILED = 'CONNECTION_FAILED';
|
||||
|
||||
/**
|
||||
* Action to signal to change connection domain.
|
||||
@@ -23,4 +27,4 @@ export const CONNECTION_FAILED = Symbol('CONNECTION_FAILED');
|
||||
* domain: string
|
||||
* }
|
||||
*/
|
||||
export const SET_DOMAIN = Symbol('SET_DOMAIN');
|
||||
export const SET_DOMAIN = 'SET_DOMAIN';
|
||||
|
||||
209
react/features/base/connection/actions.js
Normal file
209
react/features/base/connection/actions.js
Normal file
@@ -0,0 +1,209 @@
|
||||
import {
|
||||
conferenceWillLeave,
|
||||
createConference
|
||||
} from '../conference';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import {
|
||||
CONNECTION_DISCONNECTED,
|
||||
CONNECTION_ESTABLISHED,
|
||||
CONNECTION_FAILED,
|
||||
SET_DOMAIN
|
||||
} from './actionTypes';
|
||||
import './reducer';
|
||||
|
||||
const JitsiConnectionEvents = JitsiMeetJS.events.connection;
|
||||
|
||||
/**
|
||||
* Opens new connection.
|
||||
*
|
||||
* @returns {Promise<JitsiConnection>}
|
||||
*/
|
||||
export function connect() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const connectionOpts
|
||||
= state['features/base/connection'].connectionOptions;
|
||||
const room = state['features/base/conference'].room;
|
||||
const connection = new JitsiMeetJS.JitsiConnection(
|
||||
connectionOpts.appId,
|
||||
connectionOpts.token,
|
||||
{
|
||||
...connectionOpts,
|
||||
bosh: connectionOpts.bosh + (
|
||||
room ? `?room=${room}` : ''
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
|
||||
handleConnectionDisconnected);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
handleConnectionEstablished);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
handleConnectionFailed);
|
||||
|
||||
connection.connect();
|
||||
|
||||
/**
|
||||
* Dispatches CONNECTION_DISCONNECTED action when connection is
|
||||
* disconnected.
|
||||
*
|
||||
* @param {string} message - Disconnect reason.
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleConnectionDisconnected(message) {
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
|
||||
handleConnectionDisconnected);
|
||||
|
||||
dispatch(_connectionDisconnected(connection, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves external promise when connection is established.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleConnectionEstablished() {
|
||||
unsubscribe();
|
||||
resolve(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects external promise when connection fails.
|
||||
*
|
||||
* @param {JitsiConnectionErrors} err - Connection error.
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleConnectionFailed(err) {
|
||||
unsubscribe();
|
||||
console.error('CONNECTION FAILED:', err);
|
||||
reject(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes connection instance from CONNECTION_ESTABLISHED
|
||||
* and CONNECTION_FAILED events.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function unsubscribe() {
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
handleConnectionEstablished
|
||||
);
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
handleConnectionFailed
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(err => dispatch(_connectionFailed(err)))
|
||||
.then(con => dispatch(_connectionEstablished(con)))
|
||||
.then(() => dispatch(createConference()));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes connection.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function disconnect() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const conference = state['features/base/conference'].jitsiConference;
|
||||
const connection = state['features/base/connection'].jitsiConnection;
|
||||
|
||||
let promise;
|
||||
|
||||
// Leave the conference.
|
||||
if (conference) {
|
||||
// In a fashion similar to JitsiConference's CONFERENCE_LEFT event
|
||||
// (and the respective Redux action) which is fired after the
|
||||
// conference has been left, notify the application about the
|
||||
// intention to leave the conference.
|
||||
dispatch(conferenceWillLeave(conference));
|
||||
|
||||
promise = conference.leave();
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
|
||||
// Disconnect the connection.
|
||||
if (connection) {
|
||||
promise = promise.then(() => connection.disconnect());
|
||||
}
|
||||
|
||||
return promise;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets connection domain.
|
||||
*
|
||||
* @param {string} domain - Domain name.
|
||||
* @returns {{
|
||||
* type: SET_DOMAIN,
|
||||
* domain: string
|
||||
* }}
|
||||
*/
|
||||
export function setDomain(domain) {
|
||||
return {
|
||||
type: SET_DOMAIN,
|
||||
domain
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when the signaling connection has been lost.
|
||||
*
|
||||
* @param {JitsiConnection} connection - The JitsiConnection which was
|
||||
* disconnected.
|
||||
* @param {string} message - Error message.
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: CONNECTION_DISCONNECTED,
|
||||
* connection: JitsiConnection,
|
||||
* message: string
|
||||
* }}
|
||||
*/
|
||||
function _connectionDisconnected(connection, message) {
|
||||
return {
|
||||
type: CONNECTION_DISCONNECTED,
|
||||
connection,
|
||||
message
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when the signaling connection has been established.
|
||||
*
|
||||
* @param {JitsiConnection} connection - JitsiConnection instance.
|
||||
* @private
|
||||
* @returns {{type: CONNECTION_ESTABLISHED, connection: JitsiConnection}}
|
||||
*/
|
||||
function _connectionEstablished(connection) {
|
||||
return {
|
||||
type: CONNECTION_ESTABLISHED,
|
||||
connection
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when the signaling connection could not be created.
|
||||
*
|
||||
* @param {string} error - Error message.
|
||||
* @private
|
||||
* @returns {{type: CONNECTION_FAILED, error: string}}
|
||||
*/
|
||||
function _connectionFailed(error) {
|
||||
return {
|
||||
type: CONNECTION_FAILED,
|
||||
error
|
||||
};
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
import { conferenceWillLeave } from '../conference';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
|
||||
import {
|
||||
CONNECTION_DISCONNECTED,
|
||||
CONNECTION_ESTABLISHED,
|
||||
CONNECTION_FAILED,
|
||||
SET_DOMAIN
|
||||
} from './actionTypes';
|
||||
import './reducer';
|
||||
|
||||
const JitsiConnectionEvents = JitsiMeetJS.events.connection;
|
||||
|
||||
/**
|
||||
* Opens new connection.
|
||||
*
|
||||
* @returns {Promise<JitsiConnection>}
|
||||
*/
|
||||
export function connect() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const connectionOptions
|
||||
= state['features/base/connection'].connectionOptions;
|
||||
const room = state['features/base/conference'].room;
|
||||
const connection
|
||||
= new JitsiMeetJS.JitsiConnection(
|
||||
connectionOptions.appId,
|
||||
connectionOptions.token,
|
||||
{
|
||||
...connectionOptions,
|
||||
bosh: connectionOptions.bosh + (room ? `?room=${room}` : '')
|
||||
});
|
||||
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
|
||||
connectionDisconnected);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
connectionEstablished);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
connectionFailed);
|
||||
|
||||
connection.connect();
|
||||
|
||||
/**
|
||||
* Dispatches CONNECTION_DISCONNECTED action when connection is
|
||||
* disconnected.
|
||||
*
|
||||
* @param {string} message - Disconnect reason.
|
||||
* @returns {void}
|
||||
*/
|
||||
function connectionDisconnected(message) {
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
|
||||
connectionDisconnected);
|
||||
|
||||
dispatch(_connectionDisconnected(connection, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves external promise when connection is established.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function connectionEstablished() {
|
||||
unsubscribe();
|
||||
dispatch(_connectionEstablished(connection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects external promise when connection fails.
|
||||
*
|
||||
* @param {JitsiConnectionErrors} err - Connection error.
|
||||
* @returns {void}
|
||||
*/
|
||||
function connectionFailed(err) {
|
||||
unsubscribe();
|
||||
console.error('CONNECTION FAILED:', err);
|
||||
dispatch(_connectionFailed(connection, err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes connection instance from CONNECTION_ESTABLISHED
|
||||
* and CONNECTION_FAILED events.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function unsubscribe() {
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
connectionEstablished);
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
connectionFailed);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes connection.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function disconnect() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const conference = state['features/base/conference'].conference;
|
||||
const connection = state['features/base/connection'].connection;
|
||||
|
||||
let promise;
|
||||
|
||||
// Leave the conference.
|
||||
if (conference) {
|
||||
// In a fashion similar to JitsiConference's CONFERENCE_LEFT event
|
||||
// (and the respective Redux action) which is fired after the
|
||||
// conference has been left, notify the application about the
|
||||
// intention to leave the conference.
|
||||
dispatch(conferenceWillLeave(conference));
|
||||
|
||||
promise = conference.leave();
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
|
||||
// Disconnect the connection.
|
||||
if (connection) {
|
||||
promise = promise.then(() => connection.disconnect());
|
||||
}
|
||||
|
||||
return promise;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets connection domain.
|
||||
*
|
||||
* @param {string} domain - Domain name.
|
||||
* @returns {{
|
||||
* type: SET_DOMAIN,
|
||||
* domain: string
|
||||
* }}
|
||||
*/
|
||||
export function setDomain(domain) {
|
||||
return {
|
||||
type: SET_DOMAIN,
|
||||
domain
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when the signaling connection has been lost.
|
||||
*
|
||||
* @param {JitsiConnection} connection - The JitsiConnection which disconnected.
|
||||
* @param {string} message - Error message.
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: CONNECTION_DISCONNECTED,
|
||||
* connection: JitsiConnection,
|
||||
* message: string
|
||||
* }}
|
||||
*/
|
||||
function _connectionDisconnected(connection, message) {
|
||||
return {
|
||||
type: CONNECTION_DISCONNECTED,
|
||||
connection,
|
||||
message
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when the signaling connection has been established.
|
||||
*
|
||||
* @param {JitsiConnection} connection - The JitsiConnection which was
|
||||
* established.
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: CONNECTION_ESTABLISHED,
|
||||
* connection: JitsiConnection
|
||||
* }}
|
||||
*/
|
||||
function _connectionEstablished(connection) {
|
||||
return {
|
||||
type: CONNECTION_ESTABLISHED,
|
||||
connection
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when the signaling connection could not be created.
|
||||
*
|
||||
* @param {JitsiConnection} connection - The JitsiConnection which failed.
|
||||
* @param {string} error - Error message.
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: CONNECTION_FAILED,
|
||||
* connection: JitsiConnection,
|
||||
* error: string
|
||||
* }}
|
||||
*/
|
||||
function _connectionFailed(connection, error) {
|
||||
return {
|
||||
type: CONNECTION_FAILED,
|
||||
connection,
|
||||
error
|
||||
};
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/* global APP, JitsiMeetJS */
|
||||
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
import { SET_DOMAIN } from './actionTypes';
|
||||
import './reducer';
|
||||
|
||||
const JitsiConferenceEvents = JitsiMeetJS.events.conference;
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Opens new connection.
|
||||
*
|
||||
* @returns {Promise<JitsiConnection>}
|
||||
*/
|
||||
export function connect() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const room = state['features/base/conference'].room;
|
||||
|
||||
// XXX For web based version we use conference initialization logic
|
||||
// from the old app (at the moment of writing).
|
||||
return APP.conference.init({ roomName: room }).then(() => {
|
||||
if (APP.logCollector) {
|
||||
// Start the LogCollector's periodic "store logs" task
|
||||
APP.logCollector.start();
|
||||
APP.logCollectorStarted = true;
|
||||
|
||||
// Make an attempt to flush in case a lot of logs have been
|
||||
// cached, before the collector was started.
|
||||
APP.logCollector.flush();
|
||||
|
||||
// This event listener will flush the logs, before
|
||||
// the statistics module (CallStats) is stopped.
|
||||
//
|
||||
// NOTE The LogCollector is not stopped, because this event can
|
||||
// be triggered multiple times during single conference
|
||||
// (whenever statistics module is stopped). That includes
|
||||
// the case when Jicofo terminates the single person left in the
|
||||
// room. It will then restart the media session when someone
|
||||
// eventually join the room which will start the stats again.
|
||||
APP.conference.addConferenceListener(
|
||||
JitsiConferenceEvents.BEFORE_STATISTICS_DISPOSED,
|
||||
() => {
|
||||
if (APP.logCollector) {
|
||||
APP.logCollector.flush();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
APP.UI.initConference();
|
||||
|
||||
APP.UI.addListener(UIEvents.LANG_CHANGED, language => {
|
||||
APP.translation.setLanguage(language);
|
||||
APP.settings.setLanguage(language);
|
||||
});
|
||||
|
||||
APP.keyboardshortcut.init();
|
||||
})
|
||||
.catch(err => {
|
||||
APP.UI.hideRingOverLay();
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
logger.error(err);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes connection.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function disconnect() {
|
||||
// XXX For web based version we use conference hanging up logic from the old
|
||||
// app.
|
||||
return () => APP.conference.hangup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets connection domain.
|
||||
*
|
||||
* @param {string} domain - Domain name.
|
||||
* @returns {{
|
||||
* type: SET_DOMAIN,
|
||||
* domain: string
|
||||
* }}
|
||||
*/
|
||||
export function setDomain(domain) {
|
||||
return {
|
||||
type: SET_DOMAIN,
|
||||
domain
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ReducerRegistry, setStateProperty } from '../redux';
|
||||
import { ReducerRegistry } from '../redux';
|
||||
|
||||
import {
|
||||
CONNECTION_DISCONNECTED,
|
||||
@@ -7,64 +7,62 @@ import {
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Reduces the Redux actions of the feature base/connection.
|
||||
* Initial Redux state.
|
||||
*
|
||||
* @type {{
|
||||
* jitsiConnection: (JitsiConnection|null),
|
||||
* connectionOptions: Object
|
||||
* }}
|
||||
*/
|
||||
ReducerRegistry.register('features/base/connection', (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case CONNECTION_DISCONNECTED:
|
||||
return _connectionDisconnected(state, action);
|
||||
|
||||
case CONNECTION_ESTABLISHED:
|
||||
return _connectionEstablished(state, action);
|
||||
|
||||
case SET_DOMAIN:
|
||||
return _setDomain(state, action);
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
const INITIAL_STATE = {
|
||||
jitsiConnection: null,
|
||||
connectionOptions: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action CONNECTION_DISCONNECTED of the feature
|
||||
* base/connection.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/connection.
|
||||
* @param {Action} action - The Redux action CONNECTION_DISCONNECTED to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature base/connection after the
|
||||
* reduction of the specified action.
|
||||
* Listen for actions that contain the connection object, so that
|
||||
* it can be stored for use by other action creators.
|
||||
*/
|
||||
function _connectionDisconnected(state, action) {
|
||||
if (state.connection === action.connection) {
|
||||
return setStateProperty(state, 'connection', undefined);
|
||||
}
|
||||
ReducerRegistry.register('features/base/connection',
|
||||
(state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case CONNECTION_DISCONNECTED:
|
||||
if (state.jitsiConnection === action.connection) {
|
||||
return {
|
||||
...state,
|
||||
jitsiConnection: null
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
|
||||
case CONNECTION_ESTABLISHED:
|
||||
return {
|
||||
...state,
|
||||
jitsiConnection: action.connection
|
||||
};
|
||||
|
||||
case SET_DOMAIN:
|
||||
return {
|
||||
...state,
|
||||
connectionOptions: {
|
||||
...state.connectionOptions,
|
||||
...buildConnectionOptions(action.domain)
|
||||
}
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action CONNECTION_ESTABLISHED of the feature
|
||||
* base/connection.
|
||||
* Builds connection options based on domain.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/connection.
|
||||
* @param {Action} action - The Redux action CONNECTION_ESTABLISHED to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature base/connection after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _connectionEstablished(state, action) {
|
||||
return setStateProperty(state, 'connection', action.connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs options to be passed to the constructor of JitsiConnection based
|
||||
* on a specific domain.
|
||||
*
|
||||
* @param {string} domain - The domain with which the returned options are to be
|
||||
* populated.
|
||||
* @param {string} domain - Domain name.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _constructConnectionOptions(domain) {
|
||||
function buildConnectionOptions(domain) {
|
||||
// FIXME The HTTPS scheme for the BOSH URL works with meet.jit.si on both
|
||||
// mobile & Web. It also works with beta.meet.jit.si on Web. Unfortunately,
|
||||
// it doesn't work with beta.meet.jit.si on mobile. Temporarily, use the
|
||||
@@ -81,11 +79,15 @@ function _constructConnectionOptions(domain) {
|
||||
boshProtocol = windowLocation.protocol;
|
||||
}
|
||||
}
|
||||
boshProtocol || (boshProtocol = 'http:');
|
||||
if (!boshProtocol) {
|
||||
boshProtocol = 'http:';
|
||||
}
|
||||
}
|
||||
|
||||
// Default to the HTTPS scheme for the BOSH URL.
|
||||
boshProtocol || (boshProtocol = 'https:');
|
||||
if (!boshProtocol) {
|
||||
boshProtocol = 'https:';
|
||||
}
|
||||
|
||||
return {
|
||||
bosh: `${boshProtocol}//${domain}/http-bind`,
|
||||
@@ -96,22 +98,3 @@ function _constructConnectionOptions(domain) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action SET_DOMAIN of the feature base/connection.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/connection.
|
||||
* @param {Action} action - The Redux action SET_DOMAIN to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature base/connection after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _setDomain(state, action) {
|
||||
return {
|
||||
...state,
|
||||
connectionOptions: {
|
||||
...state.connectionOptions,
|
||||
..._constructConnectionOptions(action.domain)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,3 @@
|
||||
import { Symbol } from '../react';
|
||||
|
||||
/**
|
||||
* Action to signal that lib-jitsi-meet library was disposed.
|
||||
*
|
||||
@@ -7,7 +5,7 @@ import { Symbol } from '../react';
|
||||
* type: LIB_DISPOSED
|
||||
* }
|
||||
*/
|
||||
export const LIB_DISPOSED = Symbol('LIB_DISPOSED');
|
||||
export const LIB_DISPOSED = 'LIB_DISPOSED';
|
||||
|
||||
/**
|
||||
* Action to signal that lib-jitsi-meet initialized failed with error.
|
||||
@@ -17,7 +15,7 @@ export const LIB_DISPOSED = Symbol('LIB_DISPOSED');
|
||||
* error: Error
|
||||
* }
|
||||
*/
|
||||
export const LIB_INIT_ERROR = Symbol('LIB_INIT_ERROR');
|
||||
export const LIB_INIT_ERROR = 'LIB_INIT_ERROR';
|
||||
|
||||
/**
|
||||
* Action to signal that lib-jitsi-meet initialization succeeded.
|
||||
@@ -26,7 +24,7 @@ export const LIB_INIT_ERROR = Symbol('LIB_INIT_ERROR');
|
||||
* type: LIB_INITIALIZED
|
||||
* }
|
||||
*/
|
||||
export const LIB_INITIALIZED = Symbol('LIB_INITIALIZED');
|
||||
export const LIB_INITIALIZED = 'LIB_INITIALIZED';
|
||||
|
||||
/**
|
||||
* Action to signal that config was set.
|
||||
@@ -36,4 +34,4 @@ export const LIB_INITIALIZED = Symbol('LIB_INITIALIZED');
|
||||
* config: Object
|
||||
* }
|
||||
*/
|
||||
export const SET_CONFIG = Symbol('SET_CONFIG');
|
||||
export const SET_CONFIG = 'SET_CONFIG';
|
||||
|
||||
@@ -40,12 +40,6 @@ export function initLib() {
|
||||
throw new Error('Cannot initialize lib-jitsi-meet without config');
|
||||
}
|
||||
|
||||
// XXX Temporarily until conference.js is moved to the React app we
|
||||
// shouldn't use JitsiMeetJS from the React app.
|
||||
if (typeof APP !== 'undefined') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return JitsiMeetJS.init(config)
|
||||
.then(() => dispatch({ type: LIB_INITIALIZED }))
|
||||
.catch(error => {
|
||||
|
||||
@@ -4,18 +4,11 @@ import { loadScript } from '../../base/util';
|
||||
* Loads config.js file from remote server.
|
||||
*
|
||||
* @param {string} host - Host where config.js is hosted.
|
||||
* @param {string} path='/config.js' - Relative pah to config.js file.
|
||||
* @param {string} configLocation='/config.js' - Relative pah to config.js file.
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export function loadConfig(host, path = '/config.js') {
|
||||
// Returns config.js file from global scope. We can't use the version that's
|
||||
// being used for the React Native app because the old/current Web app uses
|
||||
// config from the global scope.
|
||||
if (typeof APP !== 'undefined') {
|
||||
return Promise.resolve(window.config);
|
||||
}
|
||||
|
||||
return loadScript(new URL(path, host).toString())
|
||||
export function loadConfig(host, configLocation = '/config.js') {
|
||||
return loadScript(new URL(configLocation, host).toString())
|
||||
.then(() => {
|
||||
const config = window.config;
|
||||
|
||||
@@ -28,9 +21,9 @@ export function loadConfig(host, path = '/config.js') {
|
||||
|
||||
return config;
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(`Failed to load ${path} from ${host}`, err);
|
||||
.catch(error => {
|
||||
console.error('Failed to load config.js from remote server', error);
|
||||
|
||||
throw err;
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,317 +0,0 @@
|
||||
import { Platform } from 'react-native';
|
||||
import {
|
||||
RTCPeerConnection,
|
||||
RTCSessionDescription
|
||||
} from 'react-native-webrtc';
|
||||
|
||||
import { POSIX } from '../../react-native';
|
||||
|
||||
// XXX At the time of this writing extending RTCPeerConnection using ES6 'class'
|
||||
// and 'extends' causes a runtime error related to the attempt to define the
|
||||
// onaddstream property setter. The error mentions that babelHelpers.set is
|
||||
// undefined which appears to be a thing inside React Native's packager. As a
|
||||
// workaround, extend using the pre-ES6 way.
|
||||
|
||||
/**
|
||||
* The RTCPeerConnection provided by react-native-webrtc fires onaddstream
|
||||
* before it remembers remotedescription (and thus makes it available to API
|
||||
* clients). Because that appears to be a problem for lib-jitsi-meet which has
|
||||
* been successfully running on Chrome, Firefox, Temasys, etc. for a very long
|
||||
* time, attempt to meets its expectations (by extending RTCPPeerConnection).
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
export default function _RTCPeerConnection(...args) {
|
||||
|
||||
/* eslint-disable no-invalid-this */
|
||||
|
||||
RTCPeerConnection.apply(this, args);
|
||||
|
||||
this.onaddstream = (...args) => // eslint-disable-line no-shadow
|
||||
(this._onaddstreamQueue
|
||||
? this._queueOnaddstream
|
||||
: this._invokeOnaddstream)
|
||||
.apply(this, args);
|
||||
|
||||
// Shadow RTCPeerConnection's onaddstream but after _RTCPeerConnection has
|
||||
// assigned to the property in question. Defining the property on
|
||||
// _RTCPeerConnection's prototype may (or may not, I don't know) work but I
|
||||
// don't want to try because the following approach appears to work and I
|
||||
// understand it.
|
||||
Object.defineProperty(this, 'onaddstream', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
return this._onaddstream;
|
||||
},
|
||||
set(value) {
|
||||
this._onaddstream = value;
|
||||
}
|
||||
});
|
||||
|
||||
/* eslint-enable no-invalid-this */
|
||||
}
|
||||
|
||||
_RTCPeerConnection.prototype = Object.create(RTCPeerConnection.prototype);
|
||||
_RTCPeerConnection.prototype.constructor = _RTCPeerConnection;
|
||||
|
||||
_RTCPeerConnection.prototype._invokeOnaddstream = function(...args) {
|
||||
const onaddstream = this._onaddstream;
|
||||
|
||||
return onaddstream && onaddstream.apply(this, args);
|
||||
};
|
||||
|
||||
_RTCPeerConnection.prototype._invokeQueuedOnaddstream = function(q) {
|
||||
q && q.forEach(args => {
|
||||
try {
|
||||
this._invokeOnaddstream(...args);
|
||||
} catch (e) {
|
||||
// TODO Determine whether the combination of the standard
|
||||
// setRemoteDescription and onaddstream results in a similar
|
||||
// swallowing of errors.
|
||||
_LOGE(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
_RTCPeerConnection.prototype._queueOnaddstream = function(...args) {
|
||||
this._onaddstreamQueue.push(Array.from(args));
|
||||
};
|
||||
|
||||
_RTCPeerConnection.prototype.setRemoteDescription = function(
|
||||
sessionDescription,
|
||||
successCallback,
|
||||
errorCallback) {
|
||||
// If the deprecated callback-based version is used, translate it to the
|
||||
// Promise-based version.
|
||||
if (typeof successCallback !== 'undefined'
|
||||
|| typeof errorCallback !== 'undefined') {
|
||||
// XXX Returning a Promise is not necessary. But I don't see why it'd
|
||||
// hurt (much).
|
||||
return (
|
||||
_RTCPeerConnection.prototype.setRemoteDescription.call(
|
||||
this,
|
||||
sessionDescription)
|
||||
.then(successCallback, errorCallback));
|
||||
}
|
||||
|
||||
return (
|
||||
_synthesizeIPv6Addresses(sessionDescription)
|
||||
.catch(reason => {
|
||||
reason && _LOGE(reason);
|
||||
|
||||
return sessionDescription;
|
||||
})
|
||||
.then(value => _setRemoteDescription.bind(this)(value)));
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs at error level.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function _LOGE(...args) {
|
||||
console && console.error && console.error(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts react-native-webrtc's {@link RTCPeerConnection#setRemoteDescription}
|
||||
* implementation which uses the deprecated, callback-based version to the
|
||||
* <tt>Promise</tt>-based version.
|
||||
*
|
||||
* @param {RTCSessionDescription} sessionDescription - The RTCSessionDescription
|
||||
* which specifies the configuration of the remote end of the connection.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function _setRemoteDescription(sessionDescription) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
/* eslint-disable no-invalid-this */
|
||||
|
||||
// Ensure I'm not remembering onaddstream invocations from previous
|
||||
// setRemoteDescription calls. I shouldn't be but... anyway.
|
||||
this._onaddstreamQueue = [];
|
||||
|
||||
RTCPeerConnection.prototype.setRemoteDescription.call(
|
||||
this,
|
||||
sessionDescription,
|
||||
(...args) => {
|
||||
let q;
|
||||
|
||||
try {
|
||||
resolve(...args);
|
||||
} finally {
|
||||
q = this._onaddstreamQueue;
|
||||
this._onaddstreamQueue = undefined;
|
||||
}
|
||||
|
||||
this._invokeQueuedOnaddstream(q);
|
||||
},
|
||||
(...args) => {
|
||||
this._onaddstreamQueue = undefined;
|
||||
|
||||
reject(...args);
|
||||
});
|
||||
|
||||
/* eslint-enable no-invalid-this */
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize IPv6 addresses on iOS in order to support IPv6 NAT64 networks.
|
||||
*
|
||||
* @param {RTCSessionDescription} sdp - The RTCSessionDescription which
|
||||
* specifies the configuration of the remote end of the connection.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function _synthesizeIPv6Addresses(sdp) {
|
||||
// The synthesis of IPv6 addresses is implemented on iOS only at the time of
|
||||
// this writing.
|
||||
if (Platform.OS !== 'ios') {
|
||||
return Promise.resolve(sdp);
|
||||
}
|
||||
|
||||
return (
|
||||
new Promise(resolve => resolve(_synthesizeIPv6Addresses0(sdp)))
|
||||
.then(({ ips, lines }) =>
|
||||
Promise.all(Array.from(ips.values()))
|
||||
.then(() => _synthesizeIPv6Addresses1(sdp, ips, lines))
|
||||
));
|
||||
}
|
||||
|
||||
/* eslint-disable max-depth */
|
||||
|
||||
/**
|
||||
* Implements the initial phase of the synthesis of IPv6 addresses.
|
||||
*
|
||||
* @param {RTCSessionDescription} sessionDescription - The RTCSessionDescription
|
||||
* for which IPv6 addresses will be synthesized.
|
||||
* @returns {{
|
||||
* ips: Map,
|
||||
* lines: Array
|
||||
* }}
|
||||
*/
|
||||
function _synthesizeIPv6Addresses0(sessionDescription) {
|
||||
const sdp = sessionDescription.sdp;
|
||||
let start = 0;
|
||||
const lines = [];
|
||||
const ips = new Map();
|
||||
|
||||
do {
|
||||
const end = sdp.indexOf('\r\n', start);
|
||||
let line;
|
||||
|
||||
if (end === -1) {
|
||||
line = sdp.substring(start);
|
||||
|
||||
// Break out of the loop at the end of the iteration.
|
||||
start = undefined;
|
||||
} else {
|
||||
line = sdp.substring(start, end);
|
||||
start = end + 2;
|
||||
}
|
||||
|
||||
if (line.startsWith('a=candidate:')) {
|
||||
const candidate = line.split(' ');
|
||||
|
||||
if (candidate.length >= 10 && candidate[6] === 'typ') {
|
||||
const ip4s = [ candidate[4] ];
|
||||
let abort = false;
|
||||
|
||||
for (let i = 8; i < candidate.length; ++i) {
|
||||
if (candidate[i] === 'raddr') {
|
||||
ip4s.push(candidate[++i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const ip of ip4s) {
|
||||
if (ip.indexOf(':') === -1) {
|
||||
ips.has(ip)
|
||||
|| ips.set(ip, new Promise((resolve, reject) => {
|
||||
const v = ips.get(ip);
|
||||
|
||||
if (v && typeof v === 'string') {
|
||||
resolve(v);
|
||||
} else {
|
||||
POSIX.getaddrinfo(ip).then(
|
||||
value => {
|
||||
if (value.indexOf(':') === -1
|
||||
|| value === ips.get(ip)) {
|
||||
ips.delete(ip);
|
||||
} else {
|
||||
ips.set(ip, value);
|
||||
}
|
||||
resolve(value);
|
||||
},
|
||||
reject);
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
abort = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (abort) {
|
||||
ips.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
line = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(line);
|
||||
} while (start);
|
||||
|
||||
return {
|
||||
ips,
|
||||
lines
|
||||
};
|
||||
}
|
||||
|
||||
/* eslint-enable max-depth */
|
||||
|
||||
/**
|
||||
* Implements the initial phase of the synthesis of IPv6 addresses.
|
||||
*
|
||||
* @param {RTCSessionDescription} sessionDescription - The RTCSessionDescription
|
||||
* for which IPv6 addresses are being synthesized.
|
||||
* @param {Map} ips - A Map of IPv4 addresses found in the specified
|
||||
* sessionDescription to synthesized IPv6 addresses.
|
||||
* @param {Array} lines - The lines of the specified sessionDescription.
|
||||
* @returns {RTCSessionDescription} A RTCSessionDescription that represents the
|
||||
* result of the synthesis of IPv6 addresses.
|
||||
*/
|
||||
function _synthesizeIPv6Addresses1(sessionDescription, ips, lines) {
|
||||
if (ips.size === 0) {
|
||||
return sessionDescription;
|
||||
}
|
||||
|
||||
for (let l = 0; l < lines.length; ++l) {
|
||||
const candidate = lines[l];
|
||||
|
||||
if (typeof candidate !== 'string') {
|
||||
let ip4 = candidate[4];
|
||||
let ip6 = ips.get(ip4);
|
||||
|
||||
ip6 && (candidate[4] = ip6);
|
||||
|
||||
for (let i = 8; i < candidate.length; ++i) {
|
||||
if (candidate[i] === 'raddr') {
|
||||
ip4 = candidate[++i];
|
||||
(ip6 = ips.get(ip4)) && (candidate[i] = ip6);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lines[l] = candidate.join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
return new RTCSessionDescription({
|
||||
sdp: lines.join('\r\n'),
|
||||
type: sessionDescription.type
|
||||
});
|
||||
}
|
||||
@@ -85,9 +85,6 @@ function _visitNode(node, callback) {
|
||||
|
||||
(global => {
|
||||
|
||||
// Polyfill for URL constructor
|
||||
require('url-polyfill');
|
||||
|
||||
const DOMParser = require('xmldom').DOMParser;
|
||||
|
||||
// addEventListener
|
||||
@@ -213,6 +210,30 @@ function _visitNode(node, callback) {
|
||||
};
|
||||
}
|
||||
|
||||
// performance
|
||||
if (typeof global.performance === 'undefined') {
|
||||
global.performance = {
|
||||
now() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// sessionStorage
|
||||
//
|
||||
// Required by:
|
||||
// - Strophe
|
||||
if (typeof global.sessionStorage === 'undefined') {
|
||||
global.sessionStorage = {
|
||||
/* eslint-disable no-empty-function */
|
||||
getItem() {},
|
||||
removeItem() {},
|
||||
setItem() {}
|
||||
|
||||
/* eslint-enable no-empty-function */
|
||||
};
|
||||
}
|
||||
|
||||
const navigator = global.navigator;
|
||||
|
||||
if (navigator) {
|
||||
@@ -259,30 +280,6 @@ function _visitNode(node, callback) {
|
||||
})();
|
||||
}
|
||||
|
||||
// performance
|
||||
if (typeof global.performance === 'undefined') {
|
||||
global.performance = {
|
||||
now() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// sessionStorage
|
||||
//
|
||||
// Required by:
|
||||
// - Strophe
|
||||
if (typeof global.sessionStorage === 'undefined') {
|
||||
global.sessionStorage = {
|
||||
/* eslint-disable no-empty-function */
|
||||
getItem() {},
|
||||
removeItem() {},
|
||||
setItem() {}
|
||||
|
||||
/* eslint-enable no-empty-function */
|
||||
};
|
||||
}
|
||||
|
||||
// WebRTC
|
||||
require('./polyfills-webrtc');
|
||||
|
||||
@@ -313,4 +310,7 @@ function _visitNode(node, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
// Polyfill for URL constructor
|
||||
require('url-polyfill');
|
||||
|
||||
})(global || window || this); // eslint-disable-line no-invalid-this
|
||||
|
||||
@@ -1,21 +1,144 @@
|
||||
import {
|
||||
MediaStream,
|
||||
MediaStreamTrack,
|
||||
RTCSessionDescription,
|
||||
getUserMedia
|
||||
} from 'react-native-webrtc';
|
||||
|
||||
import RTCPeerConnection from './RTCPeerConnection';
|
||||
|
||||
(global => {
|
||||
const {
|
||||
MediaStream,
|
||||
MediaStreamTrack,
|
||||
RTCPeerConnection,
|
||||
RTCSessionDescription,
|
||||
getUserMedia
|
||||
} = require('react-native-webrtc');
|
||||
|
||||
if (typeof global.webkitMediaStream === 'undefined') {
|
||||
global.webkitMediaStream = MediaStream;
|
||||
}
|
||||
|
||||
if (typeof global.MediaStreamTrack === 'undefined') {
|
||||
global.MediaStreamTrack = MediaStreamTrack;
|
||||
}
|
||||
|
||||
if (typeof global.webkitRTCPeerConnection === 'undefined') {
|
||||
global.webkitRTCPeerConnection = RTCPeerConnection;
|
||||
// XXX At the time of this writing extending RTCPeerConnection using ES6
|
||||
// 'class' and 'extends' causes a runtime error related to the attempt
|
||||
// to define the onaddstream property setter. The error mentions that
|
||||
// babelHelpers.set is undefined which appears to be a thing inside
|
||||
// React Native's packager. As a workaround, extend using the pre-ES6
|
||||
// way.
|
||||
|
||||
/* eslint-disable no-inner-declarations */
|
||||
|
||||
/**
|
||||
* The RTCPeerConnection provided by react-native-webrtc fires
|
||||
* onaddstream before it remembers remotedescription (and thus makes it
|
||||
* available to API clients). Because that appears to be a problem for
|
||||
* lib-jitsi-meet which has been successfully running
|
||||
* on Chrome, Firefox, Temasys, etc. for a very long time, attempt to
|
||||
* meets its expectations (by extending RTCPPeerConnection).
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
function _RTCPeerConnection(...args) {
|
||||
|
||||
/* eslint-disable no-invalid-this */
|
||||
|
||||
RTCPeerConnection.apply(this, args);
|
||||
|
||||
this.onaddstream = (...args) => // eslint-disable-line no-shadow
|
||||
(this._onaddstreamQueue
|
||||
? this._queueOnaddstream
|
||||
: this._invokeOnaddstream)
|
||||
.apply(this, args);
|
||||
|
||||
// Shadow RTCPeerConnection's onaddstream but after
|
||||
// _RTCPeerConnection has assigned to the property in question.
|
||||
// Defining the property on _RTCPeerConnection's prototype may (or
|
||||
// may not, I don't know) work but I don't want to try because the
|
||||
// following approach appears to work and I understand it.
|
||||
Object.defineProperty(this, 'onaddstream', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
return this._onaddstream;
|
||||
},
|
||||
set(value) {
|
||||
this._onaddstream = value;
|
||||
}
|
||||
});
|
||||
|
||||
/* eslint-enable no-invalid-this */
|
||||
}
|
||||
|
||||
/* eslint-enable no-inner-declarations */
|
||||
|
||||
_RTCPeerConnection.prototype
|
||||
= Object.create(RTCPeerConnection.prototype);
|
||||
_RTCPeerConnection.prototype.constructor = _RTCPeerConnection;
|
||||
_RTCPeerConnection.prototype._invokeOnaddstream = function(...args) {
|
||||
const onaddstream = this._onaddstream;
|
||||
let r;
|
||||
|
||||
if (onaddstream) {
|
||||
r = onaddstream.apply(this, args);
|
||||
}
|
||||
|
||||
return r;
|
||||
};
|
||||
_RTCPeerConnection.prototype._invokeQueuedOnaddstream = function(q) {
|
||||
q && q.every(function(args) {
|
||||
try {
|
||||
this._invokeOnaddstream(...args);
|
||||
} catch (e) {
|
||||
// TODO Determine whether the combination of the standard
|
||||
// setRemoteDescription and onaddstream results in a similar
|
||||
// swallowing of errors.
|
||||
console && console.error && console.error(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}, this);
|
||||
};
|
||||
_RTCPeerConnection.prototype._queueOnaddstream = function(...args) {
|
||||
this._onaddstreamQueue.push(Array.from(args));
|
||||
};
|
||||
_RTCPeerConnection.prototype.setRemoteDescription
|
||||
= function(sessionDescription, successCallback, errorCallback) {
|
||||
// Ensure I'm not remembering onaddstream invocations from
|
||||
// previous setRemoteDescription calls. I shouldn't be but...
|
||||
// anyway.
|
||||
this._onaddstreamQueue = [];
|
||||
|
||||
return RTCPeerConnection.prototype.setRemoteDescription.call(
|
||||
this,
|
||||
sessionDescription,
|
||||
(...args) => {
|
||||
let r;
|
||||
let q;
|
||||
|
||||
try {
|
||||
if (successCallback) {
|
||||
r = successCallback(...args);
|
||||
}
|
||||
} finally {
|
||||
q = this._onaddstreamQueue;
|
||||
this._onaddstreamQueue = undefined;
|
||||
}
|
||||
|
||||
this._invokeQueuedOnaddstream(q);
|
||||
|
||||
return r;
|
||||
},
|
||||
(...args) => {
|
||||
let r;
|
||||
|
||||
this._onaddstreamQueue = undefined;
|
||||
|
||||
if (errorCallback) {
|
||||
r = errorCallback(...args);
|
||||
}
|
||||
|
||||
return r;
|
||||
});
|
||||
};
|
||||
|
||||
global.webkitRTCPeerConnection = _RTCPeerConnection;
|
||||
}
|
||||
if (typeof global.RTCSessionDescription === 'undefined') {
|
||||
global.RTCSessionDescription = RTCSessionDescription;
|
||||
|
||||
@@ -59,31 +59,15 @@ ReducerRegistry.register(
|
||||
};
|
||||
|
||||
case SET_CONFIG:
|
||||
return _setConfig(state, action);
|
||||
return {
|
||||
...state,
|
||||
config: {
|
||||
...action.config,
|
||||
...state.config
|
||||
}
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Reduces a specific Redux action SET_CONFIG of the feature
|
||||
* base/lib-jitsi-meet.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature base/lib-jitsi-meet.
|
||||
* @param {Action} action - The Redux action SET_CONFIG to reduce.
|
||||
* @private
|
||||
* @returns {Object} The new state of the feature base/lib-jitsi-meet after the
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _setConfig(state, action) {
|
||||
return {
|
||||
...state,
|
||||
config: {
|
||||
// The final config is the result of augmenting the default config
|
||||
// with whatever the deployment has chosen to override/overwrite.
|
||||
...INITIAL_STATE.config,
|
||||
...action.config
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Symbol } from '../react';
|
||||
|
||||
/**
|
||||
* Action to change muted state of the local audio.
|
||||
*
|
||||
@@ -8,7 +6,7 @@ import { Symbol } from '../react';
|
||||
* muted: boolean
|
||||
* }
|
||||
*/
|
||||
export const AUDIO_MUTED_CHANGED = Symbol('AUDIO_MUTED_CHANGED');
|
||||
export const AUDIO_MUTED_CHANGED = 'AUDIO_MUTED_CHANGED';
|
||||
|
||||
/**
|
||||
* Action to signal a change of the facing mode of the local video camera.
|
||||
@@ -18,7 +16,7 @@ export const AUDIO_MUTED_CHANGED = Symbol('AUDIO_MUTED_CHANGED');
|
||||
* cameraFacingMode: CAMERA_FACING_MODE
|
||||
* }
|
||||
*/
|
||||
export const CAMERA_FACING_MODE_CHANGED = Symbol('CAMERA_FACING_MODE_CHANGED');
|
||||
export const CAMERA_FACING_MODE_CHANGED = 'CAMERA_FACING_MODE_CHANGED';
|
||||
|
||||
/**
|
||||
* Action to change muted state of the local video.
|
||||
@@ -28,4 +26,4 @@ export const CAMERA_FACING_MODE_CHANGED = Symbol('CAMERA_FACING_MODE_CHANGED');
|
||||
* muted: boolean
|
||||
* }
|
||||
*/
|
||||
export const VIDEO_MUTED_CHANGED = Symbol('VIDEO_MUTED_CHANGED');
|
||||
export const VIDEO_MUTED_CHANGED = 'VIDEO_MUTED_CHANGED';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Symbol } from '../react';
|
||||
|
||||
/**
|
||||
* Create an action for when dominant speaker changes.
|
||||
*
|
||||
@@ -10,7 +8,7 @@ import { Symbol } from '../react';
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export const DOMINANT_SPEAKER_CHANGED = Symbol('DOMINANT_SPEAKER_CHANGED');
|
||||
export const DOMINANT_SPEAKER_CHANGED = 'DOMINANT_SPEAKER_CHANGED';
|
||||
|
||||
/**
|
||||
* Action to signal that ID of participant has changed. This happens when
|
||||
@@ -22,7 +20,7 @@ export const DOMINANT_SPEAKER_CHANGED = Symbol('DOMINANT_SPEAKER_CHANGED');
|
||||
* oldValue: string
|
||||
* }
|
||||
*/
|
||||
export const PARTICIPANT_ID_CHANGED = Symbol('PARTICIPANT_ID_CHANGED');
|
||||
export const PARTICIPANT_ID_CHANGED = 'PARTICIPANT_ID_CHANGED';
|
||||
|
||||
/**
|
||||
* Action to signal that a participant has joined.
|
||||
@@ -32,7 +30,7 @@ export const PARTICIPANT_ID_CHANGED = Symbol('PARTICIPANT_ID_CHANGED');
|
||||
* participant: Participant
|
||||
* }
|
||||
*/
|
||||
export const PARTICIPANT_JOINED = Symbol('PARTICIPANT_JOINED');
|
||||
export const PARTICIPANT_JOINED = 'PARTICIPANT_JOINED';
|
||||
|
||||
/**
|
||||
* Action to handle case when participant lefts.
|
||||
@@ -44,7 +42,7 @@ export const PARTICIPANT_JOINED = Symbol('PARTICIPANT_JOINED');
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export const PARTICIPANT_LEFT = Symbol('PARTICIPANT_LEFT');
|
||||
export const PARTICIPANT_LEFT = 'PARTICIPANT_LEFT';
|
||||
|
||||
/**
|
||||
* Action to handle case when info about participant changes.
|
||||
@@ -54,7 +52,7 @@ export const PARTICIPANT_LEFT = Symbol('PARTICIPANT_LEFT');
|
||||
* participant: Participant
|
||||
* }
|
||||
*/
|
||||
export const PARTICIPANT_UPDATED = Symbol('PARTICIPANT_UPDATED');
|
||||
export const PARTICIPANT_UPDATED = 'PARTICIPANT_UPDATED';
|
||||
|
||||
/**
|
||||
* The type of the Redux action which pins a conference participant.
|
||||
@@ -66,4 +64,4 @@ export const PARTICIPANT_UPDATED = Symbol('PARTICIPANT_UPDATED');
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export const PIN_PARTICIPANT = Symbol('PIN_PARTICIPANT');
|
||||
export const PIN_PARTICIPANT = 'PIN_PARTICIPANT';
|
||||
|
||||
@@ -17,7 +17,9 @@ import { LOCAL_PARTICIPANT_DEFAULT_ID } from './constants';
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED:
|
||||
store.dispatch(localParticipantIdChanged(action.conference.myUserId()));
|
||||
store.dispatch(
|
||||
localParticipantIdChanged(
|
||||
action.conference.jitsiConference.myUserId()));
|
||||
break;
|
||||
|
||||
case CONFERENCE_LEFT:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* global MD5 */
|
||||
|
||||
import { ReducerRegistry, setStateProperty } from '../redux';
|
||||
import { ReducerRegistry } from '../redux';
|
||||
|
||||
import {
|
||||
DOMINANT_SPEAKER_CHANGED,
|
||||
@@ -55,7 +55,7 @@ function participant(state, action) {
|
||||
case DOMINANT_SPEAKER_CHANGED:
|
||||
// Only one dominant speaker is allowed.
|
||||
return (
|
||||
setStateProperty(
|
||||
_setStateProperty(
|
||||
state,
|
||||
'dominantSpeaker',
|
||||
state.id === action.participant.id));
|
||||
@@ -123,7 +123,7 @@ function participant(state, action) {
|
||||
case PIN_PARTICIPANT:
|
||||
// Currently, only one pinned participant is allowed.
|
||||
return (
|
||||
setStateProperty(
|
||||
_setStateProperty(
|
||||
state,
|
||||
'pinned',
|
||||
state.id === action.participant.id));
|
||||
@@ -201,3 +201,30 @@ function _getAvatarURL(participantId, email) {
|
||||
|
||||
return urlPref + avatarId + urlSuf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific property of a specific state to a specific value. Prevents
|
||||
* unnecessary state changes (when the specified <tt>value</tt> is equal to the
|
||||
* value of the specified <tt>property</tt> of the specified <tt>state</tt>).
|
||||
*
|
||||
* @param {Object} state - The (Redux) state from which a new state is to be
|
||||
* constructed by setting the specified <tt>property</tt> to the specified
|
||||
* <tt>value</tt>.
|
||||
* @param {string} property - The property of <tt>state</tt> which is to be
|
||||
* assigned the specified <tt>value</tt> (in the new state).
|
||||
* @param {*} value - The value to assign to the specified <tt>property</tt>.
|
||||
* @returns {Object} The specified <tt>state</tt> if the value of the specified
|
||||
* <tt>property</tt> equals the specified <tt>value/tt>; otherwise, a new state
|
||||
* constructed from the specified <tt>state</tt> by setting the specified
|
||||
* <tt>property</tt> to the specified <tt>value</tt>.
|
||||
*/
|
||||
function _setStateProperty(state, property, value) {
|
||||
if (state[property] !== value) {
|
||||
return {
|
||||
...state,
|
||||
[property]: value
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
3
react/features/base/react-native/POSIX.js
vendored
3
react/features/base/react-native/POSIX.js
vendored
@@ -1,3 +0,0 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
export default NativeModules.POSIX;
|
||||
1
react/features/base/react-native/index.js
vendored
1
react/features/base/react-native/index.js
vendored
@@ -1 +0,0 @@
|
||||
export { default as POSIX } from './POSIX';
|
||||
@@ -1,24 +0,0 @@
|
||||
// FIXME React Native does not polyfill Symbol at versions 0.39.2 or earlier.
|
||||
export default (global => {
|
||||
let clazz = global.Symbol;
|
||||
|
||||
if (typeof clazz === 'undefined') {
|
||||
// XXX At the time of this writing we use Symbol only as a way to
|
||||
// prevent collisions in Redux action types. Consequently, the Symbol
|
||||
// implementation provided bellow is minimal and specific to our
|
||||
// purpose.
|
||||
const toString = function() {
|
||||
return this.join(''); // eslint-disable-line no-invalid-this
|
||||
};
|
||||
|
||||
clazz = description => {
|
||||
const thiz = (description || '').split('');
|
||||
|
||||
thiz.toString = toString;
|
||||
|
||||
return thiz;
|
||||
};
|
||||
}
|
||||
|
||||
return clazz;
|
||||
})(global || window || this); // eslint-disable-line no-invalid-this
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './components';
|
||||
export * from './functions';
|
||||
export { default as Symbol } from './Symbol';
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
/**
|
||||
* Sets specific properties of a specific state to specific values and prevents
|
||||
* unnecessary state changes.
|
||||
*
|
||||
* @param {Object} target - The state on which the specified properties are to
|
||||
* be set.
|
||||
* @param {Object} source - The map of properties to values which are to be set
|
||||
* on the specified target.
|
||||
* @returns {Object} The specified target if the values of the specified
|
||||
* properties equal the specified values; otherwise, a new state constructed
|
||||
* from the specified target by setting the specified properties to the
|
||||
* specified values.
|
||||
*/
|
||||
export function setStateProperties(target, source) {
|
||||
let t = target;
|
||||
|
||||
for (const property in source) { // eslint-disable-line guard-for-in
|
||||
t = setStateProperty(t, property, source[property], t === target);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a specific property of a specific state to a specific value. Prevents
|
||||
* unnecessary state changes (when the specified <tt>value</tt> is equal to the
|
||||
* value of the specified <tt>property</tt> of the specified <tt>state</tt>).
|
||||
*
|
||||
* @param {Object} state - The (Redux) state from which a new state is to be
|
||||
* constructed by setting the specified <tt>property</tt> to the specified
|
||||
* <tt>value</tt>.
|
||||
* @param {string} property - The property of <tt>state</tt> which is to be
|
||||
* assigned the specified <tt>value</tt> (in the new state).
|
||||
* @param {*} value - The value to assign to the specified <tt>property</tt>.
|
||||
* @returns {Object} The specified <tt>state</tt> if the value of the specified
|
||||
* <tt>property</tt> equals the specified <tt>value/tt>; otherwise, a new state
|
||||
* constructed from the specified <tt>state</tt> by setting the specified
|
||||
* <tt>property</tt> to the specified <tt>value</tt>.
|
||||
*/
|
||||
export function setStateProperty(state, property, value) {
|
||||
return _setStateProperty(state, property, value, /* copyOnWrite */ true);
|
||||
}
|
||||
|
||||
/* eslint-disable max-params */
|
||||
|
||||
/**
|
||||
* Sets a specific property of a specific state to a specific value. Prevents
|
||||
* unnecessary state changes (when the specified <tt>value</tt> is equal to the
|
||||
* value of the specified <tt>property</tt> of the specified <tt>state</tt>).
|
||||
*
|
||||
* @param {Object} state - The (Redux) state from which a state is to be
|
||||
* constructed by setting the specified <tt>property</tt> to the specified
|
||||
* <tt>value</tt>.
|
||||
* @param {string} property - The property of <tt>state</tt> which is to be
|
||||
* assigned the specified <tt>value</tt>.
|
||||
* @param {*} value - The value to assign to the specified <tt>property</tt>.
|
||||
* @param {boolean} copyOnWrite - If the specified <tt>state</tt> is to not be
|
||||
* modified, <tt>true</tt>; otherwise, <tt>false</tt>.
|
||||
* @returns {Object} The specified <tt>state</tt> if the value of the specified
|
||||
* <tt>property</tt> equals the specified <tt>value/tt> or <tt>copyOnWrite</tt>
|
||||
* is truthy; otherwise, a new state constructed from the specified
|
||||
* <tt>state</tt> by setting the specified <tt>property</tt> to the specified
|
||||
* <tt>value</tt>.
|
||||
*/
|
||||
function _setStateProperty(state, property, value, copyOnWrite) {
|
||||
// Delete state properties that are to be set to undefined. (It is a matter
|
||||
// of personal preference, mostly.)
|
||||
if (typeof value === 'undefined'
|
||||
&& Object.prototype.hasOwnProperty.call(state, property)) {
|
||||
const newState = copyOnWrite ? { ...state } : state;
|
||||
|
||||
if (delete newState[property]) {
|
||||
return newState;
|
||||
}
|
||||
}
|
||||
|
||||
if (state[property] !== value) {
|
||||
if (copyOnWrite) {
|
||||
return {
|
||||
...state,
|
||||
[property]: value
|
||||
};
|
||||
}
|
||||
|
||||
state[property] = value;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/* eslint-enable max-params */
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './functions';
|
||||
export { default as MiddlewareRegistry } from './MiddlewareRegistry';
|
||||
export { default as ReducerRegistry } from './ReducerRegistry';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Symbol } from '../react';
|
||||
|
||||
/**
|
||||
* Action for when a track has been added to the conference,
|
||||
* local or remote.
|
||||
@@ -9,7 +7,7 @@ import { Symbol } from '../react';
|
||||
* track: Track
|
||||
* }
|
||||
*/
|
||||
export const TRACK_ADDED = Symbol('TRACK_ADDED');
|
||||
export const TRACK_ADDED = 'TRACK_ADDED';
|
||||
|
||||
/**
|
||||
* Action for when a track has been removed from the conference,
|
||||
@@ -20,7 +18,7 @@ export const TRACK_ADDED = Symbol('TRACK_ADDED');
|
||||
* track: Track
|
||||
* }
|
||||
*/
|
||||
export const TRACK_REMOVED = Symbol('TRACK_REMOVED');
|
||||
export const TRACK_REMOVED = 'TRACK_REMOVED';
|
||||
|
||||
/**
|
||||
* Action for when a track properties were updated.
|
||||
@@ -30,4 +28,4 @@ export const TRACK_REMOVED = Symbol('TRACK_REMOVED');
|
||||
* track: Track
|
||||
* }
|
||||
*/
|
||||
export const TRACK_UPDATED = Symbol('TRACK_UPDATED');
|
||||
export const TRACK_UPDATED = 'TRACK_UPDATED';
|
||||
|
||||
@@ -109,8 +109,7 @@ function _mutedChanged(store, action, mediaType) {
|
||||
* @param {Action} action - The Redux action <tt>TRACK_UPDATED</tt> which is
|
||||
* being dispatched in the specified <tt>store</tt>.
|
||||
* @private
|
||||
* @returns {Object} The new state that is the result of the reduction of the
|
||||
* specified <tt>action</tt>.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _trackUpdated(store, next, action) {
|
||||
// Determine the muted state of the local track before the update.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user