Compare commits

..

1 Commits

Author SHA1 Message Date
paweldomas
cf83b4142a ref(config): move loggingConfig to config.js 2016-12-01 16:59:39 -06:00
135 changed files with 3354 additions and 5832 deletions

View File

@@ -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).

View File

@@ -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(

View File

@@ -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'
}

View File

@@ -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">

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}
}

View File

@@ -18,4 +18,3 @@
# org.gradle.parallel=true
android.useDeprecatedNdk=true
version=1

257
app.js
View File

@@ -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;

View File

@@ -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>

View File

@@ -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());
}

View File

@@ -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>

View File

@@ -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);
}
};

View File

@@ -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'
};

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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";
}
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -82,8 +82,4 @@
color: $linkHoverFontColor;
}
}
&_center {
float: none !important;
}
}

View File

@@ -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;

View File

@@ -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
View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:
```
{

View File

@@ -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

Binary file not shown.

View File

@@ -14,6 +14,7 @@
<glyph unicode="&#xe900;" 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="&#xe901;" 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="&#xe902;" 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="&#xe903;" 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="&#xe904;" glyph-name="kick" d="M512 810l284-426h-568zM214 298h596v-84h-596v84z" />
<glyph unicode="&#xe905;" 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="&#xe906;" 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="&#xe91e;" 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="&#xe91f;" glyph-name="menu-up" d="M512 682l256-256-60-60-196 196-196-196-60 60z" />
<glyph unicode="&#xe920;" glyph-name="menu-down" d="M708 658l60-60-256-256-256 256 60 60 196-196z" />
<glyph unicode="&#xe921;" 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

Binary file not shown.

Binary file not shown.

View File

@@ -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": [

View File

@@ -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>"
+ "&nbsp;&nbsp;&nbsp;"
+ "<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>

View File

@@ -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,

View File

@@ -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];

View File

@@ -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>

View File

@@ -1,4 +0,0 @@
#import "RCTBridgeModule.h"
@interface POSIX : NSObject<RCTBridgeModule>
@end

View File

@@ -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

View File

@@ -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 */,
);

View File

@@ -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": ""
}

View File

@@ -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": "&nbsp;",
"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": {

View File

@@ -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": "&nbsp;",
"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"
},

View File

@@ -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": ""
}
}

View File

@@ -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",

View File

@@ -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'
};

View File

@@ -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]);
});
}

View File

@@ -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});
};
/**

View File

@@ -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);
};
/**

View File

@@ -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');

View File

@@ -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();
}
};

View File

@@ -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;

View File

@@ -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");

View File

@@ -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

View File

@@ -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);

View 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;

View 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;

View 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;

View File

@@ -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"
}
}

View File

@@ -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';

View File

@@ -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());
});
}
};
}

View File

@@ -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);
}
}

View File

@@ -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.

View File

@@ -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 */
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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';

View File

@@ -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)));
};
}

View File

@@ -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);
}

View File

@@ -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;
});

View File

@@ -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';

View 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
};
}

View File

@@ -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
};
}

View File

@@ -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
};
}

View File

@@ -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

View File

@@ -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';

View File

@@ -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 => {

View File

@@ -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;
});
}

View File

@@ -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
});
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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
}
};
}

View File

@@ -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';

View File

@@ -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';

View File

@@ -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:

View File

@@ -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;
}

View File

@@ -1,3 +0,0 @@
import { NativeModules } from 'react-native';
export default NativeModules.POSIX;

View File

@@ -1 +0,0 @@
export { default as POSIX } from './POSIX';

View File

@@ -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

View File

@@ -1,3 +1,2 @@
export * from './components';
export * from './functions';
export { default as Symbol } from './Symbol';

View File

@@ -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 */

View File

@@ -1,3 +1,2 @@
export * from './functions';
export { default as MiddlewareRegistry } from './MiddlewareRegistry';
export { default as ReducerRegistry } from './ReducerRegistry';

View File

@@ -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';

View File

@@ -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